Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5dfbdca

Browse files
authored
impl: remember the ssh connection state (#125)
And try to automatically establish the connections after an expiredtoken was refreshed (by going again through the login sequence)In addition a fix was provided in order to show errors when TBX isvisible after being minimized. Errors encountered while TBX was runningbut the window was not visible were never displayed by TBX. This fixqueues the errors while TBX is minimized, and they will be displayedagain only when visible. This implementation is possible due to anobservablestate object that can provide information about TBX and pluginvisibility.Among other things we also display a more human friendly version for theexceptions raised by the http client during (but not only) workspacepolling.Attention: users will still have to manually launch a new a remote IDEif it was opened while a session expired.-resolves#121
1 parent792dba9 commit5dfbdca

File tree

11 files changed

+159
-51
lines changed

11 files changed

+159
-51
lines changed

‎CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
##Unreleased
44

5+
###Changed
6+
7+
- the plugin will now remember the SSH connection state for each workspace, and it will try to automatically
8+
establish it after an expired token was refreshed.
9+
510
###Fixed
611

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

1016
##0.3.0 - 2025-06-10
1117

‎README.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,12 @@ 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` tab.
104+
>⚠️ Note:`folder` should point to a remote IDEA project that has already been opened and appears in the`Projects`
105+
>tab.
105106
>If the path refers to a project that doesn't exist, the remote IDE won’t start or load it.
106107
107-
>Until[TBX-14952](https://youtrack.jetbrains.com/issue/TBX-14952/) is fixed, it's best to either use a path to a previously opened project or leave it empty.
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.
108110
109111
##Configuring and Testing workspace polling with HTTP & SOCKS5 Proxy
110112

@@ -144,11 +146,11 @@ mitmproxy can do HTTP and SOCKS5 proxying. To configure one or the other:
144146

145147
##Debugging and Reporting issues
146148

147-
Enabling debug logging is essential for diagnosing issues with the Toolbox plugin, especially when SSH
148-
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
149151
and command execution, which is not visible at the default log level.
150152

151-
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
152154
information and help us diagnose and resolve it quickly.
153155

154156
###Enable Debug Logging
@@ -164,46 +166,48 @@ Steps to enable debug logging:
164166

165167
3. In the screen that appears, select_DEBUG_ for the`Log level:` section.
166168

167-
4. Hit the back button at the top.
169+
4. Hit the back button at the top.
168170

169171
There is no need to restart Toolbox, as it will begin logging at the__DEBUG__ level right away.
170172

171173
>⚠️**Attention:** Toolbox does not persist log level configuration between restarts.
172174
173175
####Viewing the Logs
174176

175-
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
176178
via Toolbox App Menu > About > Show log files.
177179

178-
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
179181
Workspaces page in Coder or within the individual workspace view, under the option labeled_Collect logs_.
180182

181183
##Coder Settings
182184

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

186-
###CLI related settings
188+
###CLI related settings
187189

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

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

193-
```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.
194197

195198
```Enable binary directory fallback``` if enabled, falls back to the data directory when the specified binary
196199
directory is not writable.
197200

198-
```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
199202
overridden by the binary directory setting.
200203

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

204207
###TLS settings
205208

206-
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.
207211

208212
```TLS cert path``` path to a client certificate file for TLS authentication with Coder deployment.
209213
The certificate should be in X.509 PEM format.
@@ -215,7 +219,7 @@ The certificate should be in X.509 PEM format.
215219
certs returned by the Coder deployment. The file should be in X.509 PEM format. This option can also be used to verify
216220
proxy certificates.
217221

218-
```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
219223
used to connect to the Coder deployment does not match the hostname in the TLS certificate.
220224

221225
###SSH settings
@@ -232,11 +236,13 @@ rules for matching multiple workspaces.
232236

233237
```SSH network metrics directory``` directory where network information used by the SSH proxy is stored.
234238

235-
```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.
236241

237242
###Saving Changes
238243

239-
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,
240246
may trigger regeneration of SSH configurations.
241247

242248
###Security considerations

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,28 +234,38 @@ class CoderRemoteEnvironment(
234234
* The contents are provided by the SSH view provided by Toolbox, all we
235235
* have to do is provide it a host name.
236236
*/
237-
overridesuspend
238-
fungetContentsView():EnvironmentContentsView=EnvironmentView(
237+
overridesuspendfungetContentsView():EnvironmentContentsView=EnvironmentView(
239238
client.url,
240239
cli,
241240
workspace,
242241
agent
243242
)
244243

245244
/**
246-
* Does nothing. In theory, we could do something like start the workspace
247-
* when you click into the workspace, but you would still need to press
248-
* "connect" anyway before the content is populated so there does not seem
249-
* to be much value.
245+
* Automatically launches the SSH connection if the workspace is visible, is ready and there is no
246+
* connection already established.
250247
*/
251248
overridefunsetVisible(visibilityState:EnvironmentVisibilityState) {
252-
if (wsRawStatus.ready()&& visibilityState.contentsVisible==true&& isConnected.value==false) {
249+
if (visibilityState.contentsVisible) {
250+
startSshConnection()
251+
}
252+
}
253+
254+
/**
255+
* Launches the SSH connection if the workspace is ready and there is no connection already established.
256+
*
257+
* Returns true if the SSH connection was scheduled to start, false otherwise.
258+
*/
259+
funstartSshConnection():Boolean {
260+
if (wsRawStatus.ready()&&!isConnected.value) {
253261
context.cs.launch {
254262
connectionRequest.update {
255263
true
256264
}
257265
}
266+
returntrue
258267
}
268+
returnfalse
259269
}
260270

261271
overridefungetDeleteEnvironmentConfirmationParams():DeleteEnvironmentConfirmationParams? {
@@ -298,6 +308,8 @@ class CoderRemoteEnvironment(
298308
}
299309
}
300310

311+
funisConnected():Boolean= isConnected.value
312+
301313
/**
302314
* An environment is equal if it has the same ID.
303315
*/

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.coder.toolbox
33
importcom.coder.toolbox.browser.browse
44
importcom.coder.toolbox.cli.CoderCLIManager
55
importcom.coder.toolbox.sdk.CoderRestClient
6+
importcom.coder.toolbox.sdk.ex.APIResponseException
67
importcom.coder.toolbox.sdk.v2.models.WorkspaceStatus
78
importcom.coder.toolbox.util.CoderProtocolHandler
89
importcom.coder.toolbox.util.DialogUi
@@ -19,7 +20,6 @@ import com.jetbrains.toolbox.api.core.util.LoadableState
1920
importcom.jetbrains.toolbox.api.localization.LocalizableString
2021
importcom.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState
2122
importcom.jetbrains.toolbox.api.remoteDev.RemoteProvider
22-
importcom.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
2323
importcom.jetbrains.toolbox.api.ui.actions.ActionDelimiter
2424
importcom.jetbrains.toolbox.api.ui.actions.ActionDescription
2525
importcom.jetbrains.toolbox.api.ui.components.UiPage
@@ -65,10 +65,18 @@ class CoderRemoteProvider(
6565
privateval isInitialized:MutableStateFlow<Boolean>=MutableStateFlow(false)
6666
privatevar coderHeaderPage=NewEnvironmentPage(context, context.i18n.pnotr(context.deploymentUrl.toString()))
6767
privateval linkHandler=CoderProtocolHandler(context, dialogUi, isInitialized)
68-
overrideval environments:MutableStateFlow<LoadableState<List<RemoteProviderEnvironment>>>=MutableStateFlow(
68+
69+
overrideval environments:MutableStateFlow<LoadableState<List<CoderRemoteEnvironment>>>=MutableStateFlow(
6970
LoadableState.Loading
7071
)
7172

73+
privateval visibilityState=MutableStateFlow(
74+
ProviderVisibilityState(
75+
applicationVisible=false,
76+
providerVisible=false
77+
)
78+
)
79+
7280
/**
7381
* With the provided client, start polling for workspaces. Every time a new
7482
* workspace is added, reconfigure SSH using the provided cli (including the
@@ -118,7 +126,7 @@ class CoderRemoteProvider(
118126
environments.update {
119127
LoadableState.Value(resolvedEnvironments.toList())
120128
}
121-
if (isInitialized.value==false) {
129+
if (!isInitialized.value) {
122130
context.logger.info("Environments for${client.url} are now initialized")
123131
isInitialized.update {
124132
true
@@ -128,6 +136,21 @@ class CoderRemoteProvider(
128136
clear()
129137
addAll(resolvedEnvironments.sortedBy { it.id })
130138
}
139+
140+
if (WorkspaceConnectionManager.shouldEstablishWorkspaceConnections) {
141+
WorkspaceConnectionManager.allConnected().forEach { wsId->
142+
val env= lastEnvironments.firstOrNull() { it.id== wsId }
143+
if (env!=null&&!env.isConnected()) {
144+
context.logger.info("Establishing lost SSH connection for workspace with id$wsId")
145+
if (!env.startSshConnection()) {
146+
context.logger.info("Can't establish lost SSH connection for workspace with id$wsId")
147+
}
148+
}
149+
}
150+
WorkspaceConnectionManager.reset()
151+
}
152+
153+
WorkspaceConnectionManager.collectStatuses(lastEnvironments)
131154
}catch (_:CancellationException) {
132155
context.logger.debug("${client.url} polling loop canceled")
133156
break
@@ -138,7 +161,12 @@ class CoderRemoteProvider(
138161
client.setupSession()
139162
}else {
140163
context.logger.error(ex,"workspace polling error encountered, trying to auto-login")
164+
if (exisAPIResponseException&& ex.isTokenExpired) {
165+
WorkspaceConnectionManager.shouldEstablishWorkspaceConnections=true
166+
}
141167
close()
168+
// force auto-login
169+
firstRun=true
142170
goToEnvironmentsPage()
143171
break
144172
}
@@ -168,6 +196,7 @@ class CoderRemoteProvider(
168196
// Keep the URL and token to make it easy to log back in, but set
169197
// rememberMe to false so we do not try to automatically log in.
170198
context.secrets.rememberMe=false
199+
WorkspaceConnectionManager.reset()
171200
close()
172201
}
173202

@@ -261,7 +290,11 @@ class CoderRemoteProvider(
261290
* a place to put a timer ("last updated 10 seconds ago" for example)
262291
* and a manual refresh button.
263292
*/
264-
overridefunsetVisible(visibilityState:ProviderVisibilityState) {}
293+
overridefunsetVisible(visibility:ProviderVisibilityState) {
294+
visibilityState.update {
295+
visibility
296+
}
297+
}
265298

266299
/**
267300
* Handle incoming links (like from the dashboard).
@@ -320,7 +353,7 @@ class CoderRemoteProvider(
320353
if (autologin&& lastDeploymentURL.isNotBlank()&& (lastToken.isNotBlank()||!settings.requireTokenAuth)) {
321354
try {
322355
AuthWizardState.goToStep(WizardStep.LOGIN)
323-
returnAuthWizardPage(context, settingsPage,true, ::onConnect)
356+
returnAuthWizardPage(context, settingsPage,visibilityState,true, ::onConnect)
324357
}catch (ex:Exception) {
325358
errorBuffer.add(ex)
326359
}
@@ -330,7 +363,7 @@ class CoderRemoteProvider(
330363
firstRun=false
331364

332365
// Login flow.
333-
val authWizard=AuthWizardPage(context, settingsPage,false, ::onConnect)
366+
val authWizard=AuthWizardPage(context, settingsPage,visibilityState, onConnect= ::onConnect)
334367
// We might have navigated here due to a polling error.
335368
errorBuffer.forEach {
336369
authWizard.notify("Error encountered", it)
@@ -358,7 +391,7 @@ class CoderRemoteProvider(
358391
context.refreshMainPage()
359392
}
360393

361-
privatefun MutableStateFlow<LoadableState<List<RemoteProviderEnvironment>>>.showLoadingMessage() {
394+
privatefun MutableStateFlow<LoadableState<List<CoderRemoteEnvironment>>>.showLoadingMessage() {
362395
this.update {
363396
LoadableState.Loading
364397
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
packagecom.coder.toolbox
2+
3+
object WorkspaceConnectionManager {
4+
privateval workspaceConnectionState= mutableMapOf<String,Boolean>()
5+
6+
var shouldEstablishWorkspaceConnections=false
7+
8+
funallConnected():Set<String>= workspaceConnectionState.filter { it.value }.map { it.key }.toSet()
9+
10+
funcollectStatuses(workspaces:Set<CoderRemoteEnvironment>) {
11+
workspaces.forEach { register(it.id, it.isConnected()) }
12+
}
13+
14+
privatefunregister(wsId:String,isConnected:Boolean) {
15+
workspaceConnectionState[wsId]= isConnected
16+
}
17+
18+
funreset() {
19+
workspaceConnectionState.clear()
20+
shouldEstablishWorkspaceConnections=false
21+
}
22+
}

‎src/main/kotlin/com/coder/toolbox/sdk/ex/APIResponseException.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import java.net.URL
88
classAPIResponseException(action:String,url:URL,code:Int,errorResponse:ApiErrorResponse?) :
99
IOException(formatToPretty(action, url, code, errorResponse)) {
1010

11-
11+
val reason= errorResponse?.detail
1212
val isUnauthorized=HttpURLConnection.HTTP_UNAUTHORIZED== code
13+
val isTokenExpired= isUnauthorized&& reason?.contains("API key expired")==true
1314

1415
companionobject {
1516
privatefunformatToPretty(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp