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

fix: update&start outdated workspaces#128

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fioan89 merged 5 commits intomainfromfix-outdated-workspaces
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
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
1 change: 1 addition & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@
### Fixed

- `Stop` action is now available for running workspaces that have an out of date template.
- outdated and stopped workspaces are now updated and started when handling URI

## 0.3.0 - 2025-06-10

Expand Down
22 changes: 16 additions & 6 deletionssrc/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,6 @@ import com.coder.toolbox.util.DialogUi
import com.coder.toolbox.util.withPath
import com.coder.toolbox.views.Action
import com.coder.toolbox.views.AuthWizardPage
import com.coder.toolbox.views.CoderPage
import com.coder.toolbox.views.CoderSettingsPage
import com.coder.toolbox.views.NewEnvironmentPage
import com.coder.toolbox.views.state.AuthWizardState
Expand DownExpand Up@@ -110,7 +109,6 @@ class CoderRemoteProvider(
return@launch
}


// Reconfigure if environments changed.
if (lastEnvironments.size != resolvedEnvironments.size || lastEnvironments != resolvedEnvironments) {
context.logger.info("Workspaces have changed, reconfiguring CLI: $resolvedEnvironments")
Expand DownExpand Up@@ -269,12 +267,25 @@ class CoderRemoteProvider(
* Handle incoming links (like from the dashboard).
*/
override suspend fun handleUri(uri: URI) {
linkHandler.handle(uri, shouldDoAutoLogin()) { restClient, cli ->
linkHandler.handle(
uri, shouldDoAutoLogin(),
{
coderHeaderPage.isBusyCreatingNewEnvironment.update {
true
}
},
{
coderHeaderPage.isBusyCreatingNewEnvironment.update {
false
}
}
) { restClient, cli ->
// stop polling and de-initialize resources
close()
// start initialization with the new settings
this@CoderRemoteProvider.client = restClient
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(restClient.url.toString()))

environments.showLoadingMessage()
pollJob = poll(restClient, cli)
}
Expand DownExpand Up@@ -332,7 +343,7 @@ class CoderRemoteProvider(

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

private fun onConnect(client: CoderRestClient, cli: CoderCLIManager) {
privatesuspendfun 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@@ -344,8 +355,7 @@ class CoderRemoteProvider(
environments.showLoadingMessage()
coderHeaderPage = NewEnvironmentPage(context, context.i18n.pnotr(client.url.toString()))
pollJob = poll(client, cli)
context.ui.showUiPage(CoderPage.emptyPage(context))
goToEnvironmentsPage()
context.refreshMainPage()
}

private fun MutableStateFlow<LoadableState<List<RemoteProviderEnvironment>>>.showLoadingMessage() {
Expand Down
25 changes: 25 additions & 0 deletionssrc/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ package com.coder.toolbox
import com.coder.toolbox.store.CoderSecretsStore
import com.coder.toolbox.store.CoderSettingsStore
import com.coder.toolbox.util.toURL
import com.coder.toolbox.views.CoderPage
import com.jetbrains.toolbox.api.core.diagnostics.Logger
import com.jetbrains.toolbox.api.core.os.LocalDesktopManager
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
Expand All@@ -13,8 +14,10 @@ import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
import com.jetbrains.toolbox.api.ui.ToolboxUi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import java.net.URL
import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds

@Suppress("UnstableApiUsage")
data class CoderToolboxContext(
Expand DownExpand Up@@ -88,4 +91,26 @@ data class CoderToolboxContext(
i18n.ptrl("OK")
)
}

/**
* Forces the title bar on the main page to be refreshed
*/
suspend fun refreshMainPage() {
// the url/title on the main page is only refreshed if
// we're navigating to the main env page from another page.
// If TBX is already on the main page the title is not refreshed
// hence we force a navigation from a blank page.
ui.showUiPage(CoderPage.emptyPage(this))


// Toolbox uses an internal shared flow with a buffer of 4 items and a DROP_OLDEST strategy.
// Both showUiPage and showPluginEnvironmentsPage send events to this flow.
// If we emit two events back-to-back, the first one often gets dropped and only the second is shown.
// To reduce this risk, we add a small delay to let the UI coroutine process the first event.
// Simply yielding the coroutine isn't reliable, especially right after Toolbox starts via URI handling.
// Based on my testing, a 5–10 ms delay is enough to ensure the blank page is processed,
// while still short enough to be invisible to users.
delay(10.milliseconds)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could you add a comment for why we need to do a delay here for the uneducated? 😄

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thank you for the feedback, I've addressed it in the code, but here is a summary:

From testing and decompiling the Toolbox code, it seems that TBX uses an internal shared flow with a buffer of 4 items and a DROP_OLDEST strategy. Both showUiPage and showPluginEnvironmentsPage send events to this flow. If we emit two events back-to-back, the first one often gets dropped and only the second is shown. To reduce this risk, we add a small delay to let the UI coroutine process the first event. Simply yielding the coroutine isn't reliable, especially right after Toolbox starts via URI handling. Based on my testing, a 5–10 ms delay is enough to ensure the blank page is processed, while still short enough to be invisible to users.
It goes without saying that the API is limiting and we don't have the callbacks or the hooks necessary to observer when a page goes active. That would be much cleaner.

In fact the whole thing is necessary because the url/title on the main page is only refreshed if we're navigating to the main env page from another page. If TBX is already on the main page the title is not refreshed hence we force a navigation from a blank page. We've raised a ticket in the past, once that is fixed the blank page workaround will no longer be needed and as a consequence the delay will be dropped as well.

envPageManager.showPluginEnvironmentsPage()
}
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ open class CoderProtocolHandler(
suspend fun handle(
uri: URI,
shouldWaitForAutoLogin: Boolean,
markAsBusy: () -> Unit,
unmarkAsBusy: () -> Unit,
reInitialize: suspend (CoderRestClient, CoderCLIManager) -> Unit
) {
val params = uri.toQueryParameters()
Expand All@@ -62,16 +64,27 @@ open class CoderProtocolHandler(
val workspaceName = resolveWorkspaceName(params) ?: return
val restClient = buildRestClient(deploymentURL, token) ?: return
val workspace = restClient.workspaces().matchName(workspaceName, deploymentURL) ?: return
if (!prepareWorkspace(workspace, restClient, workspaceName, deploymentURL)) return

// we resolve the agent after the workspace is started otherwise we can get misleading
// errors like: no agent available while workspace is starting or stopping
val agent = resolveAgent(params, workspace) ?: return
if (!ensureAgentIsReady(workspace, agent)) return

val cli = configureCli(deploymentURL, restClient)
reInitialize(restClient, cli)

var agent: WorkspaceAgent
try {
markAsBusy()
context.refreshMainPage()
if (!prepareWorkspace(workspace, restClient, workspaceName, deploymentURL)) return
// we resolve the agent after the workspace is started otherwise we can get misleading
// errors like: no agent available while workspace is starting or stopping
// we also need to retrieve the workspace again to have the latest resources (ex: agent)
// attached to the workspace.
agent = resolveAgent(
params,
restClient.workspace(workspace.id)
) ?: return
if (!ensureAgentIsReady(workspace, agent)) return
} finally {
unmarkAsBusy()
}
val environmentId = "${workspace.name}.${agent.name}"
context.showEnvironmentPage(environmentId)

Expand DownExpand Up@@ -173,7 +186,11 @@ open class CoderProtocolHandler(
}

try {
restClient.startWorkspace(workspace)
if (workspace.outdated) {
restClient.updateWorkspace(workspace)
} else {
restClient.startWorkspace(workspace)
}
} catch (e: Exception) {
context.logAndShowError(
CAN_T_HANDLE_URI_TITLE,
Expand DownExpand Up@@ -428,6 +445,7 @@ open class CoderProtocolHandler(
}
}


private fun CoderToolboxContext.popupPluginMainPage() {
this.ui.showWindow()
this.envPageManager.showPluginEnvironmentsPage(true)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,7 @@ class AuthWizardPage(
private val context: CoderToolboxContext,
private val settingsPage: CoderSettingsPage,
initialAutoLogin: Boolean = false,
onConnect: (
onConnect:suspend(
client: CoderRestClient,
cli: CoderCLIManager,
) -> Unit,
Expand Down
3 changes: 3 additions & 0 deletionssrc/main/kotlin/com/coder/toolbox/views/CoderPage.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@ import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType
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.launch
import java.util.UUID

Expand DownExpand Up@@ -39,6 +40,8 @@ abstract class CoderPage(
SvgIcon(byteArrayOf(), type = IconType.Masked)
}

override val isBusyCreatingNewEnvironment: MutableStateFlow<Boolean> = MutableStateFlow(false)

/**
* Show an error as a popup on this page.
*/
Expand Down
2 changes: 1 addition & 1 deletionsrc/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ class ConnectStep(
private val shouldAutoLogin: StateFlow<Boolean>,
private val notify: (String, Throwable) -> Unit,
private val refreshWizard: () -> Unit,
private val onConnect: (
private val onConnect:suspend(
client: CoderRestClient,
cli: CoderCLIManager,
) -> Unit,
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp