From 5e52f91278da48484e638b61a33c7b0dc8b63ed9 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 23 Sep 2024 15:24:10 -0800 Subject: [PATCH] [toolbox] Add workspace actions You can now start, stop, and update workspaces. Since you can start workspaces yourself now, I marked non-ready states as unreachable, which prevents JetBrains from overriding with their own text ("disconnected" and "connected"). So now you will be able to see "stopped", "starting", and so on. For the ready states you will still see "disconnected" or "connected" unfortunately. Ideally this would be a completely separate state displayed next to the workspace state. --- .../coder/gateway/CoderRemoteEnvironment.kt | 54 ++++++++++++++++++- .../com/coder/gateway/CoderRemoteProvider.kt | 2 +- .../gateway/models/WorkspaceAndAgentStatus.kt | 9 ++-- .../coder/gateway/sdk/v2/models/Workspace.kt | 1 + .../com/coder/gateway/views/CoderPage.kt | 4 +- .../kotlin/com/coder/gateway/sdk/DataGen.kt | 1 + 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt index 8f73152bf..b4d7c43ed 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt @@ -4,12 +4,15 @@ import com.coder.gateway.models.WorkspaceAndAgentStatus import com.coder.gateway.sdk.CoderRestClient import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceAgent +import com.coder.gateway.util.withPath +import com.coder.gateway.views.Action import com.coder.gateway.views.EnvironmentView 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.ui.ObservablePropertiesFactory +import com.jetbrains.toolbox.gateway.ui.ToolboxUi import java.util.concurrent.CompletableFuture /** @@ -19,18 +22,60 @@ import java.util.concurrent.CompletableFuture */ class CoderRemoteEnvironment( private val client: CoderRestClient, - private val workspace: Workspace, - private val agent: WorkspaceAgent, + private var workspace: Workspace, + private var agent: WorkspaceAgent, + private val ui: ToolboxUi, observablePropertiesFactory: ObservablePropertiesFactory, ) : AbstractRemoteProviderEnvironment(observablePropertiesFactory) { override fun getId(): String = "${workspace.name}.${agent.name}" override fun getName(): String = "${workspace.name}.${agent.name}" private var status = WorkspaceAndAgentStatus.from(workspace, agent) + init { + actionsList.add( + Action("Open web terminal") { + ui.openUrl(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) + }, + ) + actionsList.add( + Action("Open in dashboard") { + ui.openUrl(client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()) + }, + ) + actionsList.add( + Action("View template") { + ui.openUrl(client.url.withPath("/templates/${workspace.templateName}").toString()) + }, + ) + actionsList.add( + Action("Start", enabled = { status.canStart() }) { + val build = client.startWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }, + ) + actionsList.add( + Action("Stop", enabled = { status.ready() || status.pending() }) { + val build = client.stopWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }, + ) + actionsList.add( + Action("Update", enabled = { workspace.outdated }) { + val build = client.updateWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }, + ) + } + /** * Update the workspace/agent status to the listeners, if it has changed. */ fun update(workspace: Workspace, agent: WorkspaceAgent) { + this.workspace = workspace + this.agent = agent val newStatus = WorkspaceAndAgentStatus.from(workspace, agent) if (newStatus != status) { status = newStatus @@ -58,6 +103,11 @@ class CoderRemoteEnvironment( * Immediately send the state to the listener and store for updates. */ override fun addStateListener(consumer: EnvironmentStateConsumer): Boolean { + // TODO@JB: It would be ideal if we could have the workspace state and + // the connected state listed separately, since right now the + // connected state can mask the workspace state. + // TODO@JB: You can still press connect if the environment is + // unreachable. Is that expected? consumer.consume(status.toRemoteEnvironmentState()) return super.addStateListener(consumer) } diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt index 5d1299626..777b1f812 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt @@ -97,7 +97,7 @@ class CoderRemoteProvider( it.name }?.map { agent -> // If we have an environment already, update that. - val env = CoderRemoteEnvironment(client, ws, agent, observablePropertiesFactory) + val env = CoderRemoteEnvironment(client, ws, agent, ui, observablePropertiesFactory) lastEnvironments?.firstOrNull { it == env }?.let { it.update(ws, agent) it diff --git a/src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt b/src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt index bc20e4dae..5425e94ca 100644 --- a/src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt +++ b/src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt @@ -52,11 +52,8 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { * Return the environment state for Toolbox, which tells it the label, color * and whether the environment is reachable. * - * We mark all ready and pending states as reachable since if the workspace - * is pending the cli will wait for it anyway. - * - * Additionally, terminal states like stopped are also marked as reachable, - * since the cli will start them. + * Note that a reachable environment will always display "connected" or + * "disconnected" regardless of the label we give that status. */ fun toRemoteEnvironmentState(): CustomRemoteEnvironmentState { // Use comments; no named arguments for non-Kotlin functions. @@ -67,7 +64,7 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { Color(104, 112, 128, 255), // lightThemeColor Color(224, 224, 240, 26), // darkThemeBackgroundColor Color(224, 224, 245, 250), // lightThemeBackgroundColor - ready() || pending() || canStart(), // reachable + ready(), // reachable // TODO@JB: How does this work? Would like a spinner for pending states. null, // iconId ) diff --git a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt index 94c129af1..fad62c92b 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt @@ -18,4 +18,5 @@ data class Workspace( @Json(name = "latest_build") val latestBuild: WorkspaceBuild, @Json(name = "outdated") val outdated: Boolean, @Json(name = "name") val name: String, + @Json(name = "owner_name") val ownerName: String, ) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderPage.kt b/src/main/kotlin/com/coder/gateway/views/CoderPage.kt index 4e9ce4352..1ee77849d 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderPage.kt @@ -116,11 +116,13 @@ abstract class CoderPage( */ class Action( private val label: String, - private val closesPage: Boolean, + private val closesPage: Boolean = false, + private val enabled: () -> Boolean = { true }, private val cb: () -> Unit, ) : RunnableActionDescription { override fun getLabel(): String = label override fun getShouldClosePage(): Boolean = closesPage + override fun isEnabled(): Boolean = enabled() override fun run() { cb() } diff --git a/src/test/kotlin/com/coder/gateway/sdk/DataGen.kt b/src/test/kotlin/com/coder/gateway/sdk/DataGen.kt index 8e37e64ad..fda2d4181 100644 --- a/src/test/kotlin/com/coder/gateway/sdk/DataGen.kt +++ b/src/test/kotlin/com/coder/gateway/sdk/DataGen.kt @@ -53,6 +53,7 @@ class DataGen { ), outdated = false, name = name, + ownerName = "owner", ) }