From eb932252031138e3f3700d334b7d8cc5810e04a1 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Mon, 23 Jun 2025 23:41:14 +0300 Subject: [PATCH 1/8] impl: use the new API to set titles on UI pages --- src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt | 6 +++--- .../com/coder/toolbox/views/CoderCliSetupWizardPage.kt | 2 +- src/main/kotlin/com/coder/toolbox/views/CoderPage.kt | 4 ++-- .../kotlin/com/coder/toolbox/views/CoderSettingsPage.kt | 2 +- .../kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt | 5 ++--- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 101cf71..2e56e64 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -63,7 +63,7 @@ class CoderRemoteProvider( // On the first load, automatically log in if we can. private var firstRun = true private val isInitialized: MutableStateFlow = MutableStateFlow(false) - private var coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(context.deploymentUrl.toString())) + private var coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(context.deploymentUrl.toString())) private val linkHandler = CoderProtocolHandler(context, dialogUi, isInitialized) override val environments: MutableStateFlow>> = MutableStateFlow( @@ -317,7 +317,7 @@ class CoderRemoteProvider( close() // start initialization with the new settings this@CoderRemoteProvider.client = restClient - coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(restClient.url.toString())) + coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(restClient.url.toString())) environments.showLoadingMessage() pollJob = poll(restClient, cli) @@ -387,7 +387,7 @@ class CoderRemoteProvider( this.client = client pollJob?.cancel() environments.showLoadingMessage() - coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(client.url.toString())) + coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(client.url.toString())) pollJob = poll(client, cli) context.refreshMainPage() } diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt index c6193da..5115204 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt @@ -25,7 +25,7 @@ class CoderCliSetupWizardPage( client: CoderRestClient, cli: CoderCLIManager, ) -> Unit, -) : CoderPage(context.i18n.ptrl("Setting up Coder"), false) { +) : CoderPage(MutableStateFlow(context.i18n.ptrl("Setting up Coder")), false) { private val shouldAutoSetup = MutableStateFlow(initialAutoSetup) private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = { context.ui.showUiPage(settingsPage) diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 9b83f45..fe9e2ae 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -19,9 +19,9 @@ import kotlinx.coroutines.flow.MutableStateFlow * to use the mouse. */ abstract class CoderPage( - title: LocalizableString, + private val titleObservable: MutableStateFlow, showIcon: Boolean = true, -) : UiPage(title) { +) : UiPage(titleObservable) { /** * Return the icon, if showing one. diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt index de2ce0b..61827be 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt @@ -20,7 +20,7 @@ import kotlinx.coroutines.launch * I have not been able to test this page. */ class CoderSettingsPage(context: CoderToolboxContext, triggerSshConfig: Channel) : - CoderPage(context.i18n.ptrl("Coder Settings"), false) { + CoderPage(MutableStateFlow(context.i18n.ptrl("Coder Settings")), false) { private val settings = context.settingsStore.readOnly() // TODO: Copy over the descriptions, holding until I can test this page. diff --git a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt index 83e07c7..cde23b2 100644 --- a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt @@ -1,6 +1,5 @@ package com.coder.toolbox.views -import com.coder.toolbox.CoderToolboxContext import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.UiField import kotlinx.coroutines.flow.MutableStateFlow @@ -14,7 +13,7 @@ import kotlinx.coroutines.flow.StateFlow * For now we just use this to display the deployment URL since we do not * support creating environments from the plugin. */ -class NewEnvironmentPage(context: CoderToolboxContext, deploymentURL: LocalizableString) : - CoderPage(deploymentURL) { +class NewEnvironmentPage(deploymentURL: LocalizableString) : + CoderPage(MutableStateFlow(deploymentURL)) { override val fields: StateFlow> = MutableStateFlow(emptyList()) } From a042ffa3796c2735b73af702389803897bdcbbc1 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 00:09:51 +0300 Subject: [PATCH 2/8] impl: update page title instead of creating a new page With the new TBX API we can update the title of a UI page without recreating the page and using hacks to force TBX to update the internal state. --- src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt | 5 ++--- src/main/kotlin/com/coder/toolbox/views/CoderPage.kt | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 2e56e64..30de29d 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -377,7 +377,7 @@ class CoderRemoteProvider( private fun shouldDoAutoSetup(): Boolean = firstRun && context.secrets.rememberMe == true - private suspend fun onConnect(client: CoderRestClient, cli: CoderCLIManager) { + private fun onConnect(client: CoderRestClient, cli: CoderCLIManager) { // Store the URL and token for use next time. context.secrets.lastDeploymentURL = client.url.toString() context.secrets.lastToken = client.token ?: "" @@ -387,9 +387,8 @@ class CoderRemoteProvider( this.client = client pollJob?.cancel() environments.showLoadingMessage() - coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(client.url.toString())) + coderHeaderPage.setTitle(context.i18n.pnotr(client.url.toString())) pollJob = poll(client, cli) - context.refreshMainPage() } private fun MutableStateFlow>>.showLoadingMessage() { diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index fe9e2ae..363d618 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -7,6 +7,7 @@ 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.flow.MutableStateFlow +import kotlinx.coroutines.flow.update /** * Base page that handles the icon, displaying error notifications, and @@ -23,6 +24,12 @@ abstract class CoderPage( showIcon: Boolean = true, ) : UiPage(titleObservable) { + fun setTitle(title: LocalizableString) { + titleObservable.update { + title + } + } + /** * Return the icon, if showing one. * From 3283ab69ae4df0d270306ddd54fadc3c35dbdc69 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 00:18:56 +0300 Subject: [PATCH 3/8] impl: use the new API version 1.3.46097 Used by the Toolbox 2.7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0db6399..31a9855 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -toolbox-plugin-api = "1.1.41749" +toolbox-plugin-api = "1.3.46097" kotlin = "2.1.10" coroutines = "1.10.1" serialization = "1.8.0" From 1cbe9ed7bd3d9c4061615660c7ac96d664538cea Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 22:34:26 +0300 Subject: [PATCH 4/8] fix: jump to main page after coder setup is successful We still need to switch from the coder setup page back to the main screen once the coder cli and the rest api poller is initialized. --- .../com/coder/toolbox/CoderRemoteProvider.kt | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 30de29d..1ec185f 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -167,7 +167,7 @@ class CoderRemoteProvider( close() // force auto-login firstRun = true - goToEnvironmentsPage() + context.envPageManager.showPluginEnvironmentsPage() break } } @@ -324,18 +324,6 @@ class CoderRemoteProvider( } } - /** - * Make Toolbox ask for the page again. Use any time we need to change the - * root page (for example, sign-in or the environment list). - * - * When moving between related pages, instead use ui.showUiPage() and - * ui.hideUiPage() which stacks and has built-in back navigation, rather - * than using multiple root pages. - */ - private fun goToEnvironmentsPage() { - context.envPageManager.showPluginEnvironmentsPage() - } - /** * Return the sign-in page if we do not have a valid client. @@ -389,6 +377,7 @@ class CoderRemoteProvider( environments.showLoadingMessage() coderHeaderPage.setTitle(context.i18n.pnotr(client.url.toString())) pollJob = poll(client, cli) + context.envPageManager.showPluginEnvironmentsPage() } private fun MutableStateFlow>>.showLoadingMessage() { From 894d56e3f1d0580dc2996401423a57cf038a6ed5 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 22:48:08 +0300 Subject: [PATCH 5/8] impl: update URI handling page title instead of creating a new page Similar with the initial setup page we can use the new TBX API to update the title of the uri handling page without recreating it and using hacks to force TBX to update the internal state. --- src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 1ec185f..9816596 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -63,7 +63,7 @@ class CoderRemoteProvider( // On the first load, automatically log in if we can. private var firstRun = true private val isInitialized: MutableStateFlow = MutableStateFlow(false) - private var coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(context.deploymentUrl.toString())) + private val coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(context.deploymentUrl.toString())) private val linkHandler = CoderProtocolHandler(context, dialogUi, isInitialized) override val environments: MutableStateFlow>> = MutableStateFlow( @@ -317,7 +317,7 @@ class CoderRemoteProvider( close() // start initialization with the new settings this@CoderRemoteProvider.client = restClient - coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(restClient.url.toString())) + coderHeaderPage.setTitle(context.i18n.pnotr(restClient.url.toString())) environments.showLoadingMessage() pollJob = poll(restClient, cli) From f3b2be2e2b21fb168a52c0125b3797c187c113dd Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 23:16:56 +0300 Subject: [PATCH 6/8] impl: use the new constructor parameters work environment status --- .../com/coder/toolbox/models/WorkspaceAndAgentStatus.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt index cc04dfe..b5458f8 100644 --- a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt +++ b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt @@ -65,9 +65,9 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { return CustomRemoteEnvironmentStateV2( context.i18n.pnotr(label), color = getStateColor(context), - reachable = ready() || unhealthy(), + isReachable = ready() || unhealthy(), // TODO@JB: How does this work? Would like a spinner for pending states. - icon = getStateIcon() + iconId = getStateIcon().id, ) } From 6d67abe13a788c563d83be3a66553ea3658c02f8 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 24 Jun 2025 23:24:00 +0300 Subject: [PATCH 7/8] impl: prioritize workspace status over TBX agent status TBX 2.7 API prioritizes connection status to the remote TBX agent over the workspace status. This would mean that a running workspace would be listed as disconnected instead of ready. This is confusing as it only happens for running workspaces and on top of that we also have a green badge that is rendered by TBX when the agent connection is established. --- .../kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt index b5458f8..28cc14b 100644 --- a/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt +++ b/src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt @@ -68,6 +68,7 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) { isReachable = ready() || unhealthy(), // TODO@JB: How does this work? Would like a spinner for pending states. iconId = getStateIcon().id, + isPriorityShow = true ) } From 01863108a766bbd2a9a815da87f83608220164fe Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 25 Jun 2025 00:19:07 +0300 Subject: [PATCH 8/8] fix: UT's related to proxy configuration A new API was added to allow proxy authentication --- src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt b/src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt index 2727228..c42ead2 100644 --- a/src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt +++ b/src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt @@ -24,6 +24,7 @@ import com.jetbrains.toolbox.api.core.diagnostics.Logger import com.jetbrains.toolbox.api.core.os.LocalDesktopManager import com.jetbrains.toolbox.api.localization.LocalizableStringFactory import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper +import com.jetbrains.toolbox.api.remoteDev.connection.ProxyAuth import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette @@ -114,6 +115,8 @@ class CoderRestClientTest { object : ToolboxProxySettings { override fun getProxy(): Proxy? = null override fun getProxySelector(): ProxySelector? = null + override fun getProxyAuth(): ProxyAuth? = null + override fun addProxyChangeListener(listener: Runnable) { } @@ -579,6 +582,7 @@ class CoderRestClientTest { } } + override fun getProxyAuth(): ProxyAuth? = null override fun addProxyChangeListener(listener: Runnable) { }