Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
impl: confirmation dialog for workspace deletion
Users are now required to confirm the workspace name
if they want to delete a workspace. This is in order to
avoid any accidental removals.
  • Loading branch information
fioan89 committed Aug 21, 2025
commit e63fd0784ee952359decc1f06d3e0af82a3546c8
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
### Changed

- workspaces status is now refresh every time Coder Toolbox becomes visible
- improved workspace delete confirmation dialog
- workspaces can no longer be removed by accident - users are now required to input the workspace name.

### Fixed

Expand Down
74 changes: 41 additions & 33 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
import com.coder.toolbox.util.waitForFalseWithTimeout
import com.coder.toolbox.util.withPath
import com.coder.toolbox.views.Action
import com.coder.toolbox.views.CoderDelimiter
import com.coder.toolbox.views.EnvironmentView
import com.jetbrains.toolbox.api.localization.LocalizableString
import com.jetbrains.toolbox.api.remoteDev.AfterDisconnectHook
import com.jetbrains.toolbox.api.remoteDev.BeforeConnectionHook
import com.jetbrains.toolbox.api.remoteDev.DeleteEnvironmentConfirmationParams
import com.jetbrains.toolbox.api.remoteDev.EnvironmentVisibilityState
import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
import com.jetbrains.toolbox.api.remoteDev.environments.EnvironmentContentsView
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentDescription
import com.jetbrains.toolbox.api.remoteDev.states.RemoteEnvironmentState
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
import com.jetbrains.toolbox.api.ui.components.TextType
import com.squareup.moshi.Moshi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -78,7 +79,7 @@ class CoderRemoteEnvironment(
fun asPairOfWorkspaceAndAgent(): Pair<Workspace, WorkspaceAgent> = Pair(workspace, agent)

private fun getAvailableActions(): List<ActionDescription> {
val actions = mutableListOf<Action>()
val actions = mutableListOf<ActionDescription>()
if (wsRawStatus.canStop()) {
actions.add(Action(context.i18n.ptrl("Open web terminal")) {
context.cs.launch {
Expand Down Expand Up @@ -143,6 +144,24 @@ class CoderRemoteEnvironment(
}
})
}
actions.add(CoderDelimiter(context.i18n.pnotr("")))
actions.add(Action(context.i18n.ptrl("Delete workspace")) {
context.cs.launch {
val confirmation = context.ui.showTextInputPopup(
if (wsRawStatus.canStop()) context.i18n.ptrl("Delete running workspace?") else context.i18n.ptrl("Delete workspace?"),
if (wsRawStatus.canStop()) context.i18n.ptrl("This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data.")
else context.i18n.ptrl("This will remove all information from the workspace, including files, unsaved changes, history, and usage data."),
context.i18n.ptrl("Workspace name"),
TextType.General,
context.i18n.ptrl("OK"),
context.i18n.ptrl("Cancel")
)
if (confirmation != workspace.name) {
return@launch
}
deleteWorkspace()
}
})
return actions
}

Expand Down Expand Up @@ -272,43 +291,32 @@ class CoderRemoteEnvironment(
return false
}

override fun getDeleteEnvironmentConfirmationParams(): DeleteEnvironmentConfirmationParams? {
return object : DeleteEnvironmentConfirmationParams {
override val cancelButtonText: String = "Cancel"
override val confirmButtonText: String = "Delete"
override val message: String =
if (wsRawStatus.canStop()) "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data."
else "This will remove all information from the workspace, including files, unsaved changes, history, and usage data."
override val title: String = if (wsRawStatus.canStop()) "Delete running workspace?" else "Delete workspace?"
}
}
override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow(null)

override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow {
context.cs.launch {
try {
client.removeWorkspace(workspace)
// mark the env as deleting otherwise we will have to
// wait for the poller to update the status in the next 5 seconds
state.update {
WorkspaceAndAgentStatus.DELETING.toRemoteEnvironmentState(context)
}
suspend fun deleteWorkspace() {
try {
client.removeWorkspace(workspace)
// mark the env as deleting otherwise we will have to
// wait for the poller to update the status in the next 5 seconds
state.update {
WorkspaceAndAgentStatus.DELETING.toRemoteEnvironmentState(context)
}

context.cs.launch {
withTimeout(5.minutes) {
var workspaceStillExists = true
while (context.cs.isActive && workspaceStillExists) {
if (wsRawStatus == WorkspaceAndAgentStatus.DELETING || wsRawStatus == WorkspaceAndAgentStatus.DELETED) {
workspaceStillExists = false
context.envPageManager.showPluginEnvironmentsPage()
} else {
delay(1.seconds)
}
context.cs.launch {
withTimeout(5.minutes) {
var workspaceStillExists = true
while (context.cs.isActive && workspaceStillExists) {
if (wsRawStatus == WorkspaceAndAgentStatus.DELETING || wsRawStatus == WorkspaceAndAgentStatus.DELETED) {
workspaceStillExists = false
context.envPageManager.showPluginEnvironmentsPage()
} else {
delay(1.seconds)
}
}
}
} catch (e: APIResponseException) {
context.ui.showErrorInfoPopup(e)
}
} catch (e: APIResponseException) {
context.ui.showErrorInfoPopup(e)
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.coder.toolbox.util.waitForTrue
import com.coder.toolbox.util.withPath
import com.coder.toolbox.views.Action
import com.coder.toolbox.views.CoderCliSetupWizardPage
import com.coder.toolbox.views.CoderDelimiter
import com.coder.toolbox.views.CoderSettingsPage
import com.coder.toolbox.views.NewEnvironmentPage
import com.coder.toolbox.views.state.CoderCliSetupWizardState
Expand All @@ -21,7 +22,6 @@ import com.jetbrains.toolbox.api.core.util.LoadableState
import com.jetbrains.toolbox.api.localization.LocalizableString
import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState
import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
import com.jetbrains.toolbox.api.ui.actions.ActionDelimiter
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
import com.jetbrains.toolbox.api.ui.components.UiPage
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -416,6 +416,4 @@ class CoderRemoteProvider(
LoadableState.Loading
}
}
}

private class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter
}
3 changes: 3 additions & 0 deletions src/main/kotlin/com/coder/toolbox/views/CoderPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.coder.toolbox.CoderToolboxContext
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.ActionDelimiter
import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription
import com.jetbrains.toolbox.api.ui.components.UiPage
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -67,3 +68,5 @@ class Action(
actionBlock()
}
}

class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter
17 changes: 16 additions & 1 deletion src/main/resources/localization/defaultMessages.po
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,19 @@ msgid "Headers"
msgstr ""

msgid "Body"
msgstr ""
msgstr ""

msgid "Delete workspace"
msgstr ""

msgid "Delete running workspace?"
msgstr ""

msgid "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data."
msgstr ""

msgid "This will remove all information from the workspace, including files, unsaved changes, history, and usage data."
msgstr ""

msgid "Workspace name"
msgstr ""
Loading