diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 71d08cbd4eb89b..855035e7967413 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,9 +4,6 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 61445018c0889b..55f873a160926a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,9 +1,12 @@ + + + - + diff --git a/components/ide/jetbrains/backend-plugin/gradle-latest.properties b/components/ide/jetbrains/backend-plugin/gradle-latest.properties index 0777c7606a6cde..b024224ba1344f 100644 --- a/components/ide/jetbrains/backend-plugin/gradle-latest.properties +++ b/components/ide/jetbrains/backend-plugin/gradle-latest.properties @@ -6,4 +6,4 @@ pluginUntilBuild=242.* # See https://jb.gg/intellij-platform-builds-list for available build versions. pluginVerifierIdeVersions=2024.2 # Version from "com.jetbrains.intellij.idea" which can be found at https://www.jetbrains.com/intellij-repository/snapshots -platformVersion=242.19533-EAP-CANDIDATE-SNAPSHOT +platformVersion=242.20224-EAP-CANDIDATE-SNAPSHOT diff --git a/components/ide/jetbrains/toolbox/build.gradle.kts b/components/ide/jetbrains/toolbox/build.gradle.kts index ff6b421b5ec1e3..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,12 +38,12 @@ 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") implementation("org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.54.v20240208") - // RD-Core + // RD-Core https://mvnrepository.com/artifact/com.jetbrains.rd/rd-core implementation("com.jetbrains.rd:rd-core:2024.1.1") implementation(libs.gateway.api) diff --git a/components/ide/jetbrains/toolbox/gradle/libs.versions.toml b/components/ide/jetbrains/toolbox/gradle/libs.versions.toml index cdac41588bd02c..be4d983bd2f69a 100644 --- a/components/ide/jetbrains/toolbox/gradle/libs.versions.toml +++ b/components/ide/jetbrains/toolbox/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -gateway = "2.4.0.30948" +gateway = "2.4.0.31544" kotlin = "1.9.0" coroutines = "1.7.3" serialization = "1.5.0" 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..aea612ab5e8ef1 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("gitpod: user logged in ${it.accountId}") + resetCurrentAccount(it.accountId) loginListeners.forEach { it() } } + AuthEvent.Type.LOGOUT -> { - logger.info("account ${it.accountId} logged out") + logger.debug("gitpod: user logged out ${it.accountId}") + 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/colima/ColimaTestEnvironment.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/colima/ColimaTestEnvironment.kt deleted file mode 100644 index e081717757af31..00000000000000 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/colima/ColimaTestEnvironment.kt +++ /dev/null @@ -1,70 +0,0 @@ -// 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.colima - -import com.jetbrains.toolbox.gateway.AbstractRemoteProviderEnvironment -import com.jetbrains.toolbox.gateway.EnvironmentVisibilityState -import com.jetbrains.toolbox.gateway.environments.EnvironmentContentsView -import com.jetbrains.toolbox.gateway.environments.ManualEnvironmentContentsView -import com.jetbrains.toolbox.gateway.environments.SshEnvironmentContentsView -import com.jetbrains.toolbox.gateway.ssh.SshConnectionInfo -import com.jetbrains.toolbox.gateway.states.StandardRemoteEnvironmentState -import com.jetbrains.toolbox.gateway.ui.ActionDescription -import com.jetbrains.toolbox.gateway.ui.ActionListener -import com.jetbrains.toolbox.gateway.ui.ObservableList -import io.gitpod.toolbox.service.Utils -import kotlinx.coroutines.launch -import java.util.concurrent.CompletableFuture - -class ColimaTestEnvironment() : AbstractRemoteProviderEnvironment() { - private val actionListeners = mutableSetOf() - private val contentsViewFuture: CompletableFuture = - CompletableFuture.completedFuture(ColimaSSHEnvironmentContentsView()) - - init { - Utils.coroutineScope.launch { - Thread.sleep(2000) - listenerSet.forEach { it.consume(StandardRemoteEnvironmentState.Active) } - } - } - - override fun getId(): String = "colima" - override fun getName(): String = "colima" - - override fun getContentsView(): CompletableFuture = contentsViewFuture - - override fun setVisible(visibilityState: EnvironmentVisibilityState) { - - } - - override fun getActionList(): ObservableList { - return Utils.observablePropertiesFactory.emptyObservableList() - } - -} - -class ColimaSSHEnvironmentContentsView : SshEnvironmentContentsView, ManualEnvironmentContentsView { - private val listenerSet = mutableSetOf() - - override fun getConnectionInfo(): CompletableFuture { - return CompletableFuture.completedFuture(object : SshConnectionInfo { - override fun getHost(): String = "127.0.0.1" - override fun getPort() = 51710 - override fun getUserName() = "hwen" - override fun getPrivateKeyPaths(): MutableList? { - return mutableListOf("/Users/hwen/.colima/_lima/_config/user") - } - }) - } - - override fun addEnvironmentContentsListener(listener: ManualEnvironmentContentsView.Listener) { - listenerSet += listener - } - - override fun removeEnvironmentContentsListener(listener: ManualEnvironmentContentsView.Listener) { - listenerSet -= listener - } - -} diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodLogger.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodLogger.kt index f9688e928bc094..f4cbda7f5fd0ab 100644 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodLogger.kt +++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodLogger.kt @@ -5,9 +5,9 @@ package io.gitpod.toolbox.gateway import com.jetbrains.toolbox.gateway.deploy.DiagnosticInfoCollector +import org.slf4j.Logger import java.nio.file.Path import java.util.concurrent.CompletableFuture -import org.slf4j.Logger class GitpodLogger(private val logger:Logger) : DiagnosticInfoCollector { diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodNewEnvironmentPage.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodNewEnvironmentPage.kt deleted file mode 100644 index 4032bec4d9eaf9..00000000000000 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodNewEnvironmentPage.kt +++ /dev/null @@ -1,110 +0,0 @@ -// 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 com.jetbrains.toolbox.gateway.ui.ActionDescription -import com.jetbrains.toolbox.gateway.ui.ComboBoxField -import com.jetbrains.toolbox.gateway.ui.TextField -import com.jetbrains.toolbox.gateway.ui.TextType -import com.jetbrains.toolbox.gateway.ui.UiField -import io.gitpod.toolbox.auth.GitpodAuthManager -import io.gitpod.toolbox.components.AbstractUiPage -import io.gitpod.toolbox.components.SimpleButton -import io.gitpod.toolbox.service.GitpodPublicApiManager -import io.gitpod.toolbox.service.Utils -import kotlinx.coroutines.launch -import org.slf4j.LoggerFactory - -class GitpodNewEnvironmentPage(val authManager: GitpodAuthManager, val publicApi: GitpodPublicApiManager) : - AbstractUiPage() { - private val logger = LoggerFactory.getLogger(javaClass) - - override fun getFields(): MutableList { - return mutableListOf(orgField, contextUrlField, editorField, workspaceClassField) - } - - override fun getTitle(): String { - return "New environment" - } - - override fun getActionButtons(): MutableList { - return mutableListOf(SimpleButton("Create") { - val contextUrl = getFieldValue(contextUrlField) ?: return@SimpleButton - val editor = getFieldValue(editorField) ?: return@SimpleButton - val workspaceClass = getFieldValue(workspaceClassField) ?: return@SimpleButton - if (contextUrl.isBlank()) { - setActionErrorMessage("Context URL is required") - return@SimpleButton - } - if (editor.isBlank()) { - setActionErrorMessage("Editor is required") - return@SimpleButton - } - if (workspaceClass.isBlank()) { - setActionErrorMessage("Workspace class is required") - return@SimpleButton - } - Utils.coroutineScope.launch { - val workspace = publicApi.createAndStartWorkspace(contextUrl, editor, workspaceClass, null) - logger.info("workspace: ${workspace.id} created") - } - }) - } - - private val orgField = getOrgField() - private fun getOrgField(): TextField { - // TODO: Use ComboBoxField or AutocompleteTextField with org results - return TextField("Organization", authManager.getCurrentAccount()?.organizationId ?: "", TextType.General) - } - - // TODO: Use AutocompleteTextField with suggestions from API - // TODO: Add account recent repositories related field? Or get from auto start options - private val contextUrlField = - TextField("Context URL", "https://github.com/Gitpod-Samples/spring-petclinic", TextType.General) - - // TODO: get from API - private val editorField = ComboBoxField( - "Editor", - authManager.getCurrentAccount()?.preferEditor ?: "intellij", - listOf( - ComboBoxField.LabelledValue("IntelliJ IDEA", "intellij"), - ComboBoxField.LabelledValue("Goland", "goland") - ) - ) - - // TODO: get from API - private val workspaceClassField = ComboBoxField( - "Workspace Class", - authManager.getCurrentAccount()?.preferWorkspaceClass ?: "g1-standard", - listOf(ComboBoxField.LabelledValue("Standard", "g1-standard"), ComboBoxField.LabelledValue("Small", "g1-small")) - ) - - override fun fieldChanged(field: UiField) { - super.fieldChanged(field) - val account = authManager.getCurrentAccount() ?: return - if (field == orgField) { - val orgId = getFieldValue(orgField) ?: return - logger.info("set prefer orgId: $orgId") - account.organizationId = orgId - // Not works -// setFieldValue(orgField, orgId) - return - } - if (field == editorField) { - val editor = getFieldValue(editorField) ?: return - logger.info("set prefer editor: $editor") - account.preferEditor = editor - return - } - if (field == workspaceClassField) { - val cls = getFieldValue(workspaceClassField) ?: return - logger.info("set prefer workspaceClass: $cls") - account.preferWorkspaceClass = cls - // Not works -// setFieldValue(workspaceClassField, cls) - return - } - } -} 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 23ef461c3e6056..fb0370f56cbc21 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 @@ -14,15 +14,15 @@ import com.jetbrains.toolbox.gateway.ui.UiPage import io.gitpod.toolbox.auth.GitpodAuthManager import io.gitpod.toolbox.auth.GitpodLoginPage import io.gitpod.toolbox.auth.GitpodOrganizationPage -import io.gitpod.toolbox.colima.ColimaTestEnvironment 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, @@ -31,69 +31,55 @@ class GitpodRemoteProvider( private val authManger = GitpodAuthManager() private val publicApi = GitpodPublicApiManager(authManger) private val loginPage = GitpodLoginPage(authManger) - private val newEnvPage = GitpodNewEnvironmentPage(authManger, publicApi) private val organizationPage = GitpodOrganizationPage(authManger, publicApi) // cache consumed environments map locally private val environmentMap = mutableMapOf() - private val openInToolboxUriHandler = GitpodOpenInToolboxUriHandler { connectParams -> + private var pendingConnectParams: Pair? = null + private val openInToolboxUriHandler = GitpodOpenInToolboxUriHandler { (gitpodHost, connectParams) -> + if (!authManger.loginWithHost(gitpodHost)) { + pendingConnectParams = gitpodHost to connectParams + return@GitpodOpenInToolboxUriHandler + } + Utils.toolboxUi.showWindow() 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] + val env = environmentMap[connectParams.uniqueID] if (env != null) { env.markActive() - Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-233.15026.17", "/workspace/empty") + Utils.clientHelper.setAutoConnectOnEnvironmentReady(connectParams.uniqueID, "GO-242.20224.39", "/workspace/empty") } else { - GitpodRemoteProviderEnvironment(authManger, workspaceId, publicApi).apply { - environmentMap[workspaceId] = this + GitpodRemoteProviderEnvironment(authManger, connectParams, publicApi).apply { + environmentMap[connectParams.uniqueID] = this this.markActive() consumer.consumeEnvironments(listOf(this)) - Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-233.15026.17", "/workspace/empty") + Utils.clientHelper.setAutoConnectOnEnvironmentReady(workspaceId, "GO-242.20224.39", "/workspace/empty") } } } init { - startup() - authManger.addLoginListener { - logger.info("user logged in ${authManger.getCurrentAccount()?.id}") - startup() - // TODO: showPluginEnvironmentsPage not refresh the page - Utils.toolboxUi.showPluginEnvironmentsPage() - } - authManger.addLogoutListener { - logger.info("user logged out ${authManger.getCurrentAccount()?.id}") - // TODO: showPluginEnvironmentsPage not refresh the page - Utils.toolboxUi.showPluginEnvironmentsPage() - } Utils.coroutineScope.launch { Utils.dataManager.sharedWorkspaceList.collect { workspaces -> if (workspaces.isEmpty()) { return@collect } - 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] - if (env != null) { - env - } else { - val newEnv = GitpodRemoteProviderEnvironment(authManger, it.id, publicApi) - environmentMap[it.id] = newEnv - newEnv + val connectParams = it.getConnectParams() + val env = environmentMap[connectParams.uniqueID] ?: GitpodRemoteProviderEnvironment(authManger, connectParams, publicApi) + environmentMap[connectParams.uniqueID] = env + if (connectParams.uniqueID == pendingConnectParams?.second?.uniqueID) { + setEnvironmentVisibility(connectParams) + pendingConnectParams = null } + env }) } } @@ -102,7 +88,7 @@ class GitpodRemoteProvider( private fun startup() { val account = 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) @@ -112,14 +98,29 @@ 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 + startup() + authManger.addLoginListener { + Utils.toolboxUi.showWindow() + Utils.toolboxUi.showPluginEnvironmentsPage() + startup() + } + authManger.addLogoutListener { + Utils.toolboxUi.showWindow() + Utils.toolboxUi.showPluginEnvironmentsPage() + } return null } @@ -128,7 +129,7 @@ class GitpodRemoteProvider( override fun getName(): String = "Gitpod" override fun getSvgIcon() = GitpodIcon() - override fun getNewEnvironmentUiPage() = newEnvPage + override fun getNewEnvironmentUiPage() = UiPage.empty override fun getAccountDropDown(): AccountDropdownField? { val account = authManger.getCurrentAccount() ?: return null @@ -137,28 +138,11 @@ class GitpodRemoteProvider( } } - private fun testColima() = run { - val env = ColimaTestEnvironment() - consumer.consumeEnvironments(listOf(env)) - Utils.clientHelper.setAutoConnectOnEnvironmentReady("colima", "IU-241.14494.240", "/home/hwen.linux/project") - } - override fun getAdditionalPluginActions(): MutableList { return mutableListOf( SimpleButton("View documents") { Utils.openUrl("https://gitpod.io/docs") }, - SimpleButton("Colima") { - testColima() - }, - SimpleButton("Show toast") { - logger.info("toast shown") - val t = Utils.toolboxUi.showInfoPopup("This is header", "This is content", "okText") - Utils.coroutineScope.launch { - t.get() - logger.info("toast closed") - } - }, SimpleButton("Select organization") { Utils.coroutineScope.launch { organizationPage.loadData() 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..41686eec70c220 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 @@ -7,6 +7,7 @@ package io.gitpod.toolbox.gateway import com.jetbrains.toolbox.gateway.AbstractRemoteProviderEnvironment import com.jetbrains.toolbox.gateway.EnvironmentVisibilityState import com.jetbrains.toolbox.gateway.environments.EnvironmentContentsView +import com.jetbrains.toolbox.gateway.states.EnvironmentStateConsumer import com.jetbrains.toolbox.gateway.states.StandardRemoteEnvironmentState import com.jetbrains.toolbox.gateway.ui.ActionDescription import com.jetbrains.toolbox.gateway.ui.ObservableList @@ -14,6 +15,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 +26,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 +34,7 @@ class GitpodRemoteProviderEnvironment( private val contentsViewFuture: CompletableFuture = CompletableFuture.completedFuture( GitpodSSHEnvironmentContentsView( authManager, - workspaceId, + connectParams, publicApi, ) ) @@ -40,7 +42,7 @@ class GitpodRemoteProviderEnvironment( private val lastWSEnvState = MutableSharedFlow(1, 0, BufferOverflow.DROP_OLDEST) private var lastPhase: WorkspacePhase = WorkspacePhase.newBuilder().setNameValue(WorkspacePhase.Phase.PHASE_UNSPECIFIED_VALUE).build() - private var isMarkActive = false + public var isMarkActive = false set(value) { if (field != value) { field = value @@ -53,14 +55,6 @@ class GitpodRemoteProviderEnvironment( } init { - logger.info("==================GitpodRemoteProviderEnvironment.init $workspaceId") - Utils.coroutineScope.launch { - Utils.dataManager.watchWorkspaceStatus(workspaceId) { - lastPhase = it.phase - lastWSEnvState.tryEmit(WorkspaceEnvState(it.phase, isMarkActive)) - } - } - Utils.coroutineScope.launch { lastWSEnvState.collect { lastState -> val state = lastState.getState() @@ -76,20 +70,30 @@ class GitpodRemoteProviderEnvironment( Utils.coroutineScope.launch { contentsViewFuture.get().close() } } } - if (lastState.isStoppable) { - actions += SimpleButton("Stop workspace") { - logger.info("===============stop workspace clicked") - } - } actionList.clear() actionList.addAll(actions) listenerSet.forEach { it.consume(state) } } } + + Utils.coroutineScope.launch { + Utils.dataManager.watchWorkspaceStatus(connectParams.workspaceId) { + lastPhase = it.phase + lastWSEnvState.tryEmit(WorkspaceEnvState(it.phase, isMarkActive)) + } + } + } + + override fun addStateListener(consumer: EnvironmentStateConsumer): Boolean { + val ok = super.addStateListener(consumer) + Utils.coroutineScope.launch { + lastWSEnvState.tryEmit(WorkspaceEnvState(lastPhase, isMarkActive)) + } + return ok } - 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 @@ -104,7 +108,6 @@ class GitpodRemoteProviderEnvironment( private class WorkspaceEnvState(val phase: WorkspacePhase, val isMarkActive: Boolean) { val isConnectable = phase.nameValue == WorkspaceOuterClass.WorkspacePhase.Phase.PHASE_RUNNING_VALUE && !isMarkActive val isCloseable = isMarkActive - val isStoppable = phase.nameValue == WorkspaceOuterClass.WorkspacePhase.Phase.PHASE_RUNNING_VALUE fun getState() = run { if (isMarkActive && phase.nameValue == WorkspaceOuterClass.WorkspacePhase.Phase.PHASE_RUNNING_VALUE) { 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/gateway/GitpodUriHandler.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodUriHandler.kt index 74b7dd4e2f0c0b..04b0dee7cce510 100644 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodUriHandler.kt +++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/GitpodUriHandler.kt @@ -31,10 +31,13 @@ abstract class AbstractUriHandler : UriHandler { } } -class GitpodOpenInToolboxUriHandler(val handler: (ConnectParams) -> Unit) : AbstractUriHandler() { - override fun handle(data: ConnectParams): Future = CompletableFuture.runAsync { handler(data) } +class GitpodOpenInToolboxUriHandler(val handler: (Pair) -> Unit) : AbstractUriHandler>() { - override fun parseUri(uri: URI): ConnectParams { + private val logger = LoggerFactory.getLogger(javaClass) + + override fun handle(data: Pair): Future = CompletableFuture.runAsync { handler(data) } + + override fun parseUri(uri: URI): Pair { val path = uri.path.split("/").last() if (path != "open-in-toolbox") { throw IllegalArgumentException("invalid URI: $path") @@ -54,7 +57,7 @@ class GitpodOpenInToolboxUriHandler(val handler: (ConnectParams) -> Unit) : Abst } catch (e: IllegalArgumentException) { throw IllegalArgumentException("invalid host: $host") } - - return ConnectParams(host, workspaceId, debugWorkspace) + logger.info("gitpod: parsed URI: $host, $workspaceId, $debugWorkspace") + return Pair("https://$host", ConnectParams(workspaceId, debugWorkspace)) } } 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..df947e6761cf00 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(id, false) +} 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..e7669f10decb3a 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,21 +10,24 @@ 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") // TODO: debug workspace - val connectParams = ConnectParams(account.getHost(), workspaceId, false) + val connectParams = ConnectParams(workspaceId, false) val (serverPort, cancel) = tunnelWithWebSocket(workspace, connectParams, ownerTokenResp.ownerToken) @@ -80,13 +83,12 @@ class GitpodWebSocketSshConnectionInfo( } data class ConnectParams( - val gitpodHost: String, val workspaceId: String, val debugWorkspace: Boolean = false, ) { val resolvedWorkspaceId = "${if (debugWorkspace) "debug-" else ""}$workspaceId" - val title = "$resolvedWorkspaceId ($gitpodHost)" - val uniqueID = "$gitpodHost-$workspaceId-$debugWorkspace" + val title = "$resolvedWorkspaceId" + val uniqueID = "$workspaceId-$debugWorkspace" } @Serializable 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/PageRouter.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/PageRouter.kt deleted file mode 100644 index d82cb5b0583ede..00000000000000 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/service/PageRouter.kt +++ /dev/null @@ -1,75 +0,0 @@ -// 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.service - -import com.jetbrains.toolbox.gateway.ui.LabelField -import com.jetbrains.toolbox.gateway.ui.UiField -import com.jetbrains.toolbox.gateway.ui.UiPage -import org.slf4j.LoggerFactory - -interface Route { - val path: String - val page: UiPage -} - -class PageRouter { - private val logger = LoggerFactory.getLogger(javaClass) - private val history = mutableListOf() - private val routes: MutableList = mutableListOf() - private val listeners: MutableList<(String?) -> Unit> = mutableListOf() - - fun addRoutes(vararg newRoutes: Route) { - logger.info("add routes: {}", newRoutes.map { it.path }) - this.routes.addAll(newRoutes) - } - - fun goTo(path: String) { - val route = routes.find { it.path == path } ?: kotlin.run { - logger.warn("route not found: $path") - return - } - logger.info("go to route: ${route.path}") - history.add(route) - notifyListeners() - } - - fun goBack() { - logger.info("go back") - if (history.size >= 1) { - history.removeAt(history.size - 1) - notifyListeners() - } else { - logger.warn("no route to go back") - return - } - } - - fun getCurrentPage(): Pair { - logger.info("current page: ${history.lastOrNull()?.page}") - val route = history.lastOrNull() ?: return PageNotFound() to true - return route.page to false - } - - fun addListener(listener: (String?) -> Unit): () -> Unit { - listeners.add(listener) - return { - listeners.remove(listener) - } - } - - private fun notifyListeners() { - listeners.forEach { it(history.lastOrNull()?.path) } - } -} - -class PageNotFound : UiPage { - override fun getTitle(): String { - return "Not Found" - } - - override fun getFields(): MutableList { - return mutableListOf(LabelField("Not found")) - } -} 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) { diff --git a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/await.kt b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/utils/await.kt similarity index 96% rename from components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/await.kt rename to components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/utils/await.kt index 12e1be26d3b2fd..4be6a3a1583812 100644 --- a/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/gateway/await.kt +++ b/components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/utils/await.kt @@ -2,7 +2,7 @@ // 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 +package io.gitpod.toolbox.utils import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.Call