fix(macos): resolve dashboard basePath for local and remote (#15862)

Co-authored-by: 帅小呆1号 <shuaixiaodai1@openclaw.ai>
This commit is contained in:
Shuai-DaiDai
2026-02-14 09:19:36 +08:00
committed by GitHub
parent f86840f4df
commit bdc63b5b7d
3 changed files with 77 additions and 3 deletions

View File

@@ -619,7 +619,29 @@ actor GatewayEndpointStore {
}
extension GatewayEndpointStore {
static func dashboardURL(for config: GatewayConnection.Config) throws -> URL {
private static func normalizeDashboardPath(_ rawPath: String?) -> String {
let trimmed = (rawPath ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return "/" }
let withLeadingSlash = trimmed.hasPrefix("/") ? trimmed : "/" + trimmed
guard withLeadingSlash != "/" else { return "/" }
return withLeadingSlash.hasSuffix("/") ? withLeadingSlash : withLeadingSlash + "/"
}
private static func localControlUiBasePath() -> String {
let root = OpenClawConfigFile.loadDict()
guard let gateway = root["gateway"] as? [String: Any],
let controlUi = gateway["controlUi"] as? [String: Any]
else {
return "/"
}
return self.normalizeDashboardPath(controlUi["basePath"] as? String)
}
static func dashboardURL(
for config: GatewayConnection.Config,
mode: AppState.ConnectionMode,
localBasePath: String? = nil) throws -> URL
{
guard var components = URLComponents(url: config.url, resolvingAgainstBaseURL: false) else {
throw NSError(domain: "Dashboard", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Invalid gateway URL",
@@ -633,7 +655,17 @@ extension GatewayEndpointStore {
default:
components.scheme = "http"
}
components.path = "/"
let urlPath = self.normalizeDashboardPath(components.path)
if urlPath != "/" {
components.path = urlPath
} else if mode == .local {
let fallbackPath = localBasePath ?? self.localControlUiBasePath()
components.path = self.normalizeDashboardPath(fallbackPath)
} else {
components.path = "/"
}
var queryItems: [URLQueryItem] = []
if let token = config.token?.trimmingCharacters(in: .whitespacesAndNewlines),
!token.isEmpty

View File

@@ -337,7 +337,7 @@ struct MenuContent: View {
private func openDashboard() async {
do {
let config = try await GatewayEndpointStore.shared.requireConfig()
let url = try GatewayEndpointStore.dashboardURL(for: config)
let url = try GatewayEndpointStore.dashboardURL(for: config, mode: self.state.connectionMode)
NSWorkspace.shared.open(url)
} catch {
let alert = NSAlert()

View File

@@ -176,6 +176,48 @@ import Testing
#expect(host == "192.168.1.10")
}
@Test func dashboardURLUsesLocalBasePathInLocalMode() throws {
let config: GatewayConnection.Config = (
url: try #require(URL(string: "ws://127.0.0.1:18789")),
token: nil,
password: nil
)
let url = try GatewayEndpointStore.dashboardURL(
for: config,
mode: .local,
localBasePath: " control ")
#expect(url.absoluteString == "http://127.0.0.1:18789/control/")
}
@Test func dashboardURLSkipsLocalBasePathInRemoteMode() throws {
let config: GatewayConnection.Config = (
url: try #require(URL(string: "ws://gateway.example:18789")),
token: nil,
password: nil
)
let url = try GatewayEndpointStore.dashboardURL(
for: config,
mode: .remote,
localBasePath: "/local-ui")
#expect(url.absoluteString == "http://gateway.example:18789/")
}
@Test func dashboardURLPrefersPathFromConfigURL() throws {
let config: GatewayConnection.Config = (
url: try #require(URL(string: "wss://gateway.example:443/remote-ui")),
token: nil,
password: nil
)
let url = try GatewayEndpointStore.dashboardURL(
for: config,
mode: .remote,
localBasePath: "/local-ui")
#expect(url.absoluteString == "https://gateway.example:443/remote-ui/")
}
@Test func normalizeGatewayUrlAddsDefaultPortForWs() {
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://gateway")
#expect(url?.port == 18789)