Skip to content

Commit 1f7ee6f

Browse files
committed
Merge branch 'main' into fix-auto-connect-when-token-expires
2 parents 52171c2 + 792dba9 commit 1f7ee6f

22 files changed

+548
-377
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,16 @@
99

1010
### Fixed
1111

12+
- `Stop` action is now available for running workspaces that have an out of date template.
13+
- outdated and stopped workspaces are now updated and started when handling URI
1214
- show errors when the Toolbox is visible again after being minimized.
1315

16+
## 0.3.0 - 2025-06-10
17+
18+
### Added
19+
20+
- support for Toolbox 2.6.3 with improved URI handling
21+
1422
## 0.2.3 - 2025-05-26
1523

1624
### Changed

README.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ If `ide_product_code` and `ide_build_number` is missing, Toolbox will only open
101101
page. Coder Toolbox will attempt to start the workspace if it’s not already running; however, for the most reliable
102102
experience, it’s recommended to ensure the workspace is running prior to initiating the connection.
103103

104+
> ⚠️ Note: `folder` should point to a remote IDEA project that has already been opened and appears in the `Projects`
105+
> tab.
106+
> If the path refers to a project that doesn't exist, the remote IDE won’t start or load it.
107+
108+
> Until [TBX-14952](https://youtrack.jetbrains.com/issue/TBX-14952/) is fixed, it's best to either use a path to a
109+
> previously opened project or leave it empty.
110+
104111
## Configuring and Testing workspace polling with HTTP & SOCKS5 Proxy
105112

106113
This section explains how to set up a local proxy (without authentication which is not yet supported) and verify that
@@ -139,11 +146,11 @@ mitmproxy can do HTTP and SOCKS5 proxying. To configure one or the other:
139146

140147
## Debugging and Reporting issues
141148

142-
Enabling debug logging is essential for diagnosing issues with the Toolbox plugin, especially when SSH
143-
connections to the remote environment fail — it provides detailed output that includes SSH negotiation
149+
Enabling debug logging is essential for diagnosing issues with the Toolbox plugin, especially when SSH
150+
connections to the remote environment fail — it provides detailed output that includes SSH negotiation
144151
and command execution, which is not visible at the default log level.
145152

146-
If you encounter a problem with Coder's JetBrains Toolbox plugin, follow the steps below to gather more
153+
If you encounter a problem with Coder's JetBrains Toolbox plugin, follow the steps below to gather more
147154
information and help us diagnose and resolve it quickly.
148155

149156
### Enable Debug Logging
@@ -159,46 +166,48 @@ Steps to enable debug logging:
159166

160167
3. In the screen that appears, select _DEBUG_ for the `Log level:` section.
161168

162-
4. Hit the back button at the top.
169+
4. Hit the back button at the top.
163170

164171
There is no need to restart Toolbox, as it will begin logging at the __DEBUG__ level right away.
165172

166173
> ⚠️ **Attention:** Toolbox does not persist log level configuration between restarts.
167174
168175
#### Viewing the Logs
169176

170-
Once enabled, debug logs will be written to the Toolbox log files. You can access logs directly
177+
Once enabled, debug logs will be written to the Toolbox log files. You can access logs directly
171178
via Toolbox App Menu > About > Show log files.
172179

173-
Alternatively, you can generate a ZIP file using the Workspace action menu, available either on the main
180+
Alternatively, you can generate a ZIP file using the Workspace action menu, available either on the main
174181
Workspaces page in Coder or within the individual workspace view, under the option labeled _Collect logs_.
175182

176183
## Coder Settings
177184

178185
The Coder Settings allows users to control CLI download behavior, SSH configuration, TLS parameters, and data
179186
storage paths. The options can be configured from the plugin's main Workspaces page > deployment action menu > Settings.
180187

181-
### CLI related settings
188+
### CLI related settings
182189

183190
```Binary source``` specifies the source URL or relative path from which the Coder CLI should be downloaded.
184191
If a relative path is provided, it is resolved against the deployment domain.
185192

186193
```Enable downloads``` allows automatic downloading of the CLI if the current version is missing or outdated.
187194

188-
```Binary directory``` specifies the directory where CLI binaries are stored. If omitted, it defaults to the data directory.
195+
```Binary directory``` specifies the directory where CLI binaries are stored. If omitted, it defaults to the data
196+
directory.
189197

190198
```Enable binary directory fallback``` if enabled, falls back to the data directory when the specified binary
191199
directory is not writable.
192200

193-
```Data directory``` directory where plugin-specific data such as session tokens and binaries are stored if not
201+
```Data directory``` directory where plugin-specific data such as session tokens and binaries are stored if not
194202
overridden by the binary directory setting.
195203

196204
```Header command``` command that outputs additional HTTP headers. Each line of output must be in the format key=value.
197205
The environment variable CODER_URL will be available to the command process.
198206

199207
### TLS settings
200208

201-
The following options control the secure communication behavior of the plugin with Coder deployment and its available API.
209+
The following options control the secure communication behavior of the plugin with Coder deployment and its available
210+
API.
202211

203212
```TLS cert path``` path to a client certificate file for TLS authentication with Coder deployment.
204213
The certificate should be in X.509 PEM format.
@@ -210,7 +219,7 @@ The certificate should be in X.509 PEM format.
210219
certs returned by the Coder deployment. The file should be in X.509 PEM format. This option can also be used to verify
211220
proxy certificates.
212221

213-
```TLS alternate hostname``` overrides the hostname used in TLS verification. This is useful when the hostname
222+
```TLS alternate hostname``` overrides the hostname used in TLS verification. This is useful when the hostname
214223
used to connect to the Coder deployment does not match the hostname in the TLS certificate.
215224

216225
### SSH settings
@@ -227,11 +236,13 @@ rules for matching multiple workspaces.
227236

228237
```SSH network metrics directory``` directory where network information used by the SSH proxy is stored.
229238

230-
```Extra SSH options``` additional options appended to the SSH configuration. Can be used to customize the behavior of SSH connections.
239+
```Extra SSH options``` additional options appended to the SSH configuration. Can be used to customize the behavior of
240+
SSH connections.
231241

232242
### Saving Changes
233243

234-
Changes made in the settings page are saved by clicking the Save button. Some changes, like toggling SSH wildcard support,
244+
Changes made in the settings page are saved by clicking the Save button. Some changes, like toggling SSH wildcard
245+
support,
235246
may trigger regeneration of SSH configurations.
236247

237248
### Security considerations

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=0.2.3
1+
version=0.3.0
22
group=com.coder.toolbox
33
name=coder-toolbox

gradle/libs.versions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[versions]
2-
toolbox-plugin-api = "1.0.38881"
3-
kotlin = "2.1.0"
2+
toolbox-plugin-api = "1.1.41749"
3+
kotlin = "2.1.10"
44
coroutines = "1.10.1"
55
serialization = "1.8.0"
66
okhttp = "4.12.0"
@@ -9,11 +9,11 @@ marketplace-client = "2.0.46"
99
gradle-wrapper = "0.14.0"
1010
exec = "1.12"
1111
moshi = "1.15.2"
12-
ksp = "2.1.0-1.0.29"
12+
ksp = "2.1.10-1.0.31"
1313
retrofit = "2.11.0"
1414
changelog = "2.2.1"
1515
gettext = "0.7.0"
16-
plugin-structure = "3.306"
16+
plugin-structure = "3.307"
1717
mockk = "1.14.2"
1818

1919
[libraries]

src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.squareup.moshi.Moshi
2727
import kotlinx.coroutines.Job
2828
import kotlinx.coroutines.delay
2929
import kotlinx.coroutines.flow.MutableStateFlow
30+
import kotlinx.coroutines.flow.StateFlow
3031
import kotlinx.coroutines.flow.update
3132
import kotlinx.coroutines.isActive
3233
import kotlinx.coroutines.launch
@@ -125,16 +126,15 @@ class CoderRemoteEnvironment(
125126
update(workspace.copy(latestBuild = build), agent)
126127
}
127128
})
128-
} else {
129-
actions.add(Action(context.i18n.ptrl("Stop")) {
130-
context.cs.launch {
131-
tryStopSshConnection()
132-
133-
val build = client.stopWorkspace(workspace)
134-
update(workspace.copy(latestBuild = build), agent)
135-
}
136-
})
137129
}
130+
actions.add(Action(context.i18n.ptrl("Stop")) {
131+
context.cs.launch {
132+
tryStopSshConnection()
133+
134+
val build = client.stopWorkspace(workspace)
135+
update(workspace.copy(latestBuild = build), agent)
136+
}
137+
})
138138
}
139139
return actions
140140
}
@@ -203,7 +203,7 @@ class CoderRemoteEnvironment(
203203

204204
private fun File.doesNotExists(): Boolean = !this.exists()
205205

206-
override fun afterDisconnect() {
206+
override fun afterDisconnect(isManual: Boolean) {
207207
context.logger.info("Stopping the network metrics poll job for $id")
208208
pollJob?.cancel()
209209
this.connectionRequest.update { false }
@@ -279,7 +279,7 @@ class CoderRemoteEnvironment(
279279
}
280280
}
281281

282-
override fun onDelete() {
282+
override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow {
283283
context.cs.launch {
284284
try {
285285
client.removeWorkspace(workspace)

src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import com.coder.toolbox.util.DialogUi
1010
import com.coder.toolbox.util.withPath
1111
import com.coder.toolbox.views.Action
1212
import com.coder.toolbox.views.AuthWizardPage
13-
import com.coder.toolbox.views.CoderPage
1413
import com.coder.toolbox.views.CoderSettingsPage
1514
import com.coder.toolbox.views.NewEnvironmentPage
1615
import com.coder.toolbox.views.state.AuthWizardState
@@ -118,7 +117,6 @@ class CoderRemoteProvider(
118117
return@launch
119118
}
120119

121-
122120
// Reconfigure if environments changed.
123121
if (lastEnvironments.size != resolvedEnvironments.size || lastEnvironments != resolvedEnvironments) {
124122
context.logger.info("Workspaces have changed, reconfiguring CLI: $resolvedEnvironments")
@@ -302,12 +300,25 @@ class CoderRemoteProvider(
302300
* Handle incoming links (like from the dashboard).
303301
*/
304302
override suspend fun handleUri(uri: URI) {
305-
linkHandler.handle(uri, shouldDoAutoLogin()) { restClient, cli ->
303+
linkHandler.handle(
304+
uri, shouldDoAutoLogin(),
305+
{
306+
coderHeaderPage.isBusyCreatingNewEnvironment.update {
307+
true
308+
}
309+
},
310+
{
311+
coderHeaderPage.isBusyCreatingNewEnvironment.update {
312+
false
313+
}
314+
}
315+
) { restClient, cli ->
306316
// stop polling and de-initialize resources
307317
close()
308318
// start initialization with the new settings
309319
this@CoderRemoteProvider.client = restClient
310320
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(restClient.url.toString()))
321+
311322
environments.showLoadingMessage()
312323
pollJob = poll(restClient, cli)
313324
}
@@ -365,7 +376,7 @@ class CoderRemoteProvider(
365376

366377
private fun shouldDoAutoLogin(): Boolean = firstRun && context.secrets.rememberMe == true
367378

368-
private fun onConnect(client: CoderRestClient, cli: CoderCLIManager) {
379+
private suspend fun onConnect(client: CoderRestClient, cli: CoderCLIManager) {
369380
// Store the URL and token for use next time.
370381
context.secrets.lastDeploymentURL = client.url.toString()
371382
context.secrets.lastToken = client.token ?: ""
@@ -377,8 +388,7 @@ class CoderRemoteProvider(
377388
environments.showLoadingMessage()
378389
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(client.url.toString()))
379390
pollJob = poll(client, cli)
380-
context.ui.showUiPage(CoderPage.emptyPage(context))
381-
goToEnvironmentsPage()
391+
context.refreshMainPage()
382392
}
383393

384394
private fun MutableStateFlow<LoadableState<List<CoderRemoteEnvironment>>>.showLoadingMessage() {

src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,29 @@ package com.coder.toolbox
33
import com.coder.toolbox.store.CoderSecretsStore
44
import com.coder.toolbox.store.CoderSettingsStore
55
import com.coder.toolbox.util.toURL
6+
import com.coder.toolbox.views.CoderPage
67
import com.jetbrains.toolbox.api.core.diagnostics.Logger
78
import com.jetbrains.toolbox.api.core.os.LocalDesktopManager
89
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
910
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
11+
import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper
1012
import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1113
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1214
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
1315
import com.jetbrains.toolbox.api.ui.ToolboxUi
1416
import kotlinx.coroutines.CoroutineScope
17+
import kotlinx.coroutines.delay
1518
import java.net.URL
19+
import java.util.UUID
20+
import kotlin.time.Duration.Companion.milliseconds
1621

22+
@Suppress("UnstableApiUsage")
1723
data class CoderToolboxContext(
1824
val ui: ToolboxUi,
1925
val envPageManager: EnvironmentUiPageManager,
2026
val envStateColorPalette: EnvironmentStateColorPalette,
21-
val ideOrchestrator: ClientHelper,
27+
val remoteIdeOrchestrator: RemoteToolsHelper,
28+
val jbClientOrchestrator: ClientHelper,
2229
val desktop: LocalDesktopManager,
2330
val cs: CoroutineScope,
2431
val logger: Logger,
@@ -44,4 +51,66 @@ data class CoderToolboxContext(
4451
}
4552
return this.settingsStore.defaultURL.toURL()
4653
}
54+
55+
suspend fun logAndShowError(title: String, error: String) {
56+
logger.error(error)
57+
ui.showSnackbar(
58+
UUID.randomUUID().toString(),
59+
i18n.pnotr(title),
60+
i18n.pnotr(error),
61+
i18n.ptrl("OK")
62+
)
63+
}
64+
65+
suspend fun logAndShowError(title: String, error: String, exception: Exception) {
66+
logger.error(exception, error)
67+
ui.showSnackbar(
68+
UUID.randomUUID().toString(),
69+
i18n.pnotr(title),
70+
i18n.pnotr(error),
71+
i18n.ptrl("OK")
72+
)
73+
}
74+
75+
suspend fun logAndShowWarning(title: String, warning: String) {
76+
logger.warn(warning)
77+
ui.showSnackbar(
78+
UUID.randomUUID().toString(),
79+
i18n.pnotr(title),
80+
i18n.pnotr(warning),
81+
i18n.ptrl("OK")
82+
)
83+
}
84+
85+
suspend fun logAndShowInfo(title: String, info: String) {
86+
logger.info(info)
87+
ui.showSnackbar(
88+
UUID.randomUUID().toString(),
89+
i18n.pnotr(title),
90+
i18n.pnotr(info),
91+
i18n.ptrl("OK")
92+
)
93+
}
94+
95+
/**
96+
* Forces the title bar on the main page to be refreshed
97+
*/
98+
suspend fun refreshMainPage() {
99+
// the url/title on the main page is only refreshed if
100+
// we're navigating to the main env page from another page.
101+
// If TBX is already on the main page the title is not refreshed
102+
// hence we force a navigation from a blank page.
103+
ui.showUiPage(CoderPage.emptyPage(this))
104+
105+
106+
// Toolbox uses an internal shared flow with a buffer of 4 items and a DROP_OLDEST strategy.
107+
// Both showUiPage and showPluginEnvironmentsPage send events to this flow.
108+
// If we emit two events back-to-back, the first one often gets dropped and only the second is shown.
109+
// To reduce this risk, we add a small delay to let the UI coroutine process the first event.
110+
// Simply yielding the coroutine isn't reliable, especially right after Toolbox starts via URI handling.
111+
// Based on my testing, a 5–10 ms delay is enough to ensure the blank page is processed,
112+
// while still short enough to be invisible to users.
113+
delay(10.milliseconds)
114+
envPageManager.showPluginEnvironmentsPage()
115+
}
47116
}

src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
1313
import com.jetbrains.toolbox.api.remoteDev.RemoteDevExtension
1414
import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
1515
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
16+
import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper
1617
import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1718
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1819
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
@@ -31,6 +32,7 @@ class CoderToolboxExtension : RemoteDevExtension {
3132
serviceLocator.getService<ToolboxUi>(),
3233
serviceLocator.getService<EnvironmentUiPageManager>(),
3334
serviceLocator.getService<EnvironmentStateColorPalette>(),
35+
serviceLocator.getService<RemoteToolsHelper>(),
3436
serviceLocator.getService<ClientHelper>(),
3537
serviceLocator.getService<LocalDesktopManager>(),
3638
serviceLocator.getService<CoroutineScope>(),

0 commit comments

Comments
 (0)