Files
openclaw/apps/ios/Sources/Chat/SessionDashboardScreen.swift
Peter Steinberger 262deec72c feat(mobile): session Dashboard on iOS and Android via authenticated Control UI webview (#112163)
* feat(mobile): session dashboard screens on iOS and Android via authenticated Control UI webview

* fix(android): keep configured Control UI base path in session dashboard URL

* docs(android): note system-trust boundary of the shared Control UI webview

* fix(android): origin-only document-start rule for Control UI auth script

* chore(i18n): refresh native inventory on rebased head

* fix(ios): swiftlint closure form in session dashboard toolbar

* fix(i18n): tolerate workflow-owned pending native rows in PR alignment checks

* fix(android): KTX toUri per lint and refresh native inventory

* fix(android): ktlint import order incl. main-inherited fleet test, refresh inventory
2026-07-21 01:13:23 -07:00

69 lines
2.6 KiB
Swift

import OpenClawKit
import SwiftUI
/// Session-scoped dashboard rendered by the gateway Control UI.
struct SessionDashboardScreen: View {
@Environment(NodeAppModel.self) private var appModel
@Environment(\.dismiss) private var dismiss
let sessionKey: String
var body: some View {
let config = self.appModel.activeGatewayConnectConfig
let storedOperatorToken = AuthenticatedControlUI.storedOperatorToken(config: config)
ZStack {
OpenClawProBackground()
if let url = Self.dashboardURL(config: config, sessionKey: self.sessionKey) {
AuthenticatedControlUIWebView(
url: url,
authScript: AuthenticatedControlUI.authUserScript(
config: config,
pageURL: url,
storedOperatorToken: storedOperatorToken))
.id(AuthenticatedControlUI.webContentIdentity(
config: config,
storedOperatorToken: storedOperatorToken))
.ignoresSafeArea(.container, edges: .bottom)
} else {
self.unavailableCard
}
}
.navigationTitle("Dashboard")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
self.dismiss()
} label: {
Text("Done")
.font(OpenClawType.subheadSemiBold)
}
}
}
}
private var unavailableCard: some View {
VStack(spacing: 12) {
ProIconBadge(systemName: "rectangle.grid.2x2", color: OpenClawBrand.accent)
Text("Dashboard needs a connected gateway")
.font(OpenClawType.subheadSemiBold)
Text("Connect to your gateway to open this session dashboard.")
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.padding(24)
}
/// Converts the active gateway WebSocket endpoint into the one-shot
/// authenticated Control UI route. Credentials stay in the startup script.
static func dashboardURL(config: GatewayConnectConfig?, sessionKey: String) -> URL? {
AuthenticatedControlUI.pageURL(
config: config,
path: "/chat",
queryItems: [
URLQueryItem(name: "session", value: sessionKey),
URLQueryItem(name: "face", value: "dashboard"),
])
}
}