Skip to content

impl: support for Toolbox 2.7 #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
eb93225
impl: use the new API to set titles on UI pages
fioan89 Jun 23, 2025
a042ffa
impl: update page title instead of creating a new page
fioan89 Jun 23, 2025
3283ab6
impl: use the new API version 1.3.46097
fioan89 Jun 23, 2025
1cbe9ed
fix: jump to main page after coder setup is successful
fioan89 Jun 24, 2025
894d56e
impl: update URI handling page title instead of creating a new page
fioan89 Jun 24, 2025
f3b2be2
impl: use the new constructor parameters work environment status
fioan89 Jun 24, 2025
6d67abe
impl: prioritize workspace status over TBX agent status
fioan89 Jun 24, 2025
0186310
fix: UT's related to proxy configuration
fioan89 Jun 24, 2025
760b717
Merge branch 'main' into impl-support-for-tbx-2.7
fioan89 Jun 26, 2025
baa07df
impl: customize message when loading the workspaces
fioan89 Jun 26, 2025
6356b84
impl: support for proxy basic auth
fioan89 Jun 26, 2025
3a6f869
chore: update documentation related to proxy authentication
fioan89 Jun 27, 2025
a07095d
chore: next version is 0.4.0
fioan89 Jun 27, 2025
cc19d0e
Merge branch 'main' into impl-support-for-tbx-2.7
fioan89 Jul 2, 2025
604409e
chore: upgrade dependencies
fioan89 Jul 2, 2025
bc2aace
chore: change note style
fioan89 Jul 2, 2025
1802cdf
chore: update API dependency
fioan89 Jul 2, 2025
c75289b
fix: URI handling when Toolbox is already opened
fioan89 Jul 2, 2025
5bb8241
chore: remove workaround related to url refreshing
fioan89 Jul 2, 2025
961311f
chore: document URI alias
fioan89 Jul 4, 2025
f5e7926
chore: remove attention note related to the `folder` query param
fioan89 Jul 4, 2025
f7f396c
chore: remove custom progress while establishing the SSH connection
fioan89 Jul 4, 2025
759af74
fix: switch provider screens when handling URI
fioan89 Jul 4, 2025
1a9dc8c
impl: show busy indicator earlier during URI parsing
fioan89 Jul 4, 2025
7047349
Merge branch 'main' into impl-support-for-tbx-2.7
fioan89 Jul 7, 2025
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

### Added

- support for basic authentication for HTTP/HTTPS proxy
- support for Toolbox 2.7 release

### Changed

- improved message while loading the workspace

## 0.3.2 - 2025-06-25

### Changed
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ experience, it’s recommended to ensure the workspace is running prior to initi

## Configuring and Testing workspace polling with HTTP & SOCKS5 Proxy

This section explains how to set up a local proxy (without authentication which is not yet supported) and verify that
This section explains how to set up a local proxy and verify that
the plugin’s REST client works correctly when routed through it.

We’ll use [mitmproxy](https://mitmproxy.org/) for this — it can act as both an HTTP and SOCKS5 proxy with SSL
Expand All @@ -134,6 +134,12 @@ mitmproxy can do HTTP and SOCKS5 proxying. To configure one or the other:
2. Navigate to `Options -> Edit Options`
3. Update the `Mode` field to `regular` in order to activate HTTP/HTTPS or to `socks5`
4. Proxy authentication can be enabled by updating the `proxyauth` to `username:password`
5. Alternatively you can run the following commands:

```bash
mitmweb --ssl-insecure --set stream_large_bodies="10m" --mode regular --proxyauth proxyUsername:proxyPassword
mitmweb --ssl-insecure --set stream_large_bodies="10m" --mode socks5
```

### Configure Proxy in Toolbox

Expand All @@ -144,6 +150,10 @@ mitmproxy can do HTTP and SOCKS5 proxying. To configure one or the other:
5. Before authenticating to the Coder deployment we need to tell the plugin where can we find mitmproxy
certificates. In Coder's Settings page, set the `TLS CA path` to `~/.mitmproxy/mitmproxy-ca-cert.pem`

> ⚠️ Note: Coder Toolbox plugin handles only HTTP/HTTPS proxy authentication.
> SOCKS5 proxy authentication is currently not supported due to limitations
> described in: https://youtrack.jetbrains.com/issue/TBX-14532/Missing-proxy-authentication-settings#focus=Comments-27-12265861.0-0

## Debugging and Reporting issues

Enabling debug logging is essential for diagnosing issues with the Toolbox plugin, especially when SSH
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=0.3.2
version=0.4.0
group=com.coder.toolbox
name=coder-toolbox
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
25 changes: 7 additions & 18 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ class CoderRemoteProvider(
// On the first load, automatically log in if we can.
private var firstRun = true
private val isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
private var coderHeaderPage = NewEnvironmentPage(context, 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 loadingEnvironmentsDescription: LocalizableString = context.i18n.ptrl("Loading workspaces...")
override val environments: MutableStateFlow<LoadableState<List<CoderRemoteEnvironment>>> = MutableStateFlow(
LoadableState.Loading
)
Expand Down Expand Up @@ -167,7 +168,7 @@ class CoderRemoteProvider(
close()
// force auto-login
firstRun = true
goToEnvironmentsPage()
context.envPageManager.showPluginEnvironmentsPage()
break
}
}
Expand Down Expand Up @@ -317,25 +318,13 @@ class CoderRemoteProvider(
close()
// start initialization with the new settings
[email protected] = restClient
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(restClient.url.toString()))
coderHeaderPage.setTitle(context.i18n.pnotr(restClient.url.toString()))

environments.showLoadingMessage()
pollJob = poll(restClient, cli)
}
}

/**
* 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.

Expand Down Expand Up @@ -377,7 +366,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 ?: ""
Expand All @@ -387,9 +376,9 @@ class CoderRemoteProvider(
this.client = client
pollJob?.cancel()
environments.showLoadingMessage()
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(client.url.toString()))
coderHeaderPage.setTitle(context.i18n.pnotr(client.url.toString()))
pollJob = poll(client, cli)
context.refreshMainPage()
context.envPageManager.showPluginEnvironmentsPage()
}

private fun MutableStateFlow<LoadableState<List<CoderRemoteEnvironment>>>.showLoadingMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ 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,
isPriorityShow = true
)
}

Expand Down
27 changes: 15 additions & 12 deletions src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import com.coder.toolbox.util.coderTrustManagers
import com.coder.toolbox.util.getArch
import com.coder.toolbox.util.getHeaders
import com.coder.toolbox.util.getOS
import com.jetbrains.toolbox.api.remoteDev.connection.ProxyAuth
import com.squareup.moshi.Moshi
import okhttp3.Credentials
import okhttp3.OkHttpClient
import retrofit2.Response
import retrofit2.Retrofit
Expand Down Expand Up @@ -78,18 +80,19 @@ open class CoderRestClient(
builder.proxySelector(context.proxySettings.getProxySelector()!!)
}

//TODO - add support for proxy auth. when Toolbox exposes them
// builder.proxyAuthenticator { _, response ->
// if (proxyValues.useAuth && proxyValues.username != null && proxyValues.password != null) {
// val credentials = Credentials.basic(proxyValues.username, proxyValues.password)
// response.request.newBuilder()
// .header("Proxy-Authorization", credentials)
// .build()
// } else {
// null
// }
// }
// }
// Note: This handles only HTTP/HTTPS proxy authentication.
// SOCKS5 proxy authentication is currently not supported due to limitations described in:
// https://youtrack.jetbrains.com/issue/TBX-14532/Missing-proxy-authentication-settings#focus=Comments-27-12265861.0-0
builder.proxyAuthenticator { _, response ->
val proxyAuth = context.proxySettings.getProxyAuth()
if (proxyAuth == null || proxyAuth !is ProxyAuth.Basic) {
return@proxyAuthenticator null
}
val credentials = Credentials.basic(proxyAuth.username, proxyAuth.password)
response.request.newBuilder()
.header("Proxy-Authorization", credentials)
.build()
}

if (token != null) {
builder = builder.addInterceptor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions src/main/kotlin/com/coder/toolbox/views/CoderPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,9 +20,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
* to use the mouse.
*/
abstract class CoderPage(
title: LocalizableString,
private val titleObservable: MutableStateFlow<LocalizableString>,
showIcon: Boolean = true,
) : UiPage(title) {
) : UiPage(titleObservable) {

fun setTitle(title: LocalizableString) {
titleObservable.update {
title
}
}

/**
* Return the icon, if showing one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import kotlinx.coroutines.launch
* I have not been able to test this page.
*/
class CoderSettingsPage(context: CoderToolboxContext, triggerSshConfig: Channel<Boolean>) :
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.
Expand Down
5 changes: 2 additions & 3 deletions src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<List<UiField>> = MutableStateFlow(emptyList())
}
3 changes: 3 additions & 0 deletions src/main/resources/localization/defaultMessages.po
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,7 @@ msgid "Error encountered while setting up Coder"
msgstr ""

msgid "Setting up Coder"
msgstr ""

msgid "Loading workspaces..."
msgstr ""
4 changes: 4 additions & 0 deletions src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
}

Expand Down Expand Up @@ -579,6 +582,7 @@ class CoderRestClientTest {
}
}

override fun getProxyAuth(): ProxyAuth? = null
override fun addProxyChangeListener(listener: Runnable) {
}

Expand Down
Loading