mirror of https://github.com/openclaw/openclaw.git
118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
|
|
import type { OpenClawConfig } from "../config/types.js";
|
|
import { readGatewayTokenEnv } from "../gateway/credentials.js";
|
|
import { resolveConfiguredSecretInputWithFallback } from "../gateway/resolve-configured-secret-input-string.js";
|
|
import { copyToClipboard } from "../infra/clipboard.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import { defaultRuntime } from "../runtime.js";
|
|
import {
|
|
detectBrowserOpenSupport,
|
|
formatControlUiSshHint,
|
|
openUrl,
|
|
resolveControlUiLinks,
|
|
} from "./onboard-helpers.js";
|
|
|
|
type DashboardOptions = {
|
|
noOpen?: boolean;
|
|
};
|
|
|
|
async function resolveDashboardToken(
|
|
cfg: OpenClawConfig,
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): Promise<{
|
|
token?: string;
|
|
source?: "config" | "env" | "secretRef";
|
|
unresolvedRefReason?: string;
|
|
tokenSecretRefConfigured: boolean;
|
|
}> {
|
|
const resolved = await resolveConfiguredSecretInputWithFallback({
|
|
config: cfg,
|
|
env,
|
|
value: cfg.gateway?.auth?.token,
|
|
path: "gateway.auth.token",
|
|
readFallback: () => readGatewayTokenEnv(env),
|
|
});
|
|
return {
|
|
token: resolved.value,
|
|
source:
|
|
resolved.source === "config"
|
|
? "config"
|
|
: resolved.source === "secretRef"
|
|
? "secretRef"
|
|
: resolved.source === "fallback"
|
|
? "env"
|
|
: undefined,
|
|
unresolvedRefReason: resolved.unresolvedRefReason,
|
|
tokenSecretRefConfigured: resolved.secretRefConfigured,
|
|
};
|
|
}
|
|
|
|
export async function dashboardCommand(
|
|
runtime: RuntimeEnv = defaultRuntime,
|
|
options: DashboardOptions = {},
|
|
) {
|
|
const snapshot = await readConfigFileSnapshot();
|
|
const cfg = snapshot.valid ? snapshot.config : {};
|
|
const port = resolveGatewayPort(cfg);
|
|
const bind = cfg.gateway?.bind ?? "loopback";
|
|
const basePath = cfg.gateway?.controlUi?.basePath;
|
|
const customBindHost = cfg.gateway?.customBindHost;
|
|
const resolvedToken = await resolveDashboardToken(cfg, process.env);
|
|
const token = resolvedToken.token ?? "";
|
|
|
|
// LAN URLs fail secure-context checks in browsers.
|
|
// Coerce only lan->loopback and preserve other bind modes.
|
|
const links = resolveControlUiLinks({
|
|
port,
|
|
bind: bind === "lan" ? "loopback" : bind,
|
|
customBindHost,
|
|
basePath,
|
|
});
|
|
// Avoid embedding externally managed SecretRef tokens in terminal/clipboard/browser args.
|
|
const includeTokenInUrl = token.length > 0 && !resolvedToken.tokenSecretRefConfigured;
|
|
// Prefer URL fragment to avoid leaking auth tokens via query params.
|
|
const dashboardUrl = includeTokenInUrl
|
|
? `${links.httpUrl}#token=${encodeURIComponent(token)}`
|
|
: links.httpUrl;
|
|
|
|
runtime.log(`Dashboard URL: ${dashboardUrl}`);
|
|
if (resolvedToken.tokenSecretRefConfigured && token) {
|
|
runtime.log(
|
|
"Token auto-auth is disabled for SecretRef-managed gateway.auth.token; use your external token source if prompted.",
|
|
);
|
|
}
|
|
if (resolvedToken.unresolvedRefReason) {
|
|
runtime.log(`Token auto-auth unavailable: ${resolvedToken.unresolvedRefReason}`);
|
|
runtime.log(
|
|
"Set OPENCLAW_GATEWAY_TOKEN in this shell or resolve your secret provider, then rerun `openclaw dashboard`.",
|
|
);
|
|
}
|
|
|
|
const copied = await copyToClipboard(dashboardUrl).catch(() => false);
|
|
runtime.log(copied ? "Copied to clipboard." : "Copy to clipboard unavailable.");
|
|
|
|
let opened = false;
|
|
let hint: string | undefined;
|
|
if (!options.noOpen) {
|
|
const browserSupport = await detectBrowserOpenSupport();
|
|
if (browserSupport.ok) {
|
|
opened = await openUrl(dashboardUrl);
|
|
}
|
|
if (!opened) {
|
|
hint = formatControlUiSshHint({
|
|
port,
|
|
basePath,
|
|
token: includeTokenInUrl ? token || undefined : undefined,
|
|
});
|
|
}
|
|
} else {
|
|
hint = "Browser launch disabled (--no-open). Use the URL above.";
|
|
}
|
|
|
|
if (opened) {
|
|
runtime.log("Opened in your browser. Keep that tab to control OpenClaw.");
|
|
} else if (hint) {
|
|
runtime.log(hint);
|
|
}
|
|
}
|