mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(ui): preserve control-ui auth across refresh (#40892)
Merged via squash.
Prepared head SHA: f9b2375892
Co-authored-by: velvet-shark <126378+velvet-shark@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:
committed by
GitHub
parent
f6d0712f50
commit
f2f561fab1
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { connectGatewayMock, loadBootstrapMock } = vi.hoisted(() => ({
|
||||
const { applySettingsFromUrlMock, connectGatewayMock, loadBootstrapMock } = vi.hoisted(() => ({
|
||||
applySettingsFromUrlMock: vi.fn(),
|
||||
connectGatewayMock: vi.fn(),
|
||||
loadBootstrapMock: vi.fn(),
|
||||
}));
|
||||
@@ -14,7 +15,7 @@ vi.mock("./controllers/control-ui-bootstrap.ts", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./app-settings.ts", () => ({
|
||||
applySettingsFromUrl: vi.fn(),
|
||||
applySettingsFromUrl: applySettingsFromUrlMock,
|
||||
attachThemeListener: vi.fn(),
|
||||
detachThemeListener: vi.fn(),
|
||||
inferBasePath: vi.fn(() => "/"),
|
||||
@@ -65,6 +66,12 @@ function createHost() {
|
||||
}
|
||||
|
||||
describe("handleConnected", () => {
|
||||
beforeEach(() => {
|
||||
applySettingsFromUrlMock.mockReset();
|
||||
connectGatewayMock.mockReset();
|
||||
loadBootstrapMock.mockReset();
|
||||
});
|
||||
|
||||
it("waits for bootstrap load before first gateway connect", async () => {
|
||||
let resolveBootstrap!: () => void;
|
||||
loadBootstrapMock.mockReturnValueOnce(
|
||||
@@ -102,4 +109,17 @@ describe("handleConnected", () => {
|
||||
|
||||
expect(connectGatewayMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("scrubs URL settings before starting the bootstrap fetch", () => {
|
||||
loadBootstrapMock.mockResolvedValueOnce(undefined);
|
||||
const host = createHost();
|
||||
|
||||
handleConnected(host as never);
|
||||
|
||||
expect(applySettingsFromUrlMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadBootstrapMock).toHaveBeenCalledTimes(1);
|
||||
expect(applySettingsFromUrlMock.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
loadBootstrapMock.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,8 +45,8 @@ type LifecycleHost = {
|
||||
export function handleConnected(host: LifecycleHost) {
|
||||
const connectGeneration = ++host.connectGeneration;
|
||||
host.basePath = inferBasePath();
|
||||
const bootstrapReady = loadControlUiBootstrapConfig(host);
|
||||
applySettingsFromUrl(host as unknown as Parameters<typeof applySettingsFromUrl>[0]);
|
||||
const bootstrapReady = loadControlUiBootstrapConfig(host);
|
||||
syncTabWithLocation(host as unknown as Parameters<typeof syncTabWithLocation>[0], true);
|
||||
syncThemeWithSettings(host as unknown as Parameters<typeof syncThemeWithSettings>[0]);
|
||||
attachThemeListener(host as unknown as Parameters<typeof attachThemeListener>[0]);
|
||||
|
||||
@@ -59,6 +59,7 @@ type SettingsHost = {
|
||||
themeMedia: MediaQueryList | null;
|
||||
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
||||
pendingGatewayUrl?: string | null;
|
||||
pendingGatewayToken?: string | null;
|
||||
};
|
||||
|
||||
export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||
@@ -94,18 +95,26 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
||||
const params = new URLSearchParams(url.search);
|
||||
const hashParams = new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);
|
||||
|
||||
const tokenRaw = params.get("token") ?? hashParams.get("token");
|
||||
const gatewayUrlRaw = params.get("gatewayUrl") ?? hashParams.get("gatewayUrl");
|
||||
const nextGatewayUrl = gatewayUrlRaw?.trim() ?? "";
|
||||
const gatewayUrlChanged = Boolean(nextGatewayUrl && nextGatewayUrl !== host.settings.gatewayUrl);
|
||||
const tokenRaw = hashParams.get("token");
|
||||
const passwordRaw = params.get("password") ?? hashParams.get("password");
|
||||
const sessionRaw = params.get("session") ?? hashParams.get("session");
|
||||
const gatewayUrlRaw = params.get("gatewayUrl") ?? hashParams.get("gatewayUrl");
|
||||
let shouldCleanUrl = false;
|
||||
|
||||
if (params.has("token")) {
|
||||
params.delete("token");
|
||||
shouldCleanUrl = true;
|
||||
}
|
||||
|
||||
if (tokenRaw != null) {
|
||||
const token = tokenRaw.trim();
|
||||
if (token && token !== host.settings.token) {
|
||||
if (token && gatewayUrlChanged) {
|
||||
host.pendingGatewayToken = token;
|
||||
} else if (token && token !== host.settings.token) {
|
||||
applySettings(host, { ...host.settings, token });
|
||||
}
|
||||
params.delete("token");
|
||||
hashParams.delete("token");
|
||||
shouldCleanUrl = true;
|
||||
}
|
||||
@@ -130,9 +139,14 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
||||
}
|
||||
|
||||
if (gatewayUrlRaw != null) {
|
||||
const gatewayUrl = gatewayUrlRaw.trim();
|
||||
if (gatewayUrl && gatewayUrl !== host.settings.gatewayUrl) {
|
||||
host.pendingGatewayUrl = gatewayUrl;
|
||||
if (gatewayUrlChanged) {
|
||||
host.pendingGatewayUrl = nextGatewayUrl;
|
||||
if (!tokenRaw?.trim()) {
|
||||
host.pendingGatewayToken = null;
|
||||
}
|
||||
} else {
|
||||
host.pendingGatewayUrl = null;
|
||||
host.pendingGatewayToken = null;
|
||||
}
|
||||
params.delete("gatewayUrl");
|
||||
hashParams.delete("gatewayUrl");
|
||||
|
||||
@@ -178,6 +178,7 @@ export class OpenClawApp extends LitElement {
|
||||
@state() execApprovalBusy = false;
|
||||
@state() execApprovalError: string | null = null;
|
||||
@state() pendingGatewayUrl: string | null = null;
|
||||
pendingGatewayToken: string | null = null;
|
||||
|
||||
@state() configLoading = false;
|
||||
@state() configRaw = "{\n}\n";
|
||||
@@ -573,16 +574,20 @@ export class OpenClawApp extends LitElement {
|
||||
if (!nextGatewayUrl) {
|
||||
return;
|
||||
}
|
||||
const nextToken = this.pendingGatewayToken?.trim() || "";
|
||||
this.pendingGatewayUrl = null;
|
||||
this.pendingGatewayToken = null;
|
||||
applySettingsInternal(this as unknown as Parameters<typeof applySettingsInternal>[0], {
|
||||
...this.settings,
|
||||
gatewayUrl: nextGatewayUrl,
|
||||
token: nextToken,
|
||||
});
|
||||
this.connect();
|
||||
}
|
||||
|
||||
handleGatewayUrlCancel() {
|
||||
this.pendingGatewayUrl = null;
|
||||
this.pendingGatewayToken = null;
|
||||
}
|
||||
|
||||
// Sidebar handlers for tool output viewing
|
||||
|
||||
@@ -146,11 +146,11 @@ describe("control UI routing", () => {
|
||||
expect(container.scrollTop).toBe(maxScroll);
|
||||
});
|
||||
|
||||
it("hydrates token from URL params and strips it", async () => {
|
||||
it("strips query token params without importing them", async () => {
|
||||
const app = mountApp("/ui/overview?token=abc123");
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.settings.token).toBe("abc123");
|
||||
expect(app.settings.token).toBe("");
|
||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
||||
undefined,
|
||||
);
|
||||
@@ -167,12 +167,12 @@ describe("control UI routing", () => {
|
||||
expect(window.location.search).toBe("");
|
||||
});
|
||||
|
||||
it("hydrates token from URL params even when settings already set", async () => {
|
||||
it("hydrates token from URL hash when settings already set", async () => {
|
||||
localStorage.setItem(
|
||||
"openclaw.control.settings.v1",
|
||||
JSON.stringify({ token: "existing-token", gatewayUrl: "wss://gateway.example/openclaw" }),
|
||||
);
|
||||
const app = mountApp("/ui/overview?token=abc123");
|
||||
const app = mountApp("/ui/overview#token=abc123");
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.settings.token).toBe("abc123");
|
||||
@@ -183,7 +183,7 @@ describe("control UI routing", () => {
|
||||
undefined,
|
||||
);
|
||||
expect(window.location.pathname).toBe("/ui/overview");
|
||||
expect(window.location.search).toBe("");
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
it("hydrates token from URL hash and strips it", async () => {
|
||||
@@ -197,4 +197,56 @@ describe("control UI routing", () => {
|
||||
expect(window.location.pathname).toBe("/ui/overview");
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
it("clears the current token when the gateway URL changes", async () => {
|
||||
const app = mountApp("/ui/overview#token=abc123");
|
||||
await app.updateComplete;
|
||||
|
||||
const gatewayUrlInput = app.querySelector<HTMLInputElement>(
|
||||
'input[placeholder="ws://100.x.y.z:18789"]',
|
||||
);
|
||||
expect(gatewayUrlInput).not.toBeNull();
|
||||
gatewayUrlInput!.value = "wss://other-gateway.example/openclaw";
|
||||
gatewayUrlInput!.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.settings.gatewayUrl).toBe("wss://other-gateway.example/openclaw");
|
||||
expect(app.settings.token).toBe("");
|
||||
});
|
||||
|
||||
it("keeps a hash token pending until the gateway URL change is confirmed", async () => {
|
||||
const app = mountApp(
|
||||
"/ui/overview?gatewayUrl=wss://other-gateway.example/openclaw#token=abc123",
|
||||
);
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.settings.gatewayUrl).not.toBe("wss://other-gateway.example/openclaw");
|
||||
expect(app.settings.token).toBe("");
|
||||
|
||||
const confirmButton = Array.from(app.querySelectorAll<HTMLButtonElement>("button")).find(
|
||||
(button) => button.textContent?.trim() === "Confirm",
|
||||
);
|
||||
expect(confirmButton).not.toBeUndefined();
|
||||
confirmButton?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
||||
await app.updateComplete;
|
||||
|
||||
expect(app.settings.gatewayUrl).toBe("wss://other-gateway.example/openclaw");
|
||||
expect(app.settings.token).toBe("abc123");
|
||||
expect(window.location.search).toBe("");
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
it("restores the token after a same-tab refresh", async () => {
|
||||
const first = mountApp("/ui/overview#token=abc123");
|
||||
await first.updateComplete;
|
||||
first.remove();
|
||||
|
||||
const refreshed = mountApp("/ui/overview");
|
||||
await refreshed.updateComplete;
|
||||
|
||||
expect(refreshed.settings.token).toBe("abc123");
|
||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}").token).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,8 +66,10 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.stubGlobal("localStorage", createStorageMock());
|
||||
vi.stubGlobal("sessionStorage", createStorageMock());
|
||||
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
setControlUiBasePath(undefined);
|
||||
});
|
||||
|
||||
@@ -106,6 +108,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
sessionStorage.setItem("openclaw.control.token.v1", "legacy-session-token");
|
||||
localStorage.setItem(
|
||||
"openclaw.control.settings.v1",
|
||||
JSON.stringify({
|
||||
@@ -132,6 +135,76 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
});
|
||||
|
||||
it("loads the current-tab token from sessionStorage", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "session-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
expect(loadSettings()).toMatchObject({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "session-token",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not reuse a session token for a different gatewayUrl", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "gateway-a-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
localStorage.setItem(
|
||||
"openclaw.control.settings.v1",
|
||||
JSON.stringify({
|
||||
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(loadSettings()).toMatchObject({
|
||||
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||
token: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not persist gateway tokens when saving settings", async () => {
|
||||
@@ -141,7 +214,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { saveSettings } = await import("./storage.ts");
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "memory-only-token",
|
||||
@@ -154,6 +227,10 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(loadSettings()).toMatchObject({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "memory-only-token",
|
||||
});
|
||||
|
||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toEqual({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
@@ -166,5 +243,43 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(1);
|
||||
});
|
||||
|
||||
it("clears the current-tab token when saving an empty token", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "stale-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
expect(loadSettings().token).toBe("");
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const KEY = "openclaw.control.settings.v1";
|
||||
const LEGACY_TOKEN_SESSION_KEY = "openclaw.control.token.v1";
|
||||
const TOKEN_SESSION_KEY_PREFIX = "openclaw.control.token.v1:";
|
||||
|
||||
type PersistedUiSettings = Omit<UiSettings, "token"> & { token?: never };
|
||||
|
||||
@@ -20,6 +22,72 @@ export type UiSettings = {
|
||||
locale?: string;
|
||||
};
|
||||
|
||||
function getSessionStorage(): Storage | null {
|
||||
if (typeof window !== "undefined" && window.sessionStorage) {
|
||||
return window.sessionStorage;
|
||||
}
|
||||
if (typeof sessionStorage !== "undefined") {
|
||||
return sessionStorage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeGatewayTokenScope(gatewayUrl: string): string {
|
||||
const trimmed = gatewayUrl.trim();
|
||||
if (!trimmed) {
|
||||
return "default";
|
||||
}
|
||||
try {
|
||||
const base =
|
||||
typeof location !== "undefined"
|
||||
? `${location.protocol}//${location.host}${location.pathname || "/"}`
|
||||
: undefined;
|
||||
const parsed = base ? new URL(trimmed, base) : new URL(trimmed);
|
||||
const pathname =
|
||||
parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
|
||||
return `${parsed.protocol}//${parsed.host}${pathname}`;
|
||||
} catch {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
function tokenSessionKeyForGateway(gatewayUrl: string): string {
|
||||
return `${TOKEN_SESSION_KEY_PREFIX}${normalizeGatewayTokenScope(gatewayUrl)}`;
|
||||
}
|
||||
|
||||
function loadSessionToken(gatewayUrl: string): string {
|
||||
try {
|
||||
const storage = getSessionStorage();
|
||||
if (!storage) {
|
||||
return "";
|
||||
}
|
||||
storage.removeItem(LEGACY_TOKEN_SESSION_KEY);
|
||||
const token = storage.getItem(tokenSessionKeyForGateway(gatewayUrl)) ?? "";
|
||||
return token.trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function persistSessionToken(gatewayUrl: string, token: string) {
|
||||
try {
|
||||
const storage = getSessionStorage();
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
storage.removeItem(LEGACY_TOKEN_SESSION_KEY);
|
||||
const key = tokenSessionKeyForGateway(gatewayUrl);
|
||||
const normalized = token.trim();
|
||||
if (normalized) {
|
||||
storage.setItem(key, normalized);
|
||||
return;
|
||||
}
|
||||
storage.removeItem(key);
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
|
||||
export function loadSettings(): UiSettings {
|
||||
const defaultUrl = (() => {
|
||||
const proto = location.protocol === "https:" ? "wss" : "ws";
|
||||
@@ -35,7 +103,7 @@ export function loadSettings(): UiSettings {
|
||||
|
||||
const defaults: UiSettings = {
|
||||
gatewayUrl: defaultUrl,
|
||||
token: "",
|
||||
token: loadSessionToken(defaultUrl),
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
@@ -58,7 +126,11 @@ export function loadSettings(): UiSettings {
|
||||
? parsed.gatewayUrl.trim()
|
||||
: defaults.gatewayUrl,
|
||||
// Gateway auth is intentionally in-memory only; scrub any legacy persisted token on load.
|
||||
token: defaults.token,
|
||||
token: loadSessionToken(
|
||||
typeof parsed.gatewayUrl === "string" && parsed.gatewayUrl.trim()
|
||||
? parsed.gatewayUrl.trim()
|
||||
: defaults.gatewayUrl,
|
||||
),
|
||||
sessionKey:
|
||||
typeof parsed.sessionKey === "string" && parsed.sessionKey.trim()
|
||||
? parsed.sessionKey.trim()
|
||||
@@ -106,6 +178,7 @@ export function saveSettings(next: UiSettings) {
|
||||
}
|
||||
|
||||
function persistSettings(next: UiSettings) {
|
||||
persistSessionToken(next.gatewayUrl, next.token);
|
||||
const persisted: PersistedUiSettings = {
|
||||
gatewayUrl: next.gatewayUrl,
|
||||
sessionKey: next.sessionKey,
|
||||
|
||||
@@ -16,12 +16,14 @@ export function registerAppMountHooks() {
|
||||
beforeEach(() => {
|
||||
window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined;
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined;
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,7 +205,11 @@ export function renderOverview(props: OverviewProps) {
|
||||
.value=${props.settings.gatewayUrl}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
props.onSettingsChange({ ...props.settings, gatewayUrl: v });
|
||||
props.onSettingsChange({
|
||||
...props.settings,
|
||||
gatewayUrl: v,
|
||||
token: v.trim() === props.settings.gatewayUrl.trim() ? props.settings.token : "",
|
||||
});
|
||||
}}
|
||||
placeholder="ws://100.x.y.z:18789"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user