diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index f7723d86..c1d79115 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -1,7 +1,8 @@ package com.coder.gateway.sdk import com.coder.gateway.sdk.convertors.InstantConverter -import com.coder.gateway.sdk.ex.AuthenticationException +import com.coder.gateway.sdk.ex.AuthenticationResponseException +import com.coder.gateway.sdk.ex.WorkspaceResponseException import com.coder.gateway.sdk.v2.CoderV2RestFacade import com.coder.gateway.sdk.v2.models.BuildInfo import com.coder.gateway.sdk.v2.models.User @@ -30,7 +31,7 @@ class CoderRestClientService { /** * This must be called before anything else. It will authenticate with coder and retrieve a session token - * @throws [AuthenticationException] if authentication failed + * @throws [AuthenticationResponseException] if authentication failed. */ fun initClientSession(url: URL, token: String): User { val cookieUrl = url.toHttpUrlOrNull()!! @@ -61,7 +62,7 @@ class CoderRestClientService { val userResponse = retroRestClient.me().execute() if (!userResponse.isSuccessful) { - throw IllegalStateException("Could not retrieve information about logged use:${userResponse.code()}, reason: ${userResponse.message()}") + throw AuthenticationResponseException("Could not retrieve information about logged user:${userResponse.code()}, reason: ${userResponse.message()}") } coderURL = url @@ -72,10 +73,14 @@ class CoderRestClientService { return me } + /** + * Retrieves the available workspaces created by the user. + * @throws WorkspaceResponseException if workspaces could not be retrieved. + */ fun workspaces(): List { val workspacesResponse = retroRestClient.workspaces().execute() if (!workspacesResponse.isSuccessful) { - throw IllegalStateException("Could not retrieve Coder Workspaces:${workspacesResponse.code()}, reason: ${workspacesResponse.message()}") + throw WorkspaceResponseException("Could not retrieve Coder Workspaces:${workspacesResponse.code()}, reason: ${workspacesResponse.message()}") } return workspacesResponse.body()!! diff --git a/src/main/kotlin/com/coder/gateway/sdk/ex/AuthenticationException.kt b/src/main/kotlin/com/coder/gateway/sdk/ex/AuthenticationException.kt deleted file mode 100644 index e241720a..00000000 --- a/src/main/kotlin/com/coder/gateway/sdk/ex/AuthenticationException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.coder.gateway.sdk.ex - -import java.io.IOException - -class AuthenticationException(reason: String) : IOException(reason) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt b/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt new file mode 100644 index 00000000..d24e338c --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/sdk/ex/exceptions.kt @@ -0,0 +1,7 @@ +package com.coder.gateway.sdk.ex + +import java.io.IOException + +class AuthenticationResponseException(reason: String) : IOException(reason) + +class WorkspaceResponseException(reason: String) : IOException(reason) \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt index 72f29f2b..1cd46e37 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayConnectorWizardView.kt @@ -32,7 +32,9 @@ class CoderGatewayConnectorWizardView : BorderLayoutPanel(), Disposable { registerStep(CoderAuthStepView { next() }) registerStep(CoderWorkspacesStepView()) - registerStep(CoderLocateRemoteProjectStepView()) + registerStep(CoderLocateRemoteProjectStepView { + nextButton.isVisible = false + }) addToBottom(createBackComponent()) @@ -64,9 +66,15 @@ class CoderGatewayConnectorWizardView : BorderLayoutPanel(), Disposable { nextButton.text = nextActionText previousButton.text = previousActionText } + showNavigationButtons() } } + private fun showNavigationButtons() { + nextButton.isVisible = true + previousButton.isVisible = true + } + private fun next() { if (!doNextCallback()) return if (currentStep + 1 < steps.size) { @@ -81,6 +89,7 @@ class CoderGatewayConnectorWizardView : BorderLayoutPanel(), Disposable { nextButton.text = nextActionText previousButton.text = previousActionText } + showNavigationButtons() } } diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index a4159b92..221f1bc9 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -7,6 +7,7 @@ import com.coder.gateway.icons.CoderIcons import com.coder.gateway.models.RecentWorkspaceConnection import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService import com.intellij.ide.BrowserUtil +import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.components.service import com.intellij.openapi.project.DumbAwareAction @@ -30,12 +31,13 @@ import com.jetbrains.gateway.ssh.IntelliJPlatformProduct import com.jetbrains.rd.util.lifetime.Lifetime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import java.awt.Dimension import javax.swing.JComponent import javax.swing.event.DocumentEvent -class CoderGatewayRecentWorkspaceConnectionsView : GatewayRecentConnections { +class CoderGatewayRecentWorkspaceConnectionsView : GatewayRecentConnections, Disposable { private val recentConnectionsService = service() private val cs = CoroutineScope(Dispatchers.Main) @@ -137,4 +139,8 @@ class CoderGatewayRecentWorkspaceConnectionsView : GatewayRecentConnections { border = JBUI.Borders.empty(12, 0, 0, 12) } } + + override fun dispose() { + cs.cancel() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderAuthStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderAuthStepView.kt index 4fcfdad9..012a8978 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderAuthStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderAuthStepView.kt @@ -8,6 +8,7 @@ import com.coder.gateway.models.CoderWorkspacesWizardModel import com.coder.gateway.sdk.CoderCLIManager import com.coder.gateway.sdk.CoderRestClientService import com.coder.gateway.sdk.OS +import com.coder.gateway.sdk.ex.AuthenticationResponseException import com.coder.gateway.sdk.getOS import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.withPath @@ -89,7 +90,12 @@ class CoderAuthStepView(private val nextAction: () -> Unit) : CoderWorkspacesWiz if (pastedToken.isNullOrBlank()) { return false } - coderClient.initClientSession(model.coderURL.toURL(), pastedToken) + try { + coderClient.initClientSession(model.coderURL.toURL(), pastedToken) + } catch (e: AuthenticationResponseException) { + logger.error("Could not authenticate on ${model.coderURL}. Reason $e") + return false + } model.token = pastedToken model.buildVersion = coderClient.buildVersion diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt index f5caa95e..63a854bd 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt @@ -31,6 +31,7 @@ import com.jetbrains.gateway.ssh.IntelliJPlatformProduct import com.jetbrains.gateway.ssh.guessOs import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.awt.Component @@ -43,7 +44,7 @@ import javax.swing.JPanel import javax.swing.ListCellRenderer import javax.swing.SwingConstants -class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { +class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit) : CoderWorkspacesWizardStep, Disposable { private val cs = CoroutineScope(Dispatchers.Main) private val coderClient: CoderRestClientService = ApplicationManager.getApplication().getService(CoderRestClientService::class.java) @@ -110,14 +111,28 @@ class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { cs.launch { logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...") - try { - val workspaceOS = withContext(Dispatchers.IO) { + val workspaceOS = withContext(Dispatchers.IO) { + try { RemoteCredentialsHolder().apply { setHost("coder.${selectedWorkspace.name}") userName = "coder" authType = AuthType.OPEN_SSH }.guessOs + } catch (e: Exception) { + logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e") + null } + } + if (workspaceOS == null) { + disableNextAction() + cbIDE.renderer = object : ColoredListCellRenderer() { + override fun customizeCellRenderer(list: JList, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) { + background = UIUtil.getListBackground(isSelected, cellHasFocus) + icon = UIUtil.getBalloonErrorIcon() + append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name)) + } + } + } else { logger.info("Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS") val idesWithStatus = IntelliJPlatformProduct.values() .filter { it.showInGateway } @@ -130,15 +145,6 @@ class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { ideComboBoxModel.addAll(idesWithStatus) cbIDE.selectedIndex = 0 } - } catch (e: Exception) { - logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e") - cbIDE.renderer = object : ColoredListCellRenderer() { - override fun customizeCellRenderer(list: JList, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) { - background = UIUtil.getListBackground(isSelected, cellHasFocus) - icon = UIUtil.getBalloonErrorIcon() - append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name)) - } - } } } } @@ -147,7 +153,6 @@ class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { val selectedIDE = cbIDE.selectedItem ?: return false cs.launch { - GatewayUI.getInstance().connect( mapOf( "type" to "coder", @@ -164,6 +169,7 @@ class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { } override fun dispose() { + cs.cancel() } companion object { @@ -203,7 +209,7 @@ class CoderLocateRemoteProjectStepView : CoderWorkspacesWizardStep, Disposable { background = UIUtil.getListBackground(isSelected, cellHasFocus) } } else { - panel { } + panel { } } } } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index eef2f05c..822fef7b 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -8,6 +8,7 @@ import com.coder.gateway.sdk.v2.models.Workspace import com.intellij.ide.IdeBundle import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.CollectionListModel import com.intellij.ui.components.JBList @@ -19,6 +20,7 @@ import com.intellij.ui.dsl.gridLayout.VerticalAlign import com.intellij.util.ui.JBFont import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -57,7 +59,12 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable { cs.launch { val workspaceList = withContext(Dispatchers.IO) { - coderClient.workspaces() + try { + coderClient.workspaces() + } catch (e: Exception) { + logger.error("Could not retrieve workspaces for ${coderClient.me.username} on ${coderClient.coderURL}. Reason: $e") + emptyList() + } } workspaceList.forEach { workspaces.add(it) @@ -75,6 +82,10 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable { } override fun dispose() { + cs.cancel() + } + companion object { + val logger = Logger.getInstance(CoderWorkspacesStepView::class.java.simpleName) } } \ No newline at end of file