diff --git a/.idea/misc.xml b/.idea/misc.xml
index b4a9ecaf473601..49dcfc6ccde83d 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,6 +4,8 @@
+
+
diff --git a/components/ide/jetbrains/toolbox/build.gradle.kts b/components/ide/jetbrains/toolbox/build.gradle.kts
index 5fed0431406d43..5d0329d66c87b0 100644
--- a/components/ide/jetbrains/toolbox/build.gradle.kts
+++ b/components/ide/jetbrains/toolbox/build.gradle.kts
@@ -1,3 +1,7 @@
+// Copyright (c) 2024 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License.AGPL.txt in the project root for license information.
+
import com.github.jk1.license.filter.ExcludeTransitiveDependenciesFilter
import com.github.jk1.license.render.JsonReportRenderer
import org.jetbrains.intellij.pluginRepository.PluginRepositoryFactory
@@ -34,7 +38,7 @@ dependencies {
implementation("com.connectrpc:connect-kotlin:0.6.0")
// Java specific dependencies.
implementation("com.connectrpc:connect-kotlin-google-java-ext:0.6.0")
- implementation("com.google.protobuf:protobuf-java:4.26.0")
+ implementation("com.google.protobuf:protobuf-java:4.27.2")
// WebSocket
compileOnly("javax.websocket:javax.websocket-api:1.1")
compileOnly("org.eclipse.jetty.websocket:websocket-api:9.4.54.v20240208")
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodAuthManager.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodAuthManager.kt
index d7148bd98a7f52..a9af1f1f815979 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodAuthManager.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodAuthManager.kt
@@ -60,19 +60,44 @@ class GitpodAuthManager {
manager.addEventListener {
when (it.type) {
AuthEvent.Type.LOGIN -> {
- logger.info("account ${it.accountId} logged in")
+ logger.debug("account ${it.accountId} logged in")
+ resetCurrentAccount(it.accountId)
loginListeners.forEach { it() }
}
+
AuthEvent.Type.LOGOUT -> {
- logger.info("account ${it.accountId} logged out")
+ logger.debug("account ${it.accountId} logged out")
+ resetCurrentAccount(it.accountId)
logoutListeners.forEach { it() }
}
}
}
}
+ private fun resetCurrentAccount(accountId: String) {
+ val account = manager.accountsWithStatus.find { it.account.id == accountId }?.account ?: return
+ logger.debug("reset settings for ${account.getHost()}")
+ Utils.gitpodSettings.resetSettings(account.getHost())
+ }
+
fun getCurrentAccount(): GitpodAccount? {
- return manager.accountsWithStatus.firstOrNull()?.account
+ return manager.accountsWithStatus.find { it.account.getHost() === Utils.gitpodSettings.gitpodHost }?.account
+ }
+
+ fun loginWithHost(host: String): Boolean {
+ if (getCurrentAccount()?.getHost() == host) {
+ // TODO: validate token is still available
+ return true
+ }
+ val account = manager.accountsWithStatus.find { it.account.getHost() == host }?.account
+ if (account != null) {
+ Utils.gitpodSettings.gitpodHost = host
+ loginListeners.forEach { it() }
+ // TODO: validate token is still available
+ return true
+ }
+ Utils.openUrl(this.getOAuthLoginUrl(host))
+ return false
}
fun logout() {
@@ -135,48 +160,12 @@ class GitpodAccount(
private val name: String,
private val host: String
) : Account {
- private val orgSelectedListeners: MutableList<(String) -> Unit> = mutableListOf()
- private val logger = LoggerFactory.getLogger(javaClass)
override fun getId() = id
override fun getFullName() = name
fun getCredentials() = credentials
fun getHost() = host
- private fun getStoreKey(key: String) = "USER:${id}:${key}"
-
- var organizationId: String?
- get() = Utils.settingStore[getStoreKey("ORG")]
- set(value){
- if (value == null) {
- return
- }
- Utils.settingStore[getStoreKey("ORG")] = value
- orgSelectedListeners.forEach { it(value) }
- }
-
- var preferEditor: String?
- get() = Utils.settingStore[getStoreKey("EDITOR")]
- set(value){
- if (value == null) {
- return
- }
- Utils.settingStore[getStoreKey("EDITOR")] = value
- }
-
- var preferWorkspaceClass: String?
- get() = Utils.settingStore[getStoreKey("WS_CLS")]
- set(value){
- if (value == null) {
- return
- }
- Utils.settingStore[getStoreKey("WS_CLS")] = value
- }
-
- fun onOrgSelected(listener: (String) -> Unit) {
- orgSelectedListeners.add(listener)
- }
-
fun encode(): String {
return Json.encodeToString(this)
}
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodOrganizationPage.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodOrganizationPage.kt
index 09f0cd8e6bc92e..5a9efbf80de4a0 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodOrganizationPage.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodOrganizationPage.kt
@@ -29,11 +29,11 @@ class GitpodOrganizationPage(val authManager: GitpodAuthManager, val publicApi:
val options = mutableListOf()
options.addAll(organizations.map { org ->
MenuItem(org.name, null, null) {
- authManager.getCurrentAccount()?.organizationId = org.id
+ Utils.gitpodSettings.organizationId = org.id
Utils.toolboxUi.hideUiPage(this)
}
})
- val orgName = organizations.find { it.id == authManager.getCurrentAccount()?.organizationId }?.name ?: ""
+ val orgName = organizations.find { it.id == Utils.gitpodSettings.organizationId }?.name ?: ""
AutocompleteTextField("Organization", orgName, options, 1.0f) {
if (it.isNullOrEmpty()) {
ValidationResult.Invalid("Organization is required")
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProvider.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProvider.kt
index f26f4d514a5bd8..3e9af2060151b6 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProvider.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProvider.kt
@@ -16,12 +16,13 @@ import io.gitpod.toolbox.auth.GitpodLoginPage
import io.gitpod.toolbox.auth.GitpodOrganizationPage
import io.gitpod.toolbox.components.GitpodIcon
import io.gitpod.toolbox.components.SimpleButton
+import io.gitpod.toolbox.service.ConnectParams
import io.gitpod.toolbox.service.GitpodPublicApiManager
import io.gitpod.toolbox.service.Utils
+import io.gitpod.toolbox.service.getConnectParams
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import java.net.URI
-import java.net.URLEncoder
class GitpodRemoteProvider(
private val consumer: RemoteEnvironmentConsumer,
@@ -36,24 +37,29 @@ class GitpodRemoteProvider(
private val environmentMap = mutableMapOf()
private val openInToolboxUriHandler = GitpodOpenInToolboxUriHandler { connectParams ->
+ if (!authManger.loginWithHost(connectParams.gitpodHost)) {
+ // TODO: store connectParams locally so that user doesn't need another dashboard click?
+ return@GitpodOpenInToolboxUriHandler
+ }
Utils.toolboxUi.showPluginEnvironmentsPage()
- setEnvironmentVisibility(connectParams.workspaceId)
+ setEnvironmentVisibility(connectParams)
}
// TODO: multiple host support
- private fun setEnvironmentVisibility(workspaceId: String) {
+ private fun setEnvironmentVisibility(connectParams: ConnectParams) {
+ val workspaceId = connectParams.workspaceId
logger.info("setEnvironmentVisibility $workspaceId")
Utils.toolboxUi.showWindow()
val env = environmentMap[workspaceId]
if (env != null) {
env.markActive()
- Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-233.15026.17", "/workspace/empty")
+ Utils.clientHelper.setAutoConnectOnEnvironmentReady(connectParams.uniqueID, "GO-241.18034.61", "/workspace/template-golang-cli")
} else {
- GitpodRemoteProviderEnvironment(authManger, workspaceId, publicApi).apply {
+ GitpodRemoteProviderEnvironment(authManger, connectParams, publicApi).apply {
environmentMap[workspaceId] = this
this.markActive()
consumer.consumeEnvironments(listOf(this))
- Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-233.15026.17", "/workspace/empty")
+ Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-241.18034.61", "/workspace/template-golang-cli")
}
}
}
@@ -76,21 +82,14 @@ class GitpodRemoteProvider(
if (workspaces.isEmpty()) {
return@collect
}
- // TODO: Remove me
- workspaces.forEach{
- val host = URLEncoder.encode("https://exp-migration.preview.gitpod-dev.com", "UTF-8")
- val workspaceId = URLEncoder.encode(it.id, "UTF-8")
- val debugWorkspace = "false"
- val newUri = "jetbrains://gateway/io.gitpod.toolbox.gateway/open-in-toolbox?host=${host}&workspaceId=${workspaceId}&debugWorkspace=${debugWorkspace}"
- logger.info("workspace ${it.id} $newUri")
- }
consumer.consumeEnvironments(workspaces.map {
- val env = environmentMap[it.id]
+ val connectParams = it.getConnectParams()
+ val env = environmentMap[connectParams.uniqueID]
if (env != null) {
env
} else {
- val newEnv = GitpodRemoteProviderEnvironment(authManger, it.id, publicApi)
- environmentMap[it.id] = newEnv
+ val newEnv = GitpodRemoteProviderEnvironment(authManger, connectParams, publicApi)
+ environmentMap[connectParams.uniqueID] = newEnv
newEnv
}
})
@@ -99,9 +98,9 @@ class GitpodRemoteProvider(
}
private fun startup() {
- val account = authManger.getCurrentAccount() ?: return
+ authManger.getCurrentAccount() ?: return
publicApi.setup()
- val orgId = account.organizationId
+ val orgId = Utils.gitpodSettings.organizationId
logger.info("user logged in, current selected org: $orgId")
if (orgId != null) {
Utils.dataManager.startWatchWorkspaces(publicApi)
@@ -111,14 +110,19 @@ class GitpodRemoteProvider(
Utils.toolboxUi.showUiPage(organizationPage)
}
}
- authManger.getCurrentAccount()?.onOrgSelected {
- Utils.dataManager.startWatchWorkspaces(publicApi)
+ Utils.gitpodSettings.onSettingsChanged { key, _ ->
+ when (key) {
+ GitpodSettings.SettingKey.ORGANIZATION_ID.name -> {
+ Utils.dataManager.startWatchWorkspaces(publicApi)
+ }
+ }
}
}
override fun getOverrideUiPage(): UiPage? {
- logger.info("getOverrideUiPage")
- authManger.getCurrentAccount() ?: return loginPage
+ val account = authManger.getCurrentAccount()
+ logger.info("get override ui page for ${account?.getHost()}")
+ account ?: return loginPage
return null
}
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProviderEnvironment.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProviderEnvironment.kt
index 4b1be52c5cb404..6eaa89334bfb55 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProviderEnvironment.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodRemoteProviderEnvironment.kt
@@ -14,6 +14,7 @@ import io.gitpod.publicapi.v1.WorkspaceOuterClass
import io.gitpod.publicapi.v1.WorkspaceOuterClass.WorkspacePhase
import io.gitpod.toolbox.auth.GitpodAuthManager
import io.gitpod.toolbox.components.SimpleButton
+import io.gitpod.toolbox.service.ConnectParams
import io.gitpod.toolbox.service.GitpodPublicApiManager
import io.gitpod.toolbox.service.Utils
import kotlinx.coroutines.channels.BufferOverflow
@@ -24,7 +25,7 @@ import java.util.concurrent.CompletableFuture
class GitpodRemoteProviderEnvironment(
private val authManager: GitpodAuthManager,
- private val workspaceId: String,
+ private val connectParams: ConnectParams,
private val publicApi: GitpodPublicApiManager,
) : AbstractRemoteProviderEnvironment() {
private val logger = LoggerFactory.getLogger(javaClass)
@@ -32,7 +33,7 @@ class GitpodRemoteProviderEnvironment(
private val contentsViewFuture: CompletableFuture = CompletableFuture.completedFuture(
GitpodSSHEnvironmentContentsView(
authManager,
- workspaceId,
+ connectParams,
publicApi,
)
)
@@ -53,9 +54,9 @@ class GitpodRemoteProviderEnvironment(
}
init {
- logger.info("==================GitpodRemoteProviderEnvironment.init $workspaceId")
+ logger.info("==================GitpodRemoteProviderEnvironment.init ${connectParams.uniqueID}")
Utils.coroutineScope.launch {
- Utils.dataManager.watchWorkspaceStatus(workspaceId) {
+ Utils.dataManager.watchWorkspaceStatus(connectParams.workspaceId) {
lastPhase = it.phase
lastWSEnvState.tryEmit(WorkspaceEnvState(it.phase, isMarkActive))
}
@@ -88,8 +89,8 @@ class GitpodRemoteProviderEnvironment(
}
}
- override fun getId(): String = workspaceId
- override fun getName(): String = workspaceId
+ override fun getId(): String = connectParams.uniqueID
+ override fun getName(): String = connectParams.resolvedWorkspaceId
override fun getContentsView(): CompletableFuture = contentsViewFuture
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSSHEnvironmentContentsView.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSSHEnvironmentContentsView.kt
index daa087ce41a485..2deeecb09893ab 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSSHEnvironmentContentsView.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSSHEnvironmentContentsView.kt
@@ -8,6 +8,7 @@ import com.jetbrains.toolbox.gateway.environments.ManualEnvironmentContentsView
import com.jetbrains.toolbox.gateway.environments.SshEnvironmentContentsView
import com.jetbrains.toolbox.gateway.ssh.SshConnectionInfo
import io.gitpod.toolbox.auth.GitpodAuthManager
+import io.gitpod.toolbox.service.ConnectParams
import io.gitpod.toolbox.service.GitpodConnectionProvider
import io.gitpod.toolbox.service.GitpodPublicApiManager
import io.gitpod.toolbox.service.Utils
@@ -17,7 +18,7 @@ import java.util.concurrent.CompletableFuture
class GitpodSSHEnvironmentContentsView(
private val authManager: GitpodAuthManager,
- private val workspaceId: String,
+ private val connectParams: ConnectParams,
private val publicApi: GitpodPublicApiManager,
) : SshEnvironmentContentsView, ManualEnvironmentContentsView {
private var cancel = {}
@@ -27,7 +28,7 @@ class GitpodSSHEnvironmentContentsView(
override fun getConnectionInfo(): CompletableFuture {
return Utils.coroutineScope.future {
- val provider = GitpodConnectionProvider(authManager, workspaceId, publicApi)
+ val provider = GitpodConnectionProvider(authManager, connectParams, publicApi)
val (connInfo, cancel) = provider.connect()
this@GitpodSSHEnvironmentContentsView.cancel = cancel
return@future connInfo
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSettings.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSettings.kt
new file mode 100644
index 00000000000000..b3d12f3ae764e5
--- /dev/null
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodSettings.kt
@@ -0,0 +1,51 @@
+// Copyright (c) 2024 Gitpod GmbH. All rights reserved.
+// Licensed under the GNU Affero General Public License (AGPL).
+// See License.AGPL.txt in the project root for license information.
+
+package io.gitpod.toolbox.gateway
+
+import io.gitpod.toolbox.service.Utils
+import org.slf4j.LoggerFactory
+
+class GitpodSettings {
+ private val logger = LoggerFactory.getLogger(javaClass)
+ private val settingsChangedListeners: MutableList<(String, String) -> Unit> = mutableListOf()
+
+ private fun getStoreKey(key: SettingKey) = "GITPOD_SETTINGS:${key.name}"
+
+ private fun updateSetting(key: SettingKey, value: String) {
+ logger.debug("updateSetting ${key.name}=$value")
+ Utils.settingStore[getStoreKey(key)] = value
+ settingsChangedListeners.forEach { it(key.name, value) }
+ }
+
+ fun onSettingsChanged(listener: (String, String) -> Unit) {
+ settingsChangedListeners.add(listener)
+ }
+
+ var organizationId: String?
+ get() {
+ val value = Utils.settingStore[getStoreKey(SettingKey.ORGANIZATION_ID)]
+ return if (value.isNullOrBlank()) null else value
+ }
+ set(value) {
+ updateSetting(SettingKey.ORGANIZATION_ID, value ?: "")
+ }
+
+ fun resetSettings(host: String = "gitpod.io") {
+ logger.info("=============reset for $host")
+ gitpodHost = host
+ organizationId = ""
+ }
+
+ var gitpodHost: String
+ get() = Utils.settingStore[getStoreKey(SettingKey.GITPOD_HOST)] ?: "gitpod.io"
+ set(value) {
+ updateSetting(SettingKey.GITPOD_HOST, value)
+ }
+
+ enum class SettingKey {
+ ORGANIZATION_ID,
+ GITPOD_HOST
+ }
+}
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/DataManager.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/DataManager.kt
index 7e4feb841e6463..e2dfd042363fc4 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/DataManager.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/DataManager.kt
@@ -83,3 +83,7 @@ class DataManager {
}
}
}
+
+fun WorkspaceOuterClass.Workspace.getConnectParams(): ConnectParams {
+ return ConnectParams("exp-migration.preview.gitpod-dev.com", id, false)
+}
\ No newline at end of file
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodConnectionProvider.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodConnectionProvider.kt
index ea981091b8920f..41e324b2598a14 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodConnectionProvider.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodConnectionProvider.kt
@@ -10,15 +10,18 @@ import com.jetbrains.toolbox.gateway.ssh.SshConnectionInfo
import io.gitpod.publicapi.v1.WorkspaceOuterClass
import io.gitpod.toolbox.auth.GitpodAuthManager
import kotlinx.serialization.Serializable
+import org.slf4j.LoggerFactory
class GitpodConnectionProvider(
private val authManager: GitpodAuthManager,
- private val workspaceId: String,
+ private val connectParams: ConnectParams,
private val publicApi: GitpodPublicApiManager,
) {
+ private val logger = LoggerFactory.getLogger(javaClass)
private val activeConnections = ConcurrentHashMap()
suspend fun connect(): Pair Unit> {
+ val workspaceId = connectParams.workspaceId
val workspace = publicApi.getWorkspace(workspaceId).workspace
val ownerTokenResp = publicApi.getWorkspaceOwnerToken(workspaceId)
val account = authManager.getCurrentAccount() ?: throw Exception("No account found")
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodPublicApiManager.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodPublicApiManager.kt
index 504878a358d4cc..a4c34389e33ea6 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodPublicApiManager.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/GitpodPublicApiManager.kt
@@ -50,7 +50,7 @@ class GitpodPublicApiManager(private val authManger: GitpodAuthManager) {
}
private val orgId: String
- get() = account?.organizationId ?: throw IllegalStateException("Organization not selected")
+ get() = Utils.gitpodSettings.organizationId ?: throw IllegalStateException("Organization not selected")
suspend fun listOrganizations(): List {
val organizationApi = organizationApi ?: throw IllegalStateException("No client")
diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/Utils.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/Utils.kt
index 05b785e6400084..067f8ce6b759c0 100644
--- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/Utils.kt
+++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/Utils.kt
@@ -11,6 +11,7 @@ import com.jetbrains.toolbox.gateway.connection.ToolboxProxySettings
import com.jetbrains.toolbox.gateway.ssh.validation.SshConnectionValidator
import com.jetbrains.toolbox.gateway.ui.ObservablePropertiesFactory
import com.jetbrains.toolbox.gateway.ui.ToolboxUi
+import io.gitpod.toolbox.gateway.GitpodSettings
import kotlinx.coroutines.CoroutineScope
import okhttp3.OkHttpClient
import java.net.Proxy
@@ -26,6 +27,7 @@ object Utils {
lateinit var observablePropertiesFactory: ObservablePropertiesFactory private set
lateinit var proxySettings: ToolboxProxySettings private set
+ lateinit var gitpodSettings: GitpodSettings private set
lateinit var dataManager: DataManager private set
lateinit var toolboxUi: ToolboxUi private set
@@ -45,6 +47,7 @@ object Utils {
observablePropertiesFactory = serviceLocator.getService(ObservablePropertiesFactory::class.java)
proxySettings = serviceLocator.getService(ToolboxProxySettings::class.java)
dataManager = DataManager()
+ gitpodSettings = GitpodSettings()
}
fun openUrl(url: String) {