diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index c3e5f64..ad9d82f 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -3,6 +3,7 @@ package com.coder.toolbox import com.coder.toolbox.browser.BrowserUtil import com.coder.toolbox.models.WorkspaceAndAgentStatus import com.coder.toolbox.sdk.CoderRestClient +import com.coder.toolbox.sdk.ex.APIResponseException import com.coder.toolbox.sdk.v2.models.Workspace import com.coder.toolbox.sdk.v2.models.WorkspaceAgent import com.coder.toolbox.util.withPath @@ -13,9 +14,15 @@ import com.jetbrains.toolbox.api.remoteDev.AbstractRemoteProviderEnvironment import com.jetbrains.toolbox.api.remoteDev.EnvironmentVisibilityState import com.jetbrains.toolbox.api.remoteDev.environments.EnvironmentContentsView import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateConsumer +import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager import com.jetbrains.toolbox.api.ui.ToolboxUi import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds /** * Represents an agent and workspace combination. @@ -71,7 +78,7 @@ class CoderRemoteEnvironment( }, ) actionsList.add( - Action("Stop", enabled = { status.ready() || status.pending() }) { + Action("Stop", enabled = { status.canStop() }) { val build = client.stopWorkspace(workspace) workspace = workspace.copy(latestBuild = build) update(workspace, agent) @@ -128,7 +135,46 @@ class CoderRemoteEnvironment( } override fun onDelete() { - throw NotImplementedError() + cs.launch { + // TODO info and cancel pop-ups only appear on the main page where all environments are listed. + // However, #showSnackbar works on other pages. Until JetBrains fixes this issue we are going to use the snackbar + val shouldDelete = if (status.canStop()) { + ui.showOkCancelPopup( + "Delete running workspace?", + "Workspace will be closed and all the information in this workspace will be lost, including all files, unsaved changes and historical.", + "Delete", + "Cancel" + ) + } else { + ui.showOkCancelPopup( + "Delete workspace?", + "All the information in this workspace will be lost, including all files, unsaved changes and historical.", + "Delete", + "Cancel" + ) + } + if (shouldDelete) { + try { + client.removeWorkspace(workspace) + cs.launch { + withTimeout(5.minutes) { + var workspaceStillExists = true + while (cs.isActive && workspaceStillExists) { + if (status == WorkspaceAndAgentStatus.DELETING || status == WorkspaceAndAgentStatus.DELETED) { + workspaceStillExists = false + serviceLocator.getService(EnvironmentUiPageManager::class.java) + .showPluginEnvironmentsPage() + } else { + delay(1.seconds) + } + } + } + } + } catch (e: APIResponseException) { + ui.showErrorInfoPopup(e) + } + } + } } /** diff --git a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt index 63bf37f..dd5bb8b 100644 --- a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt +++ b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt @@ -9,6 +9,7 @@ import com.jetbrains.toolbox.api.core.ServiceLocator import com.jetbrains.toolbox.api.core.ui.color.StateColor import com.jetbrains.toolbox.api.remoteDev.states.CustomRemoteEnvironmentState import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette +import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateIcons import com.jetbrains.toolbox.api.remoteDev.states.StandardRemoteEnvironmentState /** @@ -59,26 +60,34 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { * "disconnected" regardless of the label we give that status. */ fun toRemoteEnvironmentState(serviceLocator: ServiceLocator): CustomRemoteEnvironmentState { - val stateColor = getStateColor(serviceLocator) return CustomRemoteEnvironmentState( label, - stateColor, + getStateColor(serviceLocator), ready(), // reachable // TODO@JB: How does this work? Would like a spinner for pending states. - null, // iconId + getStateIcon() ) } private fun getStateColor(serviceLocator: ServiceLocator): StateColor { val colorPalette = serviceLocator.getService(EnvironmentStateColorPalette::class.java) - return if (ready()) colorPalette.getColor(StandardRemoteEnvironmentState.Active) else if (canStart()) colorPalette.getColor(StandardRemoteEnvironmentState.Failed) else if (pending()) colorPalette.getColor(StandardRemoteEnvironmentState.Activating) + else if (this == DELETING) colorPalette.getColor(StandardRemoteEnvironmentState.Deleting) + else if (this == DELETED) colorPalette.getColor(StandardRemoteEnvironmentState.Deleted) else colorPalette.getColor(StandardRemoteEnvironmentState.Unreachable) } + private fun getStateIcon(): EnvironmentStateIcons { + return if (ready()) EnvironmentStateIcons.Active + else if (canStart()) EnvironmentStateIcons.Hibernated + else if (pending()) EnvironmentStateIcons.Connecting + else if (this == DELETING || this == DELETED) EnvironmentStateIcons.Offline + else EnvironmentStateIcons.NoIcon + } + /** * Return true if the agent is in a connectable state. */ @@ -107,6 +116,11 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { fun canStart(): Boolean = listOf(STOPPED, FAILED, CANCELED) .contains(this) + /** + * Return true if the workspace can be stopped. + */ + fun canStop(): Boolean = ready() || pending() + // We want to check that the workspace is `running`, the agent is // `connected`, and the agent lifecycle state is `ready` to ensure the best // possible scenario for attempting a connection. diff --git a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt index 4f4f7e0..371c818 100644 --- a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt +++ b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt @@ -30,7 +30,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory import java.net.HttpURLConnection import java.net.ProxySelector import java.net.URL -import java.util.* +import java.util.UUID import javax.net.ssl.X509TrustManager /** @@ -229,7 +229,6 @@ open class CoderRestClient( } /** - * @throws [APIResponseException]. */ fun stopWorkspace(workspace: Workspace): WorkspaceBuild { val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP) @@ -240,6 +239,17 @@ open class CoderRestClient( return buildResponse.body()!! } + /** + * @throws [APIResponseException] if issues are encountered during deletion + */ + fun removeWorkspace(workspace: Workspace) { + val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.DELETE, false) + val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute() + if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) { + throw APIResponseException("delete workspace ${workspace.name}", url, buildResponse) + } + } + /** * Start the workspace with the latest template version. Best practice is * to STOP a workspace before doing an update if it is started. diff --git a/src/main/kotlin/com/coder/toolbox/sdk/v2/models/CreateWorkspaceBuildRequest.kt b/src/main/kotlin/com/coder/toolbox/sdk/v2/models/CreateWorkspaceBuildRequest.kt index 65e310c..a2f1ca2 100644 --- a/src/main/kotlin/com/coder/toolbox/sdk/v2/models/CreateWorkspaceBuildRequest.kt +++ b/src/main/kotlin/com/coder/toolbox/sdk/v2/models/CreateWorkspaceBuildRequest.kt @@ -8,8 +8,9 @@ import java.util.UUID data class CreateWorkspaceBuildRequest( // Use to update the workspace to a new template version. @Json(name = "template_version_id") val templateVersionID: UUID?, - // Use to start and stop the workspace. + // Use to start, stop and delete the workspace. @Json(name = "transition") val transition: WorkspaceTransition, + @Json(name = "orphan") var orphan: Boolean? = null ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -19,12 +20,13 @@ data class CreateWorkspaceBuildRequest( if (templateVersionID != other.templateVersionID) return false if (transition != other.transition) return false - + if (orphan != other.orphan) return false return true } override fun hashCode(): Int { - var result = templateVersionID?.hashCode() ?: 0 + var result = orphan?.hashCode() ?: 0 + result = 31 * result + (templateVersionID?.hashCode() ?: 0) result = 31 * result + transition.hashCode() return result } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy