Skip to content

Commit 36d871a

Browse files
committed
[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.
1 parent 9e55075 commit 36d871a

File tree

6 files changed

+61
-10
lines changed

6 files changed

+61
-10
lines changed

src/main/kotlin/com/coder/gateway/CoderRemoteEnvironment.kt

+52-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import com.coder.gateway.models.WorkspaceAndAgentStatus
44
import com.coder.gateway.sdk.CoderRestClient
55
import com.coder.gateway.sdk.v2.models.Workspace
66
import com.coder.gateway.sdk.v2.models.WorkspaceAgent
7+
import com.coder.gateway.util.withPath
8+
import com.coder.gateway.views.Action
79
import com.coder.gateway.views.EnvironmentView
810
import com.jetbrains.toolbox.gateway.AbstractRemoteProviderEnvironment
911
import com.jetbrains.toolbox.gateway.EnvironmentVisibilityState
1012
import com.jetbrains.toolbox.gateway.environments.EnvironmentContentsView
1113
import com.jetbrains.toolbox.gateway.states.EnvironmentStateConsumer
1214
import com.jetbrains.toolbox.gateway.ui.ObservablePropertiesFactory
15+
import com.jetbrains.toolbox.gateway.ui.ToolboxUi
1316
import java.util.concurrent.CompletableFuture
1417

1518
/**
@@ -19,18 +22,60 @@ import java.util.concurrent.CompletableFuture
1922
*/
2023
class CoderRemoteEnvironment(
2124
private val client: CoderRestClient,
22-
private val workspace: Workspace,
23-
private val agent: WorkspaceAgent,
25+
private var workspace: Workspace,
26+
private var agent: WorkspaceAgent,
27+
private val ui: ToolboxUi,
2428
observablePropertiesFactory: ObservablePropertiesFactory,
2529
) : AbstractRemoteProviderEnvironment(observablePropertiesFactory) {
2630
override fun getId(): String = "${workspace.name}.${agent.name}"
2731
override fun getName(): String = "${workspace.name}.${agent.name}"
2832
private var status = WorkspaceAndAgentStatus.from(workspace, agent)
2933

34+
init {
35+
actionsList.add(
36+
Action("Open web terminal") {
37+
ui.openUrl(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString())
38+
},
39+
)
40+
actionsList.add(
41+
Action("Open in dashboard") {
42+
ui.openUrl(client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString())
43+
},
44+
)
45+
actionsList.add(
46+
Action("View template") {
47+
ui.openUrl(client.url.withPath("/templates/${workspace.templateName}").toString())
48+
},
49+
)
50+
actionsList.add(
51+
Action("Start", enabled = { status.canStart() }) {
52+
val build = client.startWorkspace(workspace)
53+
workspace = workspace.copy(latestBuild = build)
54+
update(workspace, agent)
55+
},
56+
)
57+
actionsList.add(
58+
Action("Stop", enabled = { status.ready() || status.pending() }) {
59+
val build = client.stopWorkspace(workspace)
60+
workspace = workspace.copy(latestBuild = build)
61+
update(workspace, agent)
62+
},
63+
)
64+
actionsList.add(
65+
Action("Update", enabled = { workspace.outdated }) {
66+
val build = client.updateWorkspace(workspace)
67+
workspace = workspace.copy(latestBuild = build)
68+
update(workspace, agent)
69+
},
70+
)
71+
}
72+
3073
/**
3174
* Update the workspace/agent status to the listeners, if it has changed.
3275
*/
3376
fun update(workspace: Workspace, agent: WorkspaceAgent) {
77+
this.workspace = workspace
78+
this.agent = agent
3479
val newStatus = WorkspaceAndAgentStatus.from(workspace, agent)
3580
if (newStatus != status) {
3681
status = newStatus
@@ -58,6 +103,11 @@ class CoderRemoteEnvironment(
58103
* Immediately send the state to the listener and store for updates.
59104
*/
60105
override fun addStateListener(consumer: EnvironmentStateConsumer): Boolean {
106+
// TODO@JB: It would be ideal if we could have the workspace state and
107+
// the connected state listed separately, since right now the
108+
// connected state can mask the workspace state.
109+
// TODO@JB: You can still press connect if the environment is
110+
// unreachable. Is that expected?
61111
consumer.consume(status.toRemoteEnvironmentState())
62112
return super.addStateListener(consumer)
63113
}

src/main/kotlin/com/coder/gateway/CoderRemoteProvider.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class CoderRemoteProvider(
9797
it.name
9898
}?.map { agent ->
9999
// If we have an environment already, update that.
100-
val env = CoderRemoteEnvironment(client, ws, agent, observablePropertiesFactory)
100+
val env = CoderRemoteEnvironment(client, ws, agent, ui, observablePropertiesFactory)
101101
lastEnvironments?.firstOrNull { it == env }?.let {
102102
it.update(ws, agent)
103103
it

src/main/kotlin/com/coder/gateway/models/WorkspaceAndAgentStatus.kt

+3-6
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,8 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) {
5252
* Return the environment state for Toolbox, which tells it the label, color
5353
* and whether the environment is reachable.
5454
*
55-
* We mark all ready and pending states as reachable since if the workspace
56-
* is pending the cli will wait for it anyway.
57-
*
58-
* Additionally, terminal states like stopped are also marked as reachable,
59-
* since the cli will start them.
55+
* Note that a reachable environment will always display "connected" or
56+
* "disconnected" regardless of the label we give that status.
6057
*/
6158
fun toRemoteEnvironmentState(): CustomRemoteEnvironmentState {
6259
// Use comments; no named arguments for non-Kotlin functions.
@@ -67,7 +64,7 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) {
6764
Color(104, 112, 128, 255), // lightThemeColor
6865
Color(224, 224, 240, 26), // darkThemeBackgroundColor
6966
Color(224, 224, 245, 250), // lightThemeBackgroundColor
70-
ready() || pending() || canStart(), // reachable
67+
ready(), // reachable
7168
// TODO@JB: How does this work? Would like a spinner for pending states.
7269
null, // iconId
7370
)

src/main/kotlin/com/coder/gateway/sdk/v2/models/Workspace.kt

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ data class Workspace(
1818
@Json(name = "latest_build") val latestBuild: WorkspaceBuild,
1919
@Json(name = "outdated") val outdated: Boolean,
2020
@Json(name = "name") val name: String,
21+
@Json(name = "owner_name") val ownerName: String,
2122
)

src/main/kotlin/com/coder/gateway/views/CoderPage.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ abstract class CoderPage(
116116
*/
117117
class Action(
118118
private val label: String,
119-
private val closesPage: Boolean,
119+
private val closesPage: Boolean = false,
120+
private val enabled: () -> Boolean = { true },
120121
private val cb: () -> Unit,
121122
) : RunnableActionDescription {
122123
override fun getLabel(): String = label
123124
override fun getShouldClosePage(): Boolean = closesPage
125+
override fun isEnabled(): Boolean = enabled()
124126
override fun run() {
125127
cb()
126128
}

src/test/kotlin/com/coder/gateway/sdk/DataGen.kt

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class DataGen {
5353
),
5454
outdated = false,
5555
name = name,
56+
ownerName = "owner",
5657
)
5758
}
5859

0 commit comments

Comments
 (0)