mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 12:18:37 +00:00
fix(control-ui): include basePath in default WebSocket URL (#30228)
Merged via squash.
Prepared head SHA: a56d8d441c
Co-authored-by: gittb <8284364+gittb@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
This commit is contained in:
@@ -97,6 +97,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Cron/Isolated sessions list: persist the intended pre-run model/provider on isolated cron session entries so `sessions_list` reflects payload/session model overrides even when runs fail before post-run telemetry persistence. (#21279) Thanks @altaywtf.
|
- Cron/Isolated sessions list: persist the intended pre-run model/provider on isolated cron session entries so `sessions_list` reflects payload/session model overrides even when runs fail before post-run telemetry persistence. (#21279) Thanks @altaywtf.
|
||||||
- Web UI/Chat sessions: add a cron-session visibility toggle in the session selector, fix cron-key detection across `cron:*` and `agent:*:cron:*` formats, and localize the new control labels/tooltips. (#26976) Thanks @ianderrington.
|
- Web UI/Chat sessions: add a cron-session visibility toggle in the session selector, fix cron-key detection across `cron:*` and `agent:*:cron:*` formats, and localize the new control labels/tooltips. (#26976) Thanks @ianderrington.
|
||||||
- Web UI/Cron jobs: add schedule-kind and last-run-status filters to the Jobs list, with reset control and client-side filtering over loaded results. (#9510) Thanks @guxu11.
|
- Web UI/Cron jobs: add schedule-kind and last-run-status filters to the Jobs list, with reset control and client-side filtering over loaded results. (#9510) Thanks @guxu11.
|
||||||
|
- Web UI/Control UI WebSocket defaults: include normalized `gateway.controlUi.basePath` (or inferred nested route base path) in the default `gatewayUrl` so first-load dashboard connections work behind path-based reverse proxies. (#30228) Thanks @gittb.
|
||||||
- Cron/One-shot reliability: retry transient one-shot failures with bounded backoff and configurable retry policy before disabling. (#24435) Thanks .
|
- Cron/One-shot reliability: retry transient one-shot failures with bounded backoff and configurable retry policy before disabling. (#24435) Thanks .
|
||||||
- Gateway/Cron auditability: add gateway info logs for successful cron create, update, and remove operations. (#25090) Thanks .
|
- Gateway/Cron auditability: add gateway info logs for successful cron create, update, and remove operations. (#25090) Thanks .
|
||||||
- Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks .
|
- Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks .
|
||||||
|
|||||||
63
ui/src/ui/storage.node.test.ts
Normal file
63
ui/src/ui/storage.node.test.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
function createStorageMock(): Storage {
|
||||||
|
const store = new Map<string, string>();
|
||||||
|
return {
|
||||||
|
get length() {
|
||||||
|
return store.size;
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
store.clear();
|
||||||
|
},
|
||||||
|
getItem(key: string) {
|
||||||
|
return store.get(key) ?? null;
|
||||||
|
},
|
||||||
|
key(index: number) {
|
||||||
|
return Array.from(store.keys())[index] ?? null;
|
||||||
|
},
|
||||||
|
removeItem(key: string) {
|
||||||
|
store.delete(key);
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
store.set(key, String(value));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("loadSettings default gateway URL derivation", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetModules();
|
||||||
|
vi.stubGlobal("localStorage", createStorageMock());
|
||||||
|
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses configured base path and normalizes trailing slash", async () => {
|
||||||
|
vi.stubGlobal("location", {
|
||||||
|
protocol: "https:",
|
||||||
|
host: "gateway.example:8443",
|
||||||
|
pathname: "/ignored/path",
|
||||||
|
} as Location);
|
||||||
|
vi.stubGlobal("window", { __OPENCLAW_CONTROL_UI_BASE_PATH__: " /openclaw/ " } as Window &
|
||||||
|
typeof globalThis);
|
||||||
|
|
||||||
|
const { loadSettings } = await import("./storage.ts");
|
||||||
|
expect(loadSettings().gatewayUrl).toBe("wss://gateway.example:8443/openclaw");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("infers base path from nested pathname when configured base path is not set", async () => {
|
||||||
|
vi.stubGlobal("location", {
|
||||||
|
protocol: "http:",
|
||||||
|
host: "gateway.example:18789",
|
||||||
|
pathname: "/apps/openclaw/chat",
|
||||||
|
} as Location);
|
||||||
|
vi.stubGlobal("window", {} as Window & typeof globalThis);
|
||||||
|
|
||||||
|
const { loadSettings } = await import("./storage.ts");
|
||||||
|
expect(loadSettings().gatewayUrl).toBe("ws://gateway.example:18789/apps/openclaw");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const KEY = "openclaw.control.settings.v1";
|
const KEY = "openclaw.control.settings.v1";
|
||||||
|
|
||||||
import { isSupportedLocale } from "../i18n/index.ts";
|
import { isSupportedLocale } from "../i18n/index.ts";
|
||||||
|
import { inferBasePathFromPathname, normalizeBasePath } from "./navigation.ts";
|
||||||
import type { ThemeMode } from "./theme.ts";
|
import type { ThemeMode } from "./theme.ts";
|
||||||
|
|
||||||
export type UiSettings = {
|
export type UiSettings = {
|
||||||
@@ -20,7 +21,14 @@ export type UiSettings = {
|
|||||||
export function loadSettings(): UiSettings {
|
export function loadSettings(): UiSettings {
|
||||||
const defaultUrl = (() => {
|
const defaultUrl = (() => {
|
||||||
const proto = location.protocol === "https:" ? "wss" : "ws";
|
const proto = location.protocol === "https:" ? "wss" : "ws";
|
||||||
return `${proto}://${location.host}`;
|
const configured =
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
typeof window.__OPENCLAW_CONTROL_UI_BASE_PATH__ === "string" &&
|
||||||
|
window.__OPENCLAW_CONTROL_UI_BASE_PATH__.trim();
|
||||||
|
const basePath = configured
|
||||||
|
? normalizeBasePath(configured)
|
||||||
|
: inferBasePathFromPathname(location.pathname);
|
||||||
|
return `${proto}://${location.host}${basePath}`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const defaults: UiSettings = {
|
const defaults: UiSettings = {
|
||||||
|
|||||||
Reference in New Issue
Block a user