diff --git a/CHANGELOG.md b/CHANGELOG.md index e87ca97..982be3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - workspaces status is now refresh every time Coder Toolbox becomes visible +- workspaces can no longer be removed by accident - users are now required to input the workspace name. ### Fixed diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index f8b3a17..91553e0 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -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 @@ -78,7 +79,7 @@ class CoderRemoteEnvironment( fun asPairOfWorkspaceAndAgent(): Pair = Pair(workspace, agent) private fun getAvailableActions(): List { - val actions = mutableListOf() + val actions = mutableListOf() if (wsRawStatus.canStop()) { actions.add(Action(context.i18n.ptrl("Open web terminal")) { context.cs.launch { @@ -143,6 +144,28 @@ class CoderRemoteEnvironment( } }) } + actions.add(CoderDelimiter(context.i18n.pnotr(""))) + actions.add(Action(context.i18n.ptrl("Delete workspace"), highlightInRed = true) { + context.cs.launch { + var dialogText = + 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." + dialogText += "\n\nType \"${workspace.name}\" below to confirm:" + + val confirmation = context.ui.showTextInputPopup( + if (wsRawStatus.canStop()) context.i18n.ptrl("Delete running workspace?") else context.i18n.ptrl("Delete workspace?"), + context.i18n.pnotr(dialogText), + context.i18n.ptrl("Workspace name"), + TextType.General, + context.i18n.ptrl("OK"), + context.i18n.ptrl("Cancel") + ) + if (confirmation != workspace.name) { + return@launch + } + deleteWorkspace() + } + }) return actions } @@ -272,43 +295,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()) "Workspace will be closed and all the information will be lost, including all files, unsaved changes, historical info and usage data." - else "All the information in this workspace will be lost, including all files, unsaved changes, historical info 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) } } diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 596255e..2584117 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -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 @@ -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 @@ -416,6 +416,4 @@ class CoderRemoteProvider( LoadableState.Loading } } -} - -private class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 363d618..1fd4cc3 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -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 @@ -57,13 +58,17 @@ abstract class CoderPage( class Action( description: LocalizableString, closesPage: Boolean = false, + highlightInRed: Boolean = false, enabled: () -> Boolean = { true }, private val actionBlock: () -> Unit, ) : RunnableActionDescription { override val label: LocalizableString = description override val shouldClosePage: Boolean = closesPage override val isEnabled: Boolean = enabled() + override val isDangerous: Boolean = highlightInRed override fun run() { actionBlock() } } + +class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter \ No newline at end of file diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 8aabe3f..29351e3 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -179,4 +179,13 @@ msgid "Headers" msgstr "" msgid "Body" -msgstr "" \ No newline at end of file +msgstr "" + +msgid "Delete workspace" +msgstr "" + +msgid "Delete running workspace?" +msgstr "" + +msgid "Workspace name" +msgstr ""