diff --git a/.github/pages/builds.html b/.github/pages/builds.html
index 8f35c5c5d..5deadf310 100644
--- a/.github/pages/builds.html
+++ b/.github/pages/builds.html
@@ -364,7 +364,11 @@
Save and apply
const r = repo.includes("/") ? ` -R ${repo}` : "";
return `gh workflow run reconfigure.yml${r} \\\n -f builds="${buildsInput()}"`;
}
- function workflowUrl() { return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml"; }
+ function workflowUrl() {
+ const parts = repo.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ }
function update() {
$("ghCmd").textContent = genGhCmd();
const wf = $("wfLink"); if (wf) wf.href = workflowUrl();
@@ -379,9 +383,19 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || ""; st.className = "status" + (kind ? " " + kind : "");
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
+ st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
const p = $("tokPanel"); if (!p) return;
@@ -407,7 +421,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -416,7 +430,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/.github/pages/configure.html b/.github/pages/configure.html
index 75c78c32e..f674d428f 100644
--- a/.github/pages/configure.html
+++ b/.github/pages/configure.html
@@ -1243,8 +1243,9 @@ Save and apply
}
function workflowUrl() {
- const repo = $("repo").value.trim();
- return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ const parts = $("repo").value.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
}
function token() { try { return localStorage.getItem(TOK_KEY) || ""; } catch (_) { return ""; } }
function tokenUrl() {
@@ -1257,9 +1258,18 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
@@ -1301,7 +1311,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings files are updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings files are updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -1316,7 +1326,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/.github/pages/integrate.html b/.github/pages/integrate.html
index 615b9f1d7..15df1dba9 100644
--- a/.github/pages/integrate.html
+++ b/.github/pages/integrate.html
@@ -743,21 +743,42 @@ 4. Finish setup
const repo = $("repo").value.trim() || "/";
const rel = selectedRelease();
const pinned = rel && catalog.containers && rel.id !== catalog.containers.default;
+ const varsStep = $("varsStep");
+ if (!varsStep) return;
+ const code = text => {
+ const el = document.createElement("code");
+ el.className = "inl";
+ el.textContent = text;
+ return el;
+ };
if (isGitLab()) {
- $("varsStep").innerHTML =
- `Optional GitLab CI/CD variables can override installer defaults later; the initial scaffold records LabVIEW ${ver} in .github/labview-ci.yml.`;
+ varsStep.replaceChildren(
+ `Optional GitLab CI/CD variables can override installer defaults later; the initial scaffold records LabVIEW ${ver} in `,
+ code(".github/labview-ci.yml"),
+ "."
+ );
} else if (pinned) {
const s = source();
const srcGhcr = "ghcr.io/" + s.repo.toLowerCase();
- $("varsStep").innerHTML =
- `Pinning this repo to ${esc(rel.label)}: the browser install sets ` +
- `LCWC_BASE_IMAGE=${esc(srcGhcr)}-labview-base:${esc(rel.id)} and ` +
- `LCWC_LINUX_BASE_IMAGE=${esc(srcGhcr)}-labview:linux-${esc(rel.id)} as Actions variables ` +
- `(command-line installs: set them with gh variable set).`;
+ const release = document.createElement("strong");
+ release.textContent = rel.label;
+ varsStep.replaceChildren(
+ "Pinning this repo to ",
+ release,
+ ": the browser install sets ",
+ code(`LCWC_BASE_IMAGE=${srcGhcr}-labview-base:${rel.id}`),
+ " and ",
+ code(`LCWC_LINUX_BASE_IMAGE=${srcGhcr}-labview:linux-${rel.id}`),
+ " as Actions variables (command-line installs: set them with ",
+ code("gh variable set"),
+ ")."
+ );
} else {
- $("varsStep").innerHTML =
- `Optionally pin settings as Actions variables: ` +
- `gh variable set LABVIEW_VERSION -R ${repo} -b ${ver} (has a safe fallback).`;
+ varsStep.replaceChildren(
+ "Optionally pin settings as Actions variables: ",
+ code(`gh variable set LABVIEW_VERSION -R ${repo} -b ${ver}`),
+ " (has a safe fallback)."
+ );
}
}
@@ -2351,27 +2372,94 @@ 4. Finish setup
const dashLink0 = $("insDashLink"); if (dashLink0) dashLink0.style.display = "none";
const showDashLink = () => {
const dl = $("insDashLink");
- if (dl) { dl.href = url; dl.innerHTML = "Open your dashboard ↗"; dl.style.display = ""; }
+ if (dl) { dl.href = url; dl.textContent = "Open your dashboard \u2197"; dl.style.display = ""; }
};
setInsStatus(`✓ Done \u2014 opened pull request #${pr.number} in ${esc(repo)}.`, "ok");
const done = [];
- if (setup.kept) done.push(`Kept your existing settings — ${esc(setup.kept)}.`);
- if (setup.removed && setup.removed.length) done.push(`Removed ${setup.removed.length} old tooling file${setup.removed.length > 1 ? "s" : ""} no longer used (in the pull request).`);
- if (setup.readme) done.push(`Added a CI dashboard badge & link to your README (in the pull request).`);
- if (setup.workflowPerms === "done") done.push(`Set Workflow permissions to read & write.`);
- if (setup.toolingToken === "done") done.push(`Saved a one-time update token — future Update now clicks open their pull request automatically.`);
- if (setup.buildImages && autoMerge) done.push(`Watching your worker container image build${setup.buildImage && setup.buildLinuxImage ? "s" : ""} — Windows and Linux image workflows are tracked separately below so it is clear which background work is still running.`);
+ const addDone = build => {
+ const item = document.createElement("li");
+ build(item);
+ done.push(item);
+ };
+ const appendText = (parent, text) => parent.appendChild(document.createTextNode(text));
+ const appendEmphasis = (parent, tag, text, className) => {
+ const el = document.createElement(tag);
+ if (className) el.className = className;
+ el.textContent = text;
+ parent.appendChild(el);
+ return el;
+ };
+ if (setup.kept) addDone(item => {
+ appendText(item, "Kept your existing settings - ");
+ appendEmphasis(item, "strong", setup.kept);
+ appendText(item, ".");
+ });
+ if (setup.removed && setup.removed.length) addDone(item => {
+ appendText(item, "Removed ");
+ appendEmphasis(item, "strong", setup.removed.length);
+ appendText(item, ` old tooling file${setup.removed.length > 1 ? "s" : ""} no longer used (in the pull request).`);
+ });
+ if (setup.readme) addDone(item => appendText(item, "Added a CI dashboard badge and link to your README (in the pull request)."));
+ if (setup.workflowPerms === "done") addDone(item => {
+ appendText(item, "Set ");
+ appendEmphasis(item, "strong", "Workflow permissions");
+ appendText(item, " to read and write.");
+ });
+ if (setup.toolingToken === "done") addDone(item => {
+ appendText(item, "Saved a one-time ");
+ appendEmphasis(item, "strong", "update token");
+ appendText(item, " - future ");
+ appendEmphasis(item, "em", "Update now");
+ appendText(item, " clicks open their pull request automatically.");
+ });
+ if (setup.buildImages && autoMerge) addDone(item => {
+ appendText(item, "Watching your ");
+ appendEmphasis(item, "strong", `worker container image build${setup.buildImage && setup.buildLinuxImage ? "s" : ""}`);
+ appendText(item, " - Windows and Linux image workflows are tracked separately below so it is clear which background work is still running.");
+ });
// Show the dashboard URL as PLAIN TEXT until Pages is confirmed serving it
// (revealLiveLink upgrades #insLiveUrl to a real link) so we never present a
// link that 404s. When Pages already existed it's almost certainly live, so
// that case links right away.
- if (setup.pages === "done") done.push(`Enabled GitHub Pages — your dashboard will be at ${esc(url)}It publishes within a minute of the install merge — no CI run required; the link activates here the moment it’s live.`);
- else if (setup.pages === "exists") done.push(`GitHub Pages is already on — dashboard at ${esc(url)}.`);
- else if (setup.hasDashboard) done.push(`Your dashboard will be at ${esc(url)} once GitHub Pages is on.`);
+ const appendDashboardUrl = (item, live) => {
+ const location = document.createElement("span");
+ location.id = "insLiveUrl";
+ if (live) {
+ const link = document.createElement("a");
+ link.href = url;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = url;
+ location.appendChild(link);
+ } else {
+ appendEmphasis(location, "code", url, "inl");
+ }
+ item.appendChild(location);
+ };
+ if (setup.pages === "done") addDone(item => {
+ appendText(item, "Enabled ");
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " - your dashboard will be at ");
+ appendDashboardUrl(item, false);
+ appendEmphasis(item, "small", "It publishes within a minute of the install merge - no CI run required; the link activates here the moment it's live.").id = "insLiveNote";
+ });
+ else if (setup.pages === "exists") addDone(item => {
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " is already on - dashboard at ");
+ appendDashboardUrl(item, true);
+ appendText(item, ".");
+ });
+ else if (setup.hasDashboard) addDone(item => {
+ appendText(item, "Your dashboard will be at ");
+ appendDashboardUrl(item, false);
+ appendText(item, " once ");
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " is on.");
+ });
const doneEl = $("insDone");
- if (doneEl) { doneEl.innerHTML = done.map(d => `${d}`).join(""); doneEl.style.display = done.length ? "" : "none"; }
+ if (doneEl) { doneEl.replaceChildren(...done); doneEl.style.display = done.length ? "" : "none"; }
const steps = [];
steps.push(`Review and merge pull request #${pr.number}.`);
diff --git a/.github/pages/lvci-header.js b/.github/pages/lvci-header.js
index c590dfbec..5609320d6 100644
--- a/.github/pages/lvci-header.js
+++ b/.github/pages/lvci-header.js
@@ -1284,7 +1284,7 @@
sel.addEventListener('change', function () {
syncSteps();
var v = sel.value;
- if (v && v !== cfg.sha) window.location.href = docDest(v);
+ if (v && v !== cfg.sha && SHA_RE.test(v)) window.location.href = docDest(v);
});
sel._lvciSync = function () { rp.refresh(); syncSteps(); };
wrap.appendChild(lbl); wrap.appendChild(prev); wrap.appendChild(rp.button); wrap.appendChild(next);
@@ -1953,10 +1953,20 @@
pendbar.id = 'lvci-pendbar';
pendbar.className = 'lvci-depbar';
pendbar.setAttribute('role', 'alert');
- pendbar.innerHTML =
- '\u26A0\uFE0F Dependencies need to be installed into your containers. ' +
- 'Your project declares dependencies that are not yet baked into the worker container(s); container CI may error or show broken code until you update them. ' +
- 'Review & update dependencies \u2197';
+ var pendText = document.createElement('span');
+ pendText.className = 'lvci-dep-txt';
+ var pendTitle = document.createElement('strong');
+ pendTitle.textContent = '\u26A0\uFE0F Dependencies need to be installed into your containers. ';
+ var pendDescription = document.createElement('span');
+ pendDescription.className = 'lvci-dep-sub';
+ pendDescription.textContent = 'Your project declares dependencies that are not yet baked into the worker container(s); container CI may error or show broken code until you update them. ';
+ var pendLink = document.createElement('a');
+ pendLink.href = navBase + '/dependencies.html';
+ pendLink.textContent = 'Review & update dependencies \u2197';
+ pendText.appendChild(pendTitle);
+ pendText.appendChild(pendDescription);
+ pendText.appendChild(pendLink);
+ pendbar.appendChild(pendText);
// Global attention bar (failure banner) — hidden until the activity poll
// finds a workflow whose newest run failed; names it + links to the run.
diff --git a/.github/pages/unit-tests.html b/.github/pages/unit-tests.html
index 09ee5927c..aadbf1fdb 100644
--- a/.github/pages/unit-tests.html
+++ b/.github/pages/unit-tests.html
@@ -332,7 +332,7 @@ 4. Review & apply
const locs = (s.locations||[]).map(x=>x.trim()).filter(Boolean);
if (locs.length){
lines.push(" locations:");
- locs.forEach(l => lines.push(` - "${l.replace(/"/g,'\\"')}"`));
+ locs.forEach(l => lines.push(` - ${JSON.stringify(l)}`));
} else {
lines.push(" locations: []");
}
@@ -491,8 +491,9 @@ 4. Review & apply
}
function workflowUrl(){
- const repo = $("repo").value;
- return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ const parts = $("repo").value.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
}
function token(){ try { return localStorage.getItem(TOK_KEY) || ""; } catch (_) { return ""; } }
function tokenUrl(){
@@ -505,9 +506,18 @@ 4. Review & apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind){
+ function setApplyStatus(message, kind, workflowLink){
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink){
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg){
@@ -534,7 +544,7 @@ 4. Review & apply
if (btn){ btn.disabled = false; btn.textContent = "Save & Apply"; }
if (r.status === 204){
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401){ try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -543,7 +553,7 @@ 4. Review & apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
}catch(e){
if (btn){ btn.disabled = false; btn.textContent = "Save & Apply"; }
- setApplyStatus("Network error: " + esc(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/.github/pages/vi-analyzer.html b/.github/pages/vi-analyzer.html
index 00e15050b..e5fadd171 100644
--- a/.github/pages/vi-analyzer.html
+++ b/.github/pages/vi-analyzer.html
@@ -370,7 +370,11 @@ Save and apply
` -f via_default="${ci.def}"` +
(ci.rules ? ` \\\n -f via_rules="${ci.rules}"` : "");
}
- function workflowUrl() { return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml"; }
+ function workflowUrl() {
+ const parts = repo.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ }
function update() {
$("ghCmd").textContent = genGhCmd();
const wf = $("wfLink"); if (wf) wf.href = workflowUrl();
@@ -385,9 +389,18 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
@@ -415,7 +428,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -424,7 +437,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/.github/pages/whats-new.html b/.github/pages/whats-new.html
index de1dee00f..d3c31e29b 100644
--- a/.github/pages/whats-new.html
+++ b/.github/pages/whats-new.html
@@ -682,16 +682,41 @@ Finish the update — add a one-time token
prog.phase === PH.PUBLISHING ? ("Publishing v" + prog.ver + "\u2026") :
prog.phase === PH.ERROR ? "Update didn\u2019t start" :
("Updating to v" + prog.ver + "\u2026");
- const rp = $("upRepo"); if (rp) rp.innerHTML = state.repo ? `${esc(state.repo)}` : "";
- if (msg != null) { const m = $("upMsg"); if (m) m.innerHTML = msg; }
- const links = [];
- if (prog.phase === PH.DONE) {
- const dash = clientPagesUrl();
- if (dash) links.push(`Go to your dashboard ↗`);
+ const rp = $("upRepo");
+ if (rp) {
+ rp.replaceChildren();
+ if (state.repo) {
+ const link = document.createElement("a");
+ link.href = ghUrl(state.repo);
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = state.repo;
+ rp.appendChild(link);
+ }
+ }
+ if (msg != null) { const m = $("upMsg"); if (m) m.textContent = msg; }
+ const lz = $("upLinks");
+ if (lz) {
+ lz.replaceChildren();
+ const appendLink = (url, text, primary) => {
+ let safeUrl;
+ try {
+ const parsed = new URL(url, location.href);
+ if (parsed.protocol !== "https:") return;
+ safeUrl = parsed.href;
+ } catch (_) { return; }
+ const link = document.createElement("a");
+ if (primary) link.className = "primary";
+ link.href = safeUrl;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = text;
+ lz.appendChild(link);
+ };
+ if (prog.phase === PH.DONE) appendLink(clientPagesUrl(), "Go to your dashboard \u2197", true);
+ if (prog.prUrl) appendLink(prog.prUrl, `Open pull request${prog.prNum ? " #" + prog.prNum : ""} \u2197`);
+ if (prog.runUrl) appendLink(prog.runUrl, "Open the workflow run \u2197");
}
- if (prog.prUrl) links.push(`Open pull request${prog.prNum ? " #" + prog.prNum : ""} ↗`);
- if (prog.runUrl) links.push(`Open the workflow run ↗`);
- const lz = $("upLinks"); if (lz) lz.innerHTML = links.join("");
const foot = $("upProgFoot");
if (foot) {
// Dismiss only when the update has truly stopped (done/failed). While it
@@ -755,13 +780,13 @@ Finish the update — add a one-time token
prog.runId = run.id; prog.runUrl = run.html_url;
if (run.status === "completed") {
if (run.conclusion === "success") { startPrPoll(); return; }
- setPhase(PH.ERROR, `The update workflow finished with ${esc(run.conclusion || "a failure")}. Open the run to see why, then try again.`);
+ setPhase(PH.ERROR, `The update workflow finished with ${run.conclusion || "a failure"}. Open the run to see why, then try again.`);
return;
}
setPhase(run.status === "in_progress" ? PH.RUNNING : PH.QUEUED,
run.status === "in_progress"
- ? "Building the update on GitHub Actions… this usually takes under a minute."
- : "Queued on GitHub Actions… waiting for a runner.");
+ ? "Building the update on GitHub Actions\u2026 this usually takes under a minute."
+ : "Queued on GitHub Actions\u2026 waiting for a runner.");
}
if (tries < 40) later(() => pollRun(tries + 1), 3000);
else startPrPoll(); // give up tracking the run; fall back to PR detection
@@ -769,7 +794,7 @@ Finish the update — add a one-time token
}
function startPrPoll() {
- if (prog.phase !== PH.APPLYING) setPhase(PH.APPLYING, "Applying the update to your repository…");
+ if (prog.phase !== PH.APPLYING) setPhase(PH.APPLYING, "Applying the update to your repository\u2026");
pollOutcome(prog.ver, 0);
}
@@ -783,7 +808,7 @@ Finish the update — add a one-time token
prog.ver = ver; prog.ts = Date.now(); prog.runId = 0; prog.runUrl = ""; prog.prUrl = ""; prog.prNum = 0;
clearTimers();
setCmdStatus("", null);
- setPhase(PH.DISPATCH, "Starting the update…");
+ setPhase(PH.DISPATCH, "Starting the update\u2026");
resolveBranch().then((branch) => {
return fetch(`https://api.github.com/repos/${state.repo}/actions/workflows/apply-tooling-update.yml/dispatches`, {
method: "POST",
@@ -793,7 +818,7 @@ Finish the update — add a one-time token
body: JSON.stringify({ ref: branch, inputs: { source_ref: "v" + ver } })
});
}).then(async (r) => {
- if (r.status === 204) { setPhase(PH.QUEUED, "Queued on GitHub Actions… waiting for a runner."); pollRun(0); return; }
+ if (r.status === 204) { setPhase(PH.QUEUED, "Queued on GitHub Actions\u2026 waiting for a runner."); pollRun(0); return; }
if (r.status === 401) { setPhase(PH.IDLE); clearTok(); refreshTokForget(); setCmdStatus("That token was rejected (401). Paste a valid one.", "err"); showTokenPanel(); return; }
let ghMsg = ""; try { const j = await r.json(); ghMsg = (j && j.message) || ""; } catch (_) {}
let hint;
@@ -998,14 +1023,13 @@ Finish the update — add a one-time token
// actually paused a check ("Action required") -- point at Approve and run.
function reviewMsg(pr, ver, lead, note) {
return (lead || "") +
- `Pull request #${pr.number} is open. ` +
- `Merge it to finish updating to v${esc(ver)}.` +
+ `Pull request #${pr.number} is open. Merge it to finish updating to v${ver}.` +
(note ? " " + note : "");
}
function showReview(pr, ver, lead) {
prog.prUrl = pr.html_url; prog.prNum = pr.number;
setPhase(PH.REVIEW, reviewMsg(pr, ver, lead,
- `Most updates change only configuration and tooling, so there are no checks to wait for — you can merge right away.`));
+ "Most updates change only configuration and tooling, so there are no checks to wait for - you can merge right away."));
const sha = pr.head && pr.head.sha;
if (!sha) return;
// Best-effort: read the head commit's checks and tailor the note to reality.
@@ -1019,13 +1043,13 @@ Finish the update — add a one-time token
const running = runs.filter((r) => r.status !== "completed" && r.status !== "waiting" && r.status !== "action_required");
let note;
if (paused.length)
- note = `GitHub paused a check (“Action required”) because the pull request was opened by automation — open its Checks tab (or the repo’s Actions tab), click Approve and run, then merge.`;
+ note = "GitHub paused a check (\"Action required\") because the pull request was opened by automation - open its Checks tab (or the repository's Actions tab), click Approve and run, then merge.";
else if (running.length)
- note = `Its checks are still running — they finish on their own, then you can merge.`;
+ note = "Its checks are still running - they finish on their own, then you can merge.";
else if (!runs.length)
- note = `This update has no checks to wait for — it’s ready to merge now.`;
+ note = "This update has no checks to wait for - it's ready to merge now.";
else
- note = `Its checks have passed — it’s ready to merge now.`;
+ note = "Its checks have passed - it's ready to merge now.";
renderProgress(reviewMsg(pr, ver, lead, note));
})
.catch(() => {});
@@ -1364,8 +1388,8 @@ Finish the update — add a one-time token
prog.ts = s.ts || Date.now(); prog.runId = s.runId || 0; prog.runUrl = s.runUrl || "";
prog.prUrl = s.prUrl || ""; prog.prNum = s.prNum || 0;
if (s.phase === PH.PUBLISHING) { startPublishPoll(prog.ver); }
- else if (prog.prNum) { setPhase(PH.REVIEW, `Resuming… pull request #${prog.prNum} is open. Merge it to finish updating to v${esc(prog.ver)}. Most updates have no checks to wait for — you can merge right away.`); pollPrMerged(prog.prNum, 0); }
- else if (prog.runId || s.phase >= PH.QUEUED) { setPhase(PH.RUNNING, "Resuming… checking the update workflow’s progress."); pollRun(0); }
+ else if (prog.prNum) { setPhase(PH.REVIEW, `Resuming\u2026 pull request #${prog.prNum} is open. Merge it to finish updating to v${prog.ver}. Most updates have no checks to wait for - you can merge right away.`); pollPrMerged(prog.prNum, 0); }
+ else if (prog.runId || s.phase >= PH.QUEUED) { setPhase(PH.RUNNING, "Resuming\u2026 checking the update workflow's progress."); pollRun(0); }
else { startPrPoll(); }
}
diff --git a/actions/dashboard/builds.html b/actions/dashboard/builds.html
index 8f35c5c5d..5deadf310 100644
--- a/actions/dashboard/builds.html
+++ b/actions/dashboard/builds.html
@@ -364,7 +364,11 @@ Save and apply
const r = repo.includes("/") ? ` -R ${repo}` : "";
return `gh workflow run reconfigure.yml${r} \\\n -f builds="${buildsInput()}"`;
}
- function workflowUrl() { return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml"; }
+ function workflowUrl() {
+ const parts = repo.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ }
function update() {
$("ghCmd").textContent = genGhCmd();
const wf = $("wfLink"); if (wf) wf.href = workflowUrl();
@@ -379,9 +383,19 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || ""; st.className = "status" + (kind ? " " + kind : "");
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
+ st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
const p = $("tokPanel"); if (!p) return;
@@ -407,7 +421,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -416,7 +430,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/actions/dashboard/configure.html b/actions/dashboard/configure.html
index 75c78c32e..2fba5bd4f 100644
--- a/actions/dashboard/configure.html
+++ b/actions/dashboard/configure.html
@@ -1243,8 +1243,9 @@ Save and apply
}
function workflowUrl() {
- const repo = $("repo").value.trim();
- return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ const parts = $("repo").value.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
}
function token() { try { return localStorage.getItem(TOK_KEY) || ""; } catch (_) { return ""; } }
function tokenUrl() {
@@ -1257,9 +1258,18 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
@@ -1281,7 +1291,7 @@ Save and apply
async function explainDispatch404(repo, file) {
try {
const r = await fetch("https://api.github.com/repos/" + repo + "/contents/.github/workflows/" + file, { headers: { "Accept": "application/vnd.github+json" }, cache: "no-cache" });
- if (r.ok) return "Not a token problem — " + escapeHtml(file) + " exists in this repository, but GitHub has not registered it as a runnable workflow, so dispatching it returns 404. This happens when workflow files were pushed while GitHub Actions was disabled (typical for fork installs). Fix: run What's New ▸ Update now — tooling updates rewrite every workflow file (each carries a version stamp), and that push makes GitHub register them — or push any commit that edits .github/workflows/" + escapeHtml(file) + ", then retry.";
+ if (r.ok) return `Not a token problem - ${file} exists in this repository, but GitHub has not registered it as a runnable workflow, so dispatching it returns 404. This happens when workflow files were pushed while GitHub Actions was disabled (typical for fork installs). Fix: run What's New > Update now - tooling updates rewrite every workflow file (each carries a version stamp), and that push makes GitHub register them - or push any commit that edits .github/workflows/${file}, then retry.`;
} catch (_) {}
return "";
}
@@ -1301,7 +1311,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings files are updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings files are updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -1316,7 +1326,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/actions/dashboard/integrate.html b/actions/dashboard/integrate.html
index 615b9f1d7..15df1dba9 100644
--- a/actions/dashboard/integrate.html
+++ b/actions/dashboard/integrate.html
@@ -743,21 +743,42 @@ 4. Finish setup
const repo = $("repo").value.trim() || "/";
const rel = selectedRelease();
const pinned = rel && catalog.containers && rel.id !== catalog.containers.default;
+ const varsStep = $("varsStep");
+ if (!varsStep) return;
+ const code = text => {
+ const el = document.createElement("code");
+ el.className = "inl";
+ el.textContent = text;
+ return el;
+ };
if (isGitLab()) {
- $("varsStep").innerHTML =
- `Optional GitLab CI/CD variables can override installer defaults later; the initial scaffold records LabVIEW ${ver} in .github/labview-ci.yml.`;
+ varsStep.replaceChildren(
+ `Optional GitLab CI/CD variables can override installer defaults later; the initial scaffold records LabVIEW ${ver} in `,
+ code(".github/labview-ci.yml"),
+ "."
+ );
} else if (pinned) {
const s = source();
const srcGhcr = "ghcr.io/" + s.repo.toLowerCase();
- $("varsStep").innerHTML =
- `Pinning this repo to ${esc(rel.label)}: the browser install sets ` +
- `LCWC_BASE_IMAGE=${esc(srcGhcr)}-labview-base:${esc(rel.id)} and ` +
- `LCWC_LINUX_BASE_IMAGE=${esc(srcGhcr)}-labview:linux-${esc(rel.id)} as Actions variables ` +
- `(command-line installs: set them with gh variable set).`;
+ const release = document.createElement("strong");
+ release.textContent = rel.label;
+ varsStep.replaceChildren(
+ "Pinning this repo to ",
+ release,
+ ": the browser install sets ",
+ code(`LCWC_BASE_IMAGE=${srcGhcr}-labview-base:${rel.id}`),
+ " and ",
+ code(`LCWC_LINUX_BASE_IMAGE=${srcGhcr}-labview:linux-${rel.id}`),
+ " as Actions variables (command-line installs: set them with ",
+ code("gh variable set"),
+ ")."
+ );
} else {
- $("varsStep").innerHTML =
- `Optionally pin settings as Actions variables: ` +
- `gh variable set LABVIEW_VERSION -R ${repo} -b ${ver} (has a safe fallback).`;
+ varsStep.replaceChildren(
+ "Optionally pin settings as Actions variables: ",
+ code(`gh variable set LABVIEW_VERSION -R ${repo} -b ${ver}`),
+ " (has a safe fallback)."
+ );
}
}
@@ -2351,27 +2372,94 @@ 4. Finish setup
const dashLink0 = $("insDashLink"); if (dashLink0) dashLink0.style.display = "none";
const showDashLink = () => {
const dl = $("insDashLink");
- if (dl) { dl.href = url; dl.innerHTML = "Open your dashboard ↗"; dl.style.display = ""; }
+ if (dl) { dl.href = url; dl.textContent = "Open your dashboard \u2197"; dl.style.display = ""; }
};
setInsStatus(`✓ Done \u2014 opened pull request #${pr.number} in ${esc(repo)}.`, "ok");
const done = [];
- if (setup.kept) done.push(`Kept your existing settings — ${esc(setup.kept)}.`);
- if (setup.removed && setup.removed.length) done.push(`Removed ${setup.removed.length} old tooling file${setup.removed.length > 1 ? "s" : ""} no longer used (in the pull request).`);
- if (setup.readme) done.push(`Added a CI dashboard badge & link to your README (in the pull request).`);
- if (setup.workflowPerms === "done") done.push(`Set Workflow permissions to read & write.`);
- if (setup.toolingToken === "done") done.push(`Saved a one-time update token — future Update now clicks open their pull request automatically.`);
- if (setup.buildImages && autoMerge) done.push(`Watching your worker container image build${setup.buildImage && setup.buildLinuxImage ? "s" : ""} — Windows and Linux image workflows are tracked separately below so it is clear which background work is still running.`);
+ const addDone = build => {
+ const item = document.createElement("li");
+ build(item);
+ done.push(item);
+ };
+ const appendText = (parent, text) => parent.appendChild(document.createTextNode(text));
+ const appendEmphasis = (parent, tag, text, className) => {
+ const el = document.createElement(tag);
+ if (className) el.className = className;
+ el.textContent = text;
+ parent.appendChild(el);
+ return el;
+ };
+ if (setup.kept) addDone(item => {
+ appendText(item, "Kept your existing settings - ");
+ appendEmphasis(item, "strong", setup.kept);
+ appendText(item, ".");
+ });
+ if (setup.removed && setup.removed.length) addDone(item => {
+ appendText(item, "Removed ");
+ appendEmphasis(item, "strong", setup.removed.length);
+ appendText(item, ` old tooling file${setup.removed.length > 1 ? "s" : ""} no longer used (in the pull request).`);
+ });
+ if (setup.readme) addDone(item => appendText(item, "Added a CI dashboard badge and link to your README (in the pull request)."));
+ if (setup.workflowPerms === "done") addDone(item => {
+ appendText(item, "Set ");
+ appendEmphasis(item, "strong", "Workflow permissions");
+ appendText(item, " to read and write.");
+ });
+ if (setup.toolingToken === "done") addDone(item => {
+ appendText(item, "Saved a one-time ");
+ appendEmphasis(item, "strong", "update token");
+ appendText(item, " - future ");
+ appendEmphasis(item, "em", "Update now");
+ appendText(item, " clicks open their pull request automatically.");
+ });
+ if (setup.buildImages && autoMerge) addDone(item => {
+ appendText(item, "Watching your ");
+ appendEmphasis(item, "strong", `worker container image build${setup.buildImage && setup.buildLinuxImage ? "s" : ""}`);
+ appendText(item, " - Windows and Linux image workflows are tracked separately below so it is clear which background work is still running.");
+ });
// Show the dashboard URL as PLAIN TEXT until Pages is confirmed serving it
// (revealLiveLink upgrades #insLiveUrl to a real link) so we never present a
// link that 404s. When Pages already existed it's almost certainly live, so
// that case links right away.
- if (setup.pages === "done") done.push(`Enabled GitHub Pages — your dashboard will be at ${esc(url)}It publishes within a minute of the install merge — no CI run required; the link activates here the moment it’s live.`);
- else if (setup.pages === "exists") done.push(`GitHub Pages is already on — dashboard at ${esc(url)}.`);
- else if (setup.hasDashboard) done.push(`Your dashboard will be at ${esc(url)} once GitHub Pages is on.`);
+ const appendDashboardUrl = (item, live) => {
+ const location = document.createElement("span");
+ location.id = "insLiveUrl";
+ if (live) {
+ const link = document.createElement("a");
+ link.href = url;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = url;
+ location.appendChild(link);
+ } else {
+ appendEmphasis(location, "code", url, "inl");
+ }
+ item.appendChild(location);
+ };
+ if (setup.pages === "done") addDone(item => {
+ appendText(item, "Enabled ");
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " - your dashboard will be at ");
+ appendDashboardUrl(item, false);
+ appendEmphasis(item, "small", "It publishes within a minute of the install merge - no CI run required; the link activates here the moment it's live.").id = "insLiveNote";
+ });
+ else if (setup.pages === "exists") addDone(item => {
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " is already on - dashboard at ");
+ appendDashboardUrl(item, true);
+ appendText(item, ".");
+ });
+ else if (setup.hasDashboard) addDone(item => {
+ appendText(item, "Your dashboard will be at ");
+ appendDashboardUrl(item, false);
+ appendText(item, " once ");
+ appendEmphasis(item, "strong", "GitHub Pages");
+ appendText(item, " is on.");
+ });
const doneEl = $("insDone");
- if (doneEl) { doneEl.innerHTML = done.map(d => `${d}`).join(""); doneEl.style.display = done.length ? "" : "none"; }
+ if (doneEl) { doneEl.replaceChildren(...done); doneEl.style.display = done.length ? "" : "none"; }
const steps = [];
steps.push(`Review and merge pull request #${pr.number}.`);
diff --git a/actions/dashboard/lvci-header.js b/actions/dashboard/lvci-header.js
index c590dfbec..5609320d6 100644
--- a/actions/dashboard/lvci-header.js
+++ b/actions/dashboard/lvci-header.js
@@ -1284,7 +1284,7 @@
sel.addEventListener('change', function () {
syncSteps();
var v = sel.value;
- if (v && v !== cfg.sha) window.location.href = docDest(v);
+ if (v && v !== cfg.sha && SHA_RE.test(v)) window.location.href = docDest(v);
});
sel._lvciSync = function () { rp.refresh(); syncSteps(); };
wrap.appendChild(lbl); wrap.appendChild(prev); wrap.appendChild(rp.button); wrap.appendChild(next);
@@ -1953,10 +1953,20 @@
pendbar.id = 'lvci-pendbar';
pendbar.className = 'lvci-depbar';
pendbar.setAttribute('role', 'alert');
- pendbar.innerHTML =
- '\u26A0\uFE0F Dependencies need to be installed into your containers. ' +
- 'Your project declares dependencies that are not yet baked into the worker container(s); container CI may error or show broken code until you update them. ' +
- 'Review & update dependencies \u2197';
+ var pendText = document.createElement('span');
+ pendText.className = 'lvci-dep-txt';
+ var pendTitle = document.createElement('strong');
+ pendTitle.textContent = '\u26A0\uFE0F Dependencies need to be installed into your containers. ';
+ var pendDescription = document.createElement('span');
+ pendDescription.className = 'lvci-dep-sub';
+ pendDescription.textContent = 'Your project declares dependencies that are not yet baked into the worker container(s); container CI may error or show broken code until you update them. ';
+ var pendLink = document.createElement('a');
+ pendLink.href = navBase + '/dependencies.html';
+ pendLink.textContent = 'Review & update dependencies \u2197';
+ pendText.appendChild(pendTitle);
+ pendText.appendChild(pendDescription);
+ pendText.appendChild(pendLink);
+ pendbar.appendChild(pendText);
// Global attention bar (failure banner) — hidden until the activity poll
// finds a workflow whose newest run failed; names it + links to the run.
diff --git a/actions/dashboard/unit-tests.html b/actions/dashboard/unit-tests.html
index 09ee5927c..aadbf1fdb 100644
--- a/actions/dashboard/unit-tests.html
+++ b/actions/dashboard/unit-tests.html
@@ -332,7 +332,7 @@ 4. Review & apply
const locs = (s.locations||[]).map(x=>x.trim()).filter(Boolean);
if (locs.length){
lines.push(" locations:");
- locs.forEach(l => lines.push(` - "${l.replace(/"/g,'\\"')}"`));
+ locs.forEach(l => lines.push(` - ${JSON.stringify(l)}`));
} else {
lines.push(" locations: []");
}
@@ -491,8 +491,9 @@ 4. Review & apply
}
function workflowUrl(){
- const repo = $("repo").value;
- return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ const parts = $("repo").value.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
}
function token(){ try { return localStorage.getItem(TOK_KEY) || ""; } catch (_) { return ""; } }
function tokenUrl(){
@@ -505,9 +506,18 @@ 4. Review & apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind){
+ function setApplyStatus(message, kind, workflowLink){
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink){
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg){
@@ -534,7 +544,7 @@ 4. Review & apply
if (btn){ btn.disabled = false; btn.textContent = "Save & Apply"; }
if (r.status === 204){
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401){ try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -543,7 +553,7 @@ 4. Review & apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
}catch(e){
if (btn){ btn.disabled = false; btn.textContent = "Save & Apply"; }
- setApplyStatus("Network error: " + esc(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/actions/dashboard/vi-analyzer.html b/actions/dashboard/vi-analyzer.html
index 00e15050b..e5fadd171 100644
--- a/actions/dashboard/vi-analyzer.html
+++ b/actions/dashboard/vi-analyzer.html
@@ -370,7 +370,11 @@ Save and apply
` -f via_default="${ci.def}"` +
(ci.rules ? ` \\\n -f via_rules="${ci.rules}"` : "");
}
- function workflowUrl() { return repo.includes("/") ? "https://github.com/" + repo + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml"; }
+ function workflowUrl() {
+ const parts = repo.trim().split("/").filter(Boolean);
+ const repoPath = parts.length === 2 ? parts.map(encodeURIComponent).join("/") : "";
+ return repoPath ? "https://github.com/" + repoPath + "/actions/workflows/reconfigure.yml" : "https://github.com/actions/workflows/reconfigure.yml";
+ }
function update() {
$("ghCmd").textContent = genGhCmd();
const wf = $("wfLink"); if (wf) wf.href = workflowUrl();
@@ -385,9 +389,18 @@ Save and apply
qp.set("actions", "write");
return "https://github.com/settings/personal-access-tokens/new?" + qp.toString();
}
- function setApplyStatus(html, kind) {
+ function setApplyStatus(message, kind, workflowLink) {
const st = $("applyStatus"); if (!st) return;
- st.innerHTML = html || "";
+ st.replaceChildren(message || "");
+ if (workflowLink) {
+ const link = document.createElement("a");
+ link.href = workflowLink;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = "View runs";
+ st.appendChild(link);
+ st.appendChild(document.createTextNode("."));
+ }
st.className = "status" + (kind ? " " + kind : "");
}
function showTokenPanel(msg) {
@@ -415,7 +428,7 @@ Save and apply
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
if (r.status === 204) {
hideTokenPanel();
- setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. View runs.", "ok");
+ setApplyStatus("Queued Reconfigure. It will open a pull request when the settings file is updated. ", "ok", workflowUrl());
return;
}
if (r.status === 401) { try { localStorage.removeItem(TOK_KEY); } catch (_) {} showTokenPanel("That token was rejected. Paste a valid fine-grained token with Actions: Read and write for this repository."); setApplyStatus("Token rejected (401).", "err"); return; }
@@ -424,7 +437,7 @@ Save and apply
setApplyStatus("Dispatch failed (HTTP " + r.status + ").", "err");
} catch (e) {
if (btn) { btn.disabled = false; btn.textContent = "Save and apply"; }
- setApplyStatus("Network error: " + escapeHtml(String(e && e.message || e)), "err");
+ setApplyStatus("Network error: " + String(e && e.message || e), "err");
}
}
diff --git a/actions/dashboard/whats-new.html b/actions/dashboard/whats-new.html
index de1dee00f..d3c31e29b 100644
--- a/actions/dashboard/whats-new.html
+++ b/actions/dashboard/whats-new.html
@@ -682,16 +682,41 @@ Finish the update — add a one-time token
prog.phase === PH.PUBLISHING ? ("Publishing v" + prog.ver + "\u2026") :
prog.phase === PH.ERROR ? "Update didn\u2019t start" :
("Updating to v" + prog.ver + "\u2026");
- const rp = $("upRepo"); if (rp) rp.innerHTML = state.repo ? `${esc(state.repo)}` : "";
- if (msg != null) { const m = $("upMsg"); if (m) m.innerHTML = msg; }
- const links = [];
- if (prog.phase === PH.DONE) {
- const dash = clientPagesUrl();
- if (dash) links.push(`Go to your dashboard ↗`);
+ const rp = $("upRepo");
+ if (rp) {
+ rp.replaceChildren();
+ if (state.repo) {
+ const link = document.createElement("a");
+ link.href = ghUrl(state.repo);
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = state.repo;
+ rp.appendChild(link);
+ }
+ }
+ if (msg != null) { const m = $("upMsg"); if (m) m.textContent = msg; }
+ const lz = $("upLinks");
+ if (lz) {
+ lz.replaceChildren();
+ const appendLink = (url, text, primary) => {
+ let safeUrl;
+ try {
+ const parsed = new URL(url, location.href);
+ if (parsed.protocol !== "https:") return;
+ safeUrl = parsed.href;
+ } catch (_) { return; }
+ const link = document.createElement("a");
+ if (primary) link.className = "primary";
+ link.href = safeUrl;
+ link.target = "_blank";
+ link.rel = "noopener";
+ link.textContent = text;
+ lz.appendChild(link);
+ };
+ if (prog.phase === PH.DONE) appendLink(clientPagesUrl(), "Go to your dashboard \u2197", true);
+ if (prog.prUrl) appendLink(prog.prUrl, `Open pull request${prog.prNum ? " #" + prog.prNum : ""} \u2197`);
+ if (prog.runUrl) appendLink(prog.runUrl, "Open the workflow run \u2197");
}
- if (prog.prUrl) links.push(`Open pull request${prog.prNum ? " #" + prog.prNum : ""} ↗`);
- if (prog.runUrl) links.push(`Open the workflow run ↗`);
- const lz = $("upLinks"); if (lz) lz.innerHTML = links.join("");
const foot = $("upProgFoot");
if (foot) {
// Dismiss only when the update has truly stopped (done/failed). While it
@@ -755,13 +780,13 @@ Finish the update — add a one-time token
prog.runId = run.id; prog.runUrl = run.html_url;
if (run.status === "completed") {
if (run.conclusion === "success") { startPrPoll(); return; }
- setPhase(PH.ERROR, `The update workflow finished with ${esc(run.conclusion || "a failure")}. Open the run to see why, then try again.`);
+ setPhase(PH.ERROR, `The update workflow finished with ${run.conclusion || "a failure"}. Open the run to see why, then try again.`);
return;
}
setPhase(run.status === "in_progress" ? PH.RUNNING : PH.QUEUED,
run.status === "in_progress"
- ? "Building the update on GitHub Actions… this usually takes under a minute."
- : "Queued on GitHub Actions… waiting for a runner.");
+ ? "Building the update on GitHub Actions\u2026 this usually takes under a minute."
+ : "Queued on GitHub Actions\u2026 waiting for a runner.");
}
if (tries < 40) later(() => pollRun(tries + 1), 3000);
else startPrPoll(); // give up tracking the run; fall back to PR detection
@@ -769,7 +794,7 @@ Finish the update — add a one-time token
}
function startPrPoll() {
- if (prog.phase !== PH.APPLYING) setPhase(PH.APPLYING, "Applying the update to your repository…");
+ if (prog.phase !== PH.APPLYING) setPhase(PH.APPLYING, "Applying the update to your repository\u2026");
pollOutcome(prog.ver, 0);
}
@@ -783,7 +808,7 @@ Finish the update — add a one-time token
prog.ver = ver; prog.ts = Date.now(); prog.runId = 0; prog.runUrl = ""; prog.prUrl = ""; prog.prNum = 0;
clearTimers();
setCmdStatus("", null);
- setPhase(PH.DISPATCH, "Starting the update…");
+ setPhase(PH.DISPATCH, "Starting the update\u2026");
resolveBranch().then((branch) => {
return fetch(`https://api.github.com/repos/${state.repo}/actions/workflows/apply-tooling-update.yml/dispatches`, {
method: "POST",
@@ -793,7 +818,7 @@ Finish the update — add a one-time token
body: JSON.stringify({ ref: branch, inputs: { source_ref: "v" + ver } })
});
}).then(async (r) => {
- if (r.status === 204) { setPhase(PH.QUEUED, "Queued on GitHub Actions… waiting for a runner."); pollRun(0); return; }
+ if (r.status === 204) { setPhase(PH.QUEUED, "Queued on GitHub Actions\u2026 waiting for a runner."); pollRun(0); return; }
if (r.status === 401) { setPhase(PH.IDLE); clearTok(); refreshTokForget(); setCmdStatus("That token was rejected (401). Paste a valid one.", "err"); showTokenPanel(); return; }
let ghMsg = ""; try { const j = await r.json(); ghMsg = (j && j.message) || ""; } catch (_) {}
let hint;
@@ -998,14 +1023,13 @@ Finish the update — add a one-time token
// actually paused a check ("Action required") -- point at Approve and run.
function reviewMsg(pr, ver, lead, note) {
return (lead || "") +
- `Pull request #${pr.number} is open. ` +
- `Merge it to finish updating to v${esc(ver)}.` +
+ `Pull request #${pr.number} is open. Merge it to finish updating to v${ver}.` +
(note ? " " + note : "");
}
function showReview(pr, ver, lead) {
prog.prUrl = pr.html_url; prog.prNum = pr.number;
setPhase(PH.REVIEW, reviewMsg(pr, ver, lead,
- `Most updates change only configuration and tooling, so there are no checks to wait for — you can merge right away.`));
+ "Most updates change only configuration and tooling, so there are no checks to wait for - you can merge right away."));
const sha = pr.head && pr.head.sha;
if (!sha) return;
// Best-effort: read the head commit's checks and tailor the note to reality.
@@ -1019,13 +1043,13 @@ Finish the update — add a one-time token
const running = runs.filter((r) => r.status !== "completed" && r.status !== "waiting" && r.status !== "action_required");
let note;
if (paused.length)
- note = `GitHub paused a check (“Action required”) because the pull request was opened by automation — open its Checks tab (or the repo’s Actions tab), click Approve and run, then merge.`;
+ note = "GitHub paused a check (\"Action required\") because the pull request was opened by automation - open its Checks tab (or the repository's Actions tab), click Approve and run, then merge.";
else if (running.length)
- note = `Its checks are still running — they finish on their own, then you can merge.`;
+ note = "Its checks are still running - they finish on their own, then you can merge.";
else if (!runs.length)
- note = `This update has no checks to wait for — it’s ready to merge now.`;
+ note = "This update has no checks to wait for - it's ready to merge now.";
else
- note = `Its checks have passed — it’s ready to merge now.`;
+ note = "Its checks have passed - it's ready to merge now.";
renderProgress(reviewMsg(pr, ver, lead, note));
})
.catch(() => {});
@@ -1364,8 +1388,8 @@ Finish the update — add a one-time token
prog.ts = s.ts || Date.now(); prog.runId = s.runId || 0; prog.runUrl = s.runUrl || "";
prog.prUrl = s.prUrl || ""; prog.prNum = s.prNum || 0;
if (s.phase === PH.PUBLISHING) { startPublishPoll(prog.ver); }
- else if (prog.prNum) { setPhase(PH.REVIEW, `Resuming… pull request #${prog.prNum} is open. Merge it to finish updating to v${esc(prog.ver)}. Most updates have no checks to wait for — you can merge right away.`); pollPrMerged(prog.prNum, 0); }
- else if (prog.runId || s.phase >= PH.QUEUED) { setPhase(PH.RUNNING, "Resuming… checking the update workflow’s progress."); pollRun(0); }
+ else if (prog.prNum) { setPhase(PH.REVIEW, `Resuming\u2026 pull request #${prog.prNum} is open. Merge it to finish updating to v${prog.ver}. Most updates have no checks to wait for - you can merge right away.`); pollPrMerged(prog.prNum, 0); }
+ else if (prog.runId || s.phase >= PH.QUEUED) { setPhase(PH.RUNNING, "Resuming\u2026 checking the update workflow's progress."); pollRun(0); }
else { startPrPoll(); }
}