fix(gateway): use secure dashboard links when TLS is enabled (#71499)

Fixes #71494.

- Render Control UI links with https:// when gateway TLS is enabled.
- Render websocket links with wss:// through the shared link resolver.
- Add daemon status handoff coverage and TLS scheme docs.

Co-authored-by: deepkilord <wang_hgang@msn.com>
This commit is contained in:
deepkilo
2026-04-25 12:45:15 +02:00
committed by GitHub
parent 8cbb62d93c
commit df6c58cf30
18 changed files with 110 additions and 4 deletions

View File

@@ -205,6 +205,7 @@ describe("gatherDaemonStatus", () => {
}),
);
expect(status.gateway?.probeUrl).toBe("wss://127.0.0.1:19001");
expect(status.gateway?.tlsEnabled).toBe(true);
expect(status.rpc?.url).toBe("wss://127.0.0.1:19001");
expect(status.rpc?.ok).toBe(true);
expect(inspectGatewayRestart).not.toHaveBeenCalled();

View File

@@ -43,6 +43,7 @@ type GatewayStatusSummary = {
bindMode: GatewayBindMode;
bindHost: string;
customBindHost?: string;
tlsEnabled?: boolean;
port: number;
portSource: "service args" | "env/config";
probeUrl: string;
@@ -284,7 +285,8 @@ async function resolveGatewayStatusSummary(params: {
});
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4, customBindHost);
const probeUrlOverride = trimToUndefined(params.rpcUrlOverride) ?? null;
const scheme = params.daemonCfg.gateway?.tls?.enabled === true ? "wss" : "ws";
const tlsEnabled = params.daemonCfg.gateway?.tls?.enabled === true;
const scheme = tlsEnabled ? "wss" : "ws";
const probeUrl = probeUrlOverride ?? `${scheme}://${probeHost}:${daemonPort}`;
let probeNote =
!probeUrlOverride && bindMode === "lan"
@@ -300,6 +302,7 @@ async function resolveGatewayStatusSummary(params: {
bindMode,
bindHost,
customBindHost,
...(tlsEnabled ? { tlsEnabled } : {}),
port: daemonPort,
portSource,
probeUrl,

View File

@@ -6,6 +6,9 @@ const runtime = vi.hoisted(() => ({
log: vi.fn<(line: string) => void>(),
error: vi.fn<(line: string) => void>(),
}));
const resolveControlUiLinksMock = vi.hoisted(() =>
vi.fn((_opts?: unknown) => ({ httpUrl: "http://127.0.0.1:18789" })),
);
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
@@ -21,7 +24,7 @@ vi.mock("../../terminal/theme.js", async () => {
});
vi.mock("../../gateway/control-ui-links.js", () => ({
resolveControlUiLinks: () => ({ httpUrl: "http://127.0.0.1:18789" }),
resolveControlUiLinks: resolveControlUiLinksMock,
}));
vi.mock("../../daemon/inspect.js", () => ({
@@ -73,6 +76,7 @@ describe("printDaemonStatus", () => {
beforeEach(() => {
runtime.log.mockReset();
runtime.error.mockReset();
resolveControlUiLinksMock.mockClear();
});
it("prints stale gateway pid guidance when runtime does not own the listener", () => {
@@ -152,4 +156,56 @@ describe("printDaemonStatus", () => {
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("Connectivity probe: ok"));
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("Capability: write-capable"));
});
it("passes daemon TLS state to dashboard link rendering", () => {
printDaemonStatus(
{
service: {
label: "LaunchAgent",
loaded: true,
loadedText: "loaded",
notLoadedText: "not loaded",
runtime: { status: "running", pid: 8000 },
},
config: {
cli: {
path: "/tmp/openclaw-cli/openclaw.json",
exists: true,
valid: true,
},
daemon: {
path: "/tmp/openclaw-daemon/openclaw.json",
exists: true,
valid: true,
controlUi: { basePath: "/ui" },
},
mismatch: true,
},
gateway: {
bindMode: "lan",
bindHost: "0.0.0.0",
port: 19001,
portSource: "service args",
probeUrl: "wss://127.0.0.1:19001",
tlsEnabled: true,
},
rpc: {
ok: true,
kind: "connect",
capability: "write_capable",
url: "wss://127.0.0.1:19001",
},
extraServices: [],
},
{ json: false },
);
expect(resolveControlUiLinksMock).toHaveBeenCalledWith({
port: 19001,
bind: "lan",
customBindHost: undefined,
basePath: "/ui",
tlsEnabled: true,
});
});
});

View File

@@ -165,6 +165,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
bind: status.gateway.bindMode,
customBindHost: status.gateway.customBindHost,
basePath: status.config?.daemon?.controlUi?.basePath,
tlsEnabled: status.gateway.tlsEnabled === true,
});
defaultRuntime.log(`${label("Dashboard:")} ${infoText(links.httpUrl)}`);
}