Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed

- token is no longer required when authentication is done via certificates
- errors while running actions are now reported

## 0.6.4 - 2025-09-03

Expand Down
83 changes: 38 additions & 45 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,68 +81,61 @@ class CoderRemoteEnvironment(
private fun getAvailableActions(): List<ActionDescription> {
val actions = mutableListOf<Action>()
if (wsRawStatus.canStop()) {
actions.add(Action(context.i18n.ptrl("Open web terminal")) {
context.cs.launch(CoroutineName("Open Web Terminal Action")) {
context.desktop.browse(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) {
context.ui.showErrorInfoPopup(it)
}
actions.add(Action(context, "Open web terminal") {
context.desktop.browse(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) {
context.ui.showErrorInfoPopup(it)
}
})
}
)
}
actions.add(
Action(context.i18n.ptrl("Open in dashboard")) {
context.cs.launch(CoroutineName("Open in Dashboard Action")) {
context.desktop.browse(
client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()
) {
context.ui.showErrorInfoPopup(it)
}
}
})

actions.add(Action(context.i18n.ptrl("View template")) {
context.cs.launch(CoroutineName("View Template Action")) {
context.desktop.browse(client.url.withPath("/templates/${workspace.templateName}").toString()) {
Action(context, "Open in dashboard") {
context.desktop.browse(
client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()
) {
context.ui.showErrorInfoPopup(it)
}
}
})
)

actions.add(Action(context, "View template") {
context.desktop.browse(client.url.withPath("/templates/${workspace.templateName}").toString()) {
context.ui.showErrorInfoPopup(it)
}
}
)

if (wsRawStatus.canStart()) {
if (workspace.outdated) {
actions.add(Action(context.i18n.ptrl("Update and start")) {
context.cs.launch(CoroutineName("Update and Start Action")) {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
})
actions.add(Action(context, "Update and start") {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
)
} else {
actions.add(Action(context.i18n.ptrl("Start")) {
context.cs.launch(CoroutineName("Start Action")) {
val build = client.startWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
actions.add(Action(context, "Start") {
val build = client.startWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)

}
})
}
)
}
}
if (wsRawStatus.canStop()) {
if (workspace.outdated) {
actions.add(Action(context.i18n.ptrl("Update and restart")) {
context.cs.launch(CoroutineName("Update and Restart Action")) {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
})
}
actions.add(Action(context.i18n.ptrl("Stop")) {
context.cs.launch(CoroutineName("Stop Action")) {
tryStopSshConnection()

val build = client.stopWorkspace(workspace)
actions.add(Action(context, "Update and restart") {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
})
)
}
actions.add(Action(context, "Stop") {
tryStopSshConnection()

val build = client.stopWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
)
}
return actions
}
Expand Down
24 changes: 14 additions & 10 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ class CoderRemoteProvider(
* first page.
*/
private fun logout() {
context.logger.info("Logging out ${client?.me?.username}...")
WorkspaceConnectionManager.reset()
close()
context.logger.info("User ${client?.me?.username} logged out successfully")
}

/**
Expand All @@ -222,15 +224,13 @@ class CoderRemoteProvider(

override val additionalPluginActions: StateFlow<List<ActionDescription>> = MutableStateFlow(
listOf(
Action(context.i18n.ptrl("Create workspace")) {
context.cs.launch(CoroutineName("Create Workspace Action")) {
context.desktop.browse(client?.url?.withPath("/templates").toString()) {
context.ui.showErrorInfoPopup(it)
}
Action(context, "Create workspace") {
context.desktop.browse(client?.url?.withPath("/templates").toString()) {
context.ui.showErrorInfoPopup(it)
}
},
CoderDelimiter(context.i18n.pnotr("")),
Action(context.i18n.ptrl("Settings")) {
Action(context, "Settings") {
context.ui.showUiPage(settingsPage)
},
)
Expand All @@ -246,12 +246,16 @@ class CoderRemoteProvider(
it.cancel()
context.logger.info("Cancelled workspace poll job ${pollJob.toString()}")
}
client?.close()
client?.let {
it.close()
context.logger.info("REST API client closed and resources released")
}
client = null
lastEnvironments.clear()
environments.value = LoadableState.Value(emptyList())
isInitialized.update { false }
client = null
CoderCliSetupWizardState.goToFirstStep()
context.logger.info("Coder plugin is now closed")
}

override val svgIcon: SvgIcon =
Expand Down Expand Up @@ -319,12 +323,12 @@ class CoderRemoteProvider(
uri,
shouldDoAutoSetup()
) { restClient, cli ->
// stop polling and de-initialize resources
context.logger.info("Stopping workspace polling and de-initializing resources")
close()
isInitialized.update {
false
}
// start initialization with the new settings
context.logger.info("Starting initialization with the new settings")
this@CoderRemoteProvider.client = restClient
coderHeaderPage.setTitle(context.i18n.pnotr(restClient.url.toString()))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class CoderCliSetupWizardPage(
) -> Unit,
) : CoderPage(MutableStateFlow(context.i18n.ptrl("Setting up Coder")), false) {
private val shouldAutoSetup = MutableStateFlow(initialAutoSetup)
private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = {
private val settingsAction = Action(context, "Settings") {
context.ui.showUiPage(settingsPage)
})
}

private val deploymentUrlStep = DeploymentUrlStep(context, visibilityState)
private val tokenStep = TokenStep(context)
Expand Down Expand Up @@ -60,7 +60,7 @@ class CoderCliSetupWizardPage(
}
actionButtons.update {
listOf(
Action(context.i18n.ptrl("Next"), closesPage = false, actionBlock = {
Action(context, "Next", closesPage = false, actionBlock = {
if (deploymentUrlStep.onNext()) {
displaySteps()
}
Expand All @@ -77,13 +77,13 @@ class CoderCliSetupWizardPage(
}
actionButtons.update {
listOf(
Action(context.i18n.ptrl("Connect"), closesPage = false, actionBlock = {
Action(context, "Connect", closesPage = false, actionBlock = {
if (tokenStep.onNext()) {
displaySteps()
}
}),
settingsAction,
Action(context.i18n.ptrl("Back"), closesPage = false, actionBlock = {
Action(context, "Back", closesPage = false, actionBlock = {
tokenStep.onBack()
displaySteps()
})
Expand All @@ -99,7 +99,7 @@ class CoderCliSetupWizardPage(
actionButtons.update {
listOf(
settingsAction,
Action(context.i18n.ptrl("Back"), closesPage = false, actionBlock = {
Action(context, "Back", closesPage = false, actionBlock = {
connectStep.onBack()
shouldAutoSetup.update {
false
Expand Down
24 changes: 20 additions & 4 deletions src/main/kotlin/com/coder/toolbox/views/CoderPage.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.coder.toolbox.views

import com.coder.toolbox.CoderToolboxContext
import com.coder.toolbox.sdk.ex.APIResponseException
import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon
import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType
import com.jetbrains.toolbox.api.localization.LocalizableString
import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription
import com.jetbrains.toolbox.api.ui.components.UiPage
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

/**
* Base page that handles the icon, displaying error notifications, and
Expand Down Expand Up @@ -48,15 +52,27 @@ abstract class CoderPage(
* An action that simply runs the provided callback.
*/
class Action(
description: LocalizableString,
private val context: CoderToolboxContext,
private val description: String,
closesPage: Boolean = false,
enabled: () -> Boolean = { true },
private val actionBlock: () -> Unit,
private val actionBlock: suspend () -> Unit,
) : RunnableActionDescription {
override val label: LocalizableString = description
override val label: LocalizableString = context.i18n.ptrl(description)
override val shouldClosePage: Boolean = closesPage
override val isEnabled: Boolean = enabled()
override fun run() {
actionBlock()
context.cs.launch(CoroutineName("$description Action")) {
try {
actionBlock()
} catch (ex: Exception) {
val textError = if (ex is APIResponseException) {
if (!ex.reason.isNullOrBlank()) {
ex.reason
} else ex.message
} else ex.message
context.logAndShowError("Error while running `$description`", textError ?: "", ex)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class CoderSettingsPage(private val context: CoderToolboxContext, triggerSshConf

override val actionButtons: StateFlow<List<RunnableActionDescription>> = MutableStateFlow(
listOf(
Action(context.i18n.ptrl("Save"), closesPage = true) {
Action(context, "Save", closesPage = true) {
context.settingsStore.updateBinarySource(binarySourceField.contentState.value)
context.settingsStore.updateBinaryDirectory(binaryDirectoryField.contentState.value)
context.settingsStore.updateDataDirectory(dataDirectoryField.contentState.value)
Expand Down
Loading