mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(linux): show clear update status and recover release-page retries (#158037)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
parent
92b129bcdc
commit
98ced6a5d7
2 changed files with 103 additions and 49 deletions
|
|
@ -705,7 +705,7 @@ await listen("updater://not-available", () => {
|
|||
await listen("updater://available", ({ payload }) => {
|
||||
elements.updateProgress.removeAttribute("value");
|
||||
renderUpdate({
|
||||
message: payload.notes || "Downloading in the background…",
|
||||
message: "Downloading in the background…",
|
||||
progress: true,
|
||||
title: `Update available v${payload.version} — downloading…`,
|
||||
});
|
||||
|
|
@ -729,23 +729,33 @@ await listen("updater://ready", ({ payload }) => {
|
|||
});
|
||||
});
|
||||
await listen("updater://available-manual", ({ payload }) => {
|
||||
const openDownloadPage = () =>
|
||||
invoke("open_release_page").catch((error) => {
|
||||
if (updateAction !== openDownloadPage || elements.updateBanner.classList.contains("hidden")) {
|
||||
return;
|
||||
}
|
||||
renderUpdate({
|
||||
action: openDownloadPage,
|
||||
actionLabel: "Open download page",
|
||||
const availableUpdate = {
|
||||
message: "Install the latest system package from the release page.",
|
||||
title: `Update available v${payload.version}`,
|
||||
};
|
||||
const openDownloadPage = async () => {
|
||||
let result = availableUpdate;
|
||||
try {
|
||||
await invoke("open_release_page");
|
||||
} catch (error) {
|
||||
result = {
|
||||
message: friendlyError(error),
|
||||
title: "Could not open release page",
|
||||
});
|
||||
};
|
||||
}
|
||||
if (updateAction !== openDownloadPage || elements.updateBanner.classList.contains("hidden")) {
|
||||
return;
|
||||
}
|
||||
renderUpdate({
|
||||
...result,
|
||||
action: openDownloadPage,
|
||||
actionLabel: "Open download page",
|
||||
});
|
||||
};
|
||||
renderUpdate({
|
||||
...availableUpdate,
|
||||
action: openDownloadPage,
|
||||
actionLabel: "Open download page",
|
||||
message: payload.notes || "Install the latest system package from the release page.",
|
||||
title: `Update available v${payload.version}`,
|
||||
});
|
||||
});
|
||||
await listen("updater://error", ({ payload }) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { setImmediate as yieldToDashboard } from "node:timers/promises";
|
||||
import vm from "node:vm";
|
||||
import { test } from "vitest";
|
||||
import { createDeferred } from "./helpers/promise.js";
|
||||
|
|
@ -118,17 +119,39 @@ test("CLI recovery errors offer both retry and reinstall", async () => {
|
|||
assert.equal(element("#install-controls").classList.contains("hidden"), false);
|
||||
});
|
||||
|
||||
test("failed release-page opening retains an explicit download-page retry", async () => {
|
||||
test.each([
|
||||
{
|
||||
event: "updater://available",
|
||||
message: "Downloading in the background…",
|
||||
},
|
||||
{
|
||||
event: "updater://available-manual",
|
||||
message: "Install the latest system package from the release page.",
|
||||
},
|
||||
])("$event keeps release markup out of the status banner", async ({ event, message }) => {
|
||||
const { element, emit } = await mountDashboard("?mode=missingCli");
|
||||
emit(event, {
|
||||
version: "2026.9.10",
|
||||
notes: "<!-- openclaw-release-publication:docs-v1 -->\n## Changes\n\n- A release fix.",
|
||||
});
|
||||
|
||||
assert.equal(element("#update-message").textContent, message);
|
||||
});
|
||||
|
||||
test("failed release-page opening retains a retry that clears the error on success", async () => {
|
||||
const opening = createDeferred();
|
||||
const retry = createDeferred();
|
||||
let attempts = 0;
|
||||
const { element, emit, invoked } = await mountDashboard("?mode=missingCli", () =>
|
||||
++attempts === 1 ? opening.promise : Promise.resolve(),
|
||||
++attempts === 1 ? opening.promise : retry.promise,
|
||||
);
|
||||
emit("updater://available-manual", { version: "2026.9.10" });
|
||||
assert.equal(element("#update-action").textContent, "Open download page");
|
||||
element("#update-action").click();
|
||||
opening.reject(new Error("Browser unavailable"));
|
||||
await opening.promise.catch(() => {});
|
||||
// Let the VM's cross-realm promise continuation finish rendering.
|
||||
await yieldToDashboard();
|
||||
|
||||
assert.equal(element("#update-title").textContent, "Could not open release page");
|
||||
assert.equal(element("#update-message").textContent, "Browser unavailable");
|
||||
|
|
@ -137,41 +160,66 @@ test("failed release-page opening retains an explicit download-page retry", asyn
|
|||
assert.equal(attempts, 1);
|
||||
element("#update-action").click();
|
||||
assert.equal(attempts, 2);
|
||||
retry.resolve();
|
||||
await retry.promise;
|
||||
await yieldToDashboard();
|
||||
assert.equal(element("#update-title").textContent, "Update available v2026.9.10");
|
||||
assert.equal(
|
||||
element("#update-message").textContent,
|
||||
"Install the latest system package from the release page.",
|
||||
);
|
||||
assert.equal(element("#update-banner").classList.contains("hidden"), false);
|
||||
assert.equal(invoked.filter((command) => command === "open_release_page").length, 2);
|
||||
});
|
||||
|
||||
test("late release-page failure preserves a newer update action", async () => {
|
||||
const opening = createDeferred();
|
||||
const { element, emit, invoked } = await mountDashboard(
|
||||
"?mode=missingCli",
|
||||
() => opening.promise,
|
||||
);
|
||||
emit("updater://available-manual", { version: "2026.9.10" });
|
||||
element("#update-action").click();
|
||||
emit("updater://ready", { version: "2026.9.11" });
|
||||
opening.reject(new Error("Browser unavailable"));
|
||||
await opening.promise.catch(() => {});
|
||||
test.each(["success", "failure"])(
|
||||
"late release-page %s preserves a newer update action",
|
||||
async (outcome) => {
|
||||
const opening = createDeferred();
|
||||
const { element, emit, invoked } = await mountDashboard(
|
||||
"?mode=missingCli",
|
||||
() => opening.promise,
|
||||
);
|
||||
emit("updater://available-manual", { version: "2026.9.10" });
|
||||
element("#update-action").click();
|
||||
emit("updater://ready", { version: "2026.9.11" });
|
||||
if (outcome === "failure") {
|
||||
opening.reject(new Error("Browser unavailable"));
|
||||
} else {
|
||||
opening.resolve();
|
||||
}
|
||||
await opening.promise.catch(() => {});
|
||||
await yieldToDashboard();
|
||||
|
||||
assert.equal(element("#update-title").textContent, "Update ready");
|
||||
assert.equal(element("#update-action").textContent, "Restart to update");
|
||||
assert.equal(element("#update-action").classList.contains("hidden"), false);
|
||||
element("#update-action").click();
|
||||
assert.equal(invoked.at(-1), "relaunch");
|
||||
});
|
||||
assert.equal(element("#update-title").textContent, "Update ready");
|
||||
assert.equal(element("#update-action").textContent, "Restart to update");
|
||||
assert.equal(element("#update-action").classList.contains("hidden"), false);
|
||||
element("#update-action").click();
|
||||
assert.equal(invoked.at(-1), "relaunch");
|
||||
},
|
||||
);
|
||||
|
||||
test("late release-page failure does not reopen a dismissed update banner", async () => {
|
||||
const opening = createDeferred();
|
||||
const { element, emit } = await mountDashboard("?mode=missingCli", () => opening.promise);
|
||||
emit("updater://available-manual", { version: "2026.9.10" });
|
||||
element("#update-action").click();
|
||||
element("#update-dismiss").click();
|
||||
assert.equal(element("#update-banner").classList.contains("hidden"), true);
|
||||
opening.reject(new Error("Browser unavailable"));
|
||||
await opening.promise.catch(() => {});
|
||||
test.each(["success", "failure"])(
|
||||
"late release-page %s does not reopen a dismissed update banner",
|
||||
async (outcome) => {
|
||||
const opening = createDeferred();
|
||||
const { element, emit } = await mountDashboard("?mode=missingCli", () => opening.promise);
|
||||
emit("updater://available-manual", { version: "2026.9.10" });
|
||||
element("#update-action").click();
|
||||
element("#update-dismiss").click();
|
||||
assert.equal(element("#update-banner").classList.contains("hidden"), true);
|
||||
if (outcome === "failure") {
|
||||
opening.reject(new Error("Browser unavailable"));
|
||||
} else {
|
||||
opening.resolve();
|
||||
}
|
||||
await opening.promise.catch(() => {});
|
||||
await yieldToDashboard();
|
||||
|
||||
assert.equal(element("#update-banner").classList.contains("hidden"), true);
|
||||
assert.equal(element("#update-title").textContent, "Update available v2026.9.10");
|
||||
});
|
||||
assert.equal(element("#update-banner").classList.contains("hidden"), true);
|
||||
assert.equal(element("#update-title").textContent, "Update available v2026.9.10");
|
||||
},
|
||||
);
|
||||
|
||||
test.each([
|
||||
{ name: "local", remote: null, failure: false, entry: "settings", lateRetry: false },
|
||||
|
|
@ -259,14 +307,10 @@ test.each([
|
|||
}
|
||||
elements.get("#edit-connection")?.click();
|
||||
// Drain the actual click's promise continuations, without changing the handler.
|
||||
await new Promise<void>((resolve) => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
await yieldToDashboard();
|
||||
if (lateRetry) {
|
||||
retry.resolve({ phase: "remoteError" });
|
||||
await new Promise<void>((resolve) => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
await yieldToDashboard();
|
||||
}
|
||||
assert.equal(elements.get("#title")?.textContent, "Connection Settings");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue