config on overview

This commit is contained in:
赵一寰 2026-03-13 18:38:05 +08:00
parent 75e68f0638
commit 735886ba13
4 changed files with 74 additions and 21 deletions

View File

@ -1472,8 +1472,8 @@ export function renderApp(state: AppViewState) {
<button class="claw-computer-close" @click=${() => state.toggleClawComputer()}>×</button>
</div>
<claw-computer-panel
.vncUrl=${"127.0.0.1:5901"}
.vncToken=${"123456"}
.vncUrl=${state.settings.vncWsUrl}
.password=${state.settings.vncPassword}
></claw-computer-panel>
</div>
`

View File

@ -2,8 +2,9 @@
import RFB from "@novnc/novnc";
// ui/src/ui/components/claw-computer-panel.ts
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { customElement, state, property } from "lit/decorators.js";
import { createRef, ref, Ref } from "lit/directives/ref.js";
// @ts-ignore - noVNC types are not available
// Compatible with both .default and non-.default versions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -21,13 +22,16 @@ interface RFBInstance {
@customElement("claw-computer-panel")
export class ClawComputerPanel extends LitElement {
@property() vncUrl = "ws://localhost:8081";
@property() password = "";
@state() status = "等待連接...";
@state() isConnected = false;
@state() isFitted = true;
@state() password = "";
private rfb: RFBInstance | null = null;
private screenRef: Ref<HTMLDivElement> = createRef<HTMLDivElement>();
private autoConnectAttempted = false;
static styles = css`
:host {
@ -120,7 +124,7 @@ export class ClawComputerPanel extends LitElement {
<div class="container">
<h2>noVNC - 調</h2>
<p style="text-align:center; color:#888;">
ws://localhost:8081已轉發到你的 10.75.171.0:25900
${this.vncUrl} 10.75.171.0:25900
</p>
<div class="controls">
<label>VNC :</label>
@ -152,7 +156,7 @@ export class ClawComputerPanel extends LitElement {
}
private connect = async () => {
const url = "ws://localhost:8081";
const url = this.vncUrl || "ws://localhost:8081";
if (this.rfb) {
this.rfb.disconnect();
}
@ -243,6 +247,14 @@ export class ClawComputerPanel extends LitElement {
firstUpdated() {
window.addEventListener("resize", this.handleResize);
// Auto-connect if URL is configured
if (this.vncUrl) {
// Use setTimeout to ensure DOM is fully ready and to allow UI to render first
setTimeout(() => {
void this.connect();
}, 100);
}
}
disconnectedCallback() {

View File

@ -22,6 +22,9 @@ export type UiSettings = {
navWidth: number; // Sidebar width when expanded (240400px)
navGroupsCollapsed: Record<string, boolean>; // Which nav groups are collapsed
locale?: string;
vncWsUrl?: string;
vncPassword?: string;
vncTarget?: string;
};
function isViteDevPage(): boolean {
@ -135,6 +138,8 @@ export function loadSettings(): UiSettings {
navCollapsed: false,
navWidth: 220,
navGroupsCollapsed: {},
vncWsUrl: "ws://localhost:8081",
vncTarget: "10.75.171.25900",
};
try {
@ -190,6 +195,9 @@ export function loadSettings(): UiSettings {
? parsed.navGroupsCollapsed
: defaults.navGroupsCollapsed,
locale: isSupportedLocale(parsed.locale) ? parsed.locale : undefined,
vncWsUrl: typeof parsed.vncWsUrl === "string" ? parsed.vncWsUrl : defaults.vncWsUrl,
vncPassword: typeof parsed.vncPassword === "string" ? parsed.vncPassword : undefined,
vncTarget: typeof parsed.vncTarget === "string" ? parsed.vncTarget : defaults.vncTarget,
};
if ("token" in parsed) {
persistSettings(settings);
@ -219,6 +227,9 @@ function persistSettings(next: UiSettings) {
navWidth: next.navWidth,
navGroupsCollapsed: next.navGroupsCollapsed,
...(next.locale ? { locale: next.locale } : {}),
vncWsUrl: next.vncWsUrl,
vncPassword: next.vncPassword,
vncTarget: next.vncTarget,
};
localStorage.setItem(KEY, JSON.stringify(persisted));
}

View File

@ -299,24 +299,13 @@ export function renderOverview(props: OverviewProps) {
</select>
</label>
</div>
<div class="row" style="margin-top: 14px;">
<button class="btn" @click=${() => props.onConnect()}>${t("common.connect")}</button>
<button class="btn" @click=${() => props.onRefresh()}>${t("common.refresh")}</button>
<span class="muted">${
isTrustedProxy ? t("overview.access.trustedProxy") : t("overview.access.connectHint")
}</span>
</div>
${
!props.connected
? html`
<div class="login-gate__help" style="margin-top: 16px;">
<div class="login-gate__help-title">${t("overview.connection.title")}</div>
<ol class="login-gate__steps">
<li>${t("overview.connection.step1")}<code>openclaw gateway run</code></li>
<li>${t("overview.connection.step2")}<code>openclaw dashboard --no-open</code></li>
<li>${t("overview.connection.step3")}</li>
<li>${t("overview.connection.step4")}<code>openclaw doctor --generate-gateway-token</code></li>
</ol>
<div class="login-gate" style="margin-top: 24px;">
<button class="btn primary large" @click=${props.onConnect}>
${t("overview.connection.connect")}
</button>
<div class="login-gate__docs">
${t("overview.connection.docsHint")}
<a
@ -332,6 +321,47 @@ export function renderOverview(props: OverviewProps) {
}
</div>
<div class="card">
<div class="card-title">Remote Desktop</div>
<div class="card-sub">Configure VNC connection details</div>
<div class="ov-access-grid" style="margin-top: 16px;">
<label class="field ov-access-grid__full">
<span>WebSocket URL</span>
<input
.value=${props.settings.vncWsUrl ?? ""}
@input=${(e: Event) => {
const v = (e.target as HTMLInputElement).value;
props.onSettingsChange({ ...props.settings, vncWsUrl: v });
}}
placeholder="ws://localhost:8081"
/>
</label>
<label class="field ov-access-grid__full">
<span>Target (Host:Port)</span>
<input
.value=${props.settings.vncTarget ?? ""}
@input=${(e: Event) => {
const v = (e.target as HTMLInputElement).value;
props.onSettingsChange({ ...props.settings, vncTarget: v });
}}
placeholder="10.75.171.25900"
/>
</label>
<label class="field">
<span>Password</span>
<input
type="password"
.value=${props.settings.vncPassword ?? ""}
@input=${(e: Event) => {
const v = (e.target as HTMLInputElement).value;
props.onSettingsChange({ ...props.settings, vncPassword: v });
}}
placeholder="VNC Password"
/>
</label>
</div>
</div>
<div class="card">
<div class="card-title">${t("overview.snapshot.title")}</div>
<div class="card-sub">${t("overview.snapshot.subtitle")}</div>