Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ jvm/

# hidden macOS metadata files
.DS_Store
bin/
6 changes: 6 additions & 0 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ class CoderRemoteEnvironment(
context.logger.info("Disconnected from $id")
}

/**
* Update the workspace/agent status to the listeners, if it has changed.
*/
/**
* Update the workspace/agent status to the listeners, if it has changed.
*/
Expand All @@ -265,15 +268,18 @@ class CoderRemoteEnvironment(
}
this.workspace = workspace
this.agent = agent

// workspace&agent status can be different from "environment status"
// which is forced to queued state when a workspace is scheduled to start
updateStatus(WorkspaceAndAgentStatus.from(workspace, agent))
context.connectionMonitoringService.checkConnectionStatus(workspace, agent)

// we have to regenerate the action list in order to force a redraw
// because the actions don't have a state flow on the enabled property
refreshAvailableActions()
}


private fun updateStatus(status: WorkspaceAndAgentStatus) {
environmentStatus = status
state.update {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.coder.toolbox

import com.coder.toolbox.store.CoderSecretsStore
import com.coder.toolbox.store.CoderSettingsStore
import com.coder.toolbox.util.ConnectionMonitoringService
import com.coder.toolbox.util.toURL
import com.jetbrains.toolbox.api.core.diagnostics.Logger
import com.jetbrains.toolbox.api.core.os.LocalDesktopManager
Expand Down Expand Up @@ -30,6 +31,7 @@ data class CoderToolboxContext(
val settingsStore: CoderSettingsStore,
val secrets: CoderSecretsStore,
val proxySettings: ToolboxProxySettings,
val connectionMonitoringService: ConnectionMonitoringService,
) {
/**
* Try to find a URL.
Expand Down
18 changes: 14 additions & 4 deletions src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.coder.toolbox
import com.coder.toolbox.settings.Environment
import com.coder.toolbox.store.CoderSecretsStore
import com.coder.toolbox.store.CoderSettingsStore
import com.coder.toolbox.util.ConnectionMonitoringService
import com.jetbrains.toolbox.api.core.PluginSecretStore
import com.jetbrains.toolbox.api.core.PluginSettingsStore
import com.jetbrains.toolbox.api.core.ServiceLocator
Expand All @@ -26,21 +27,30 @@ import kotlinx.coroutines.CoroutineScope
class CoderToolboxExtension : RemoteDevExtension {
// All services must be passed in here and threaded as necessary.
override fun createRemoteProviderPluginInstance(serviceLocator: ServiceLocator): RemoteProvider {
val ui = serviceLocator.getService<ToolboxUi>()
val logger = serviceLocator.getService(Logger::class.java)
val cs = serviceLocator.getService<CoroutineScope>()
val i18n = serviceLocator.getService<LocalizableStringFactory>()
return CoderRemoteProvider(
CoderToolboxContext(
serviceLocator.getService<ToolboxUi>(),
ui,
serviceLocator.getService<EnvironmentUiPageManager>(),
serviceLocator.getService<EnvironmentStateColorPalette>(),
serviceLocator.getService<RemoteToolsHelper>(),
serviceLocator.getService<ClientHelper>(),
serviceLocator.getService<LocalDesktopManager>(),
serviceLocator.getService<CoroutineScope>(),
cs,
serviceLocator.getService<Logger>(),
serviceLocator.getService<LocalizableStringFactory>(),
i18n,
CoderSettingsStore(serviceLocator.getService<PluginSettingsStore>(), Environment(), logger),
CoderSecretsStore(serviceLocator.getService<PluginSecretStore>()),
serviceLocator.getService<ToolboxProxySettings>()
serviceLocator.getService<ToolboxProxySettings>(),
ConnectionMonitoringService(
cs,
ui,
logger,
i18n
)
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.coder.toolbox.util

import com.coder.toolbox.sdk.v2.models.Workspace
import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
import com.coder.toolbox.sdk.v2.models.WorkspaceAgentLifecycleState
import com.coder.toolbox.sdk.v2.models.WorkspaceAgentStatus
import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
import com.jetbrains.toolbox.api.core.diagnostics.Logger
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
import com.jetbrains.toolbox.api.ui.ToolboxUi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.UUID

class ConnectionMonitoringService(
private val cs: CoroutineScope,
private val ui: ToolboxUi,
private val logger: Logger,
private val i18n: LocalizableStringFactory
) {
private var alreadyNotified = false

fun checkConnectionStatus(ws: Workspace, agent: WorkspaceAgent) {
if (alreadyNotified) {
return
}

val isWorkspaceRunning = ws.latestBuild.status == WorkspaceStatus.RUNNING
val isAgentReady = agent.lifecycleState == WorkspaceAgentLifecycleState.READY
val hasConnectionIssue = agent.status in setOf(
WorkspaceAgentStatus.DISCONNECTED,
WorkspaceAgentStatus.TIMEOUT
)

when {
isWorkspaceRunning && isAgentReady && hasConnectionIssue -> {
cs.launch {
logAndShowWarning(
title = "Unstable connection detected",
warning = "Unstable connection between Coder server and workspace detected. Your active sessions may disconnect"
)
}
alreadyNotified = true
}
}
}


private suspend fun logAndShowWarning(title: String, warning: String) {
logger.warn(warning)
ui.showSnackbar(
UUID.randomUUID().toString(),
i18n.ptrl(title),
i18n.ptrl(warning),
i18n.ptrl("OK")
)
}
}
6 changes: 6 additions & 0 deletions src/main/resources/localization/defaultMessages.po
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,10 @@ msgid "Workspace name"
msgstr ""

msgid "Use app name as main page title instead of URL"
msgstr ""

msgid "Unstable connection detected"
msgstr ""

msgid "Unstable connection between Coder server and workspace detected. Your active sessions may disconnect"
msgstr ""
5 changes: 4 additions & 1 deletion src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.coder.toolbox.store.NETWORK_INFO_DIR
import com.coder.toolbox.store.SSH_CONFIG_OPTIONS
import com.coder.toolbox.store.SSH_CONFIG_PATH
import com.coder.toolbox.store.SSH_LOG_DIR
import com.coder.toolbox.util.ConnectionMonitoringService
import com.coder.toolbox.util.InvalidVersionException
import com.coder.toolbox.util.OS
import com.coder.toolbox.util.SemVer
Expand Down Expand Up @@ -100,7 +101,9 @@ internal class CoderCLIManagerTest {

override fun removeProxyChangeListener(listener: Runnable) {
}
})
},
mockk<ConnectionMonitoringService>()
)

@BeforeTest
fun setup() {
Expand Down
5 changes: 4 additions & 1 deletion src/test/kotlin/com/coder/toolbox/sdk/CoderRestClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.coder.toolbox.store.CoderSecretsStore
import com.coder.toolbox.store.CoderSettingsStore
import com.coder.toolbox.store.TLS_ALTERNATE_HOSTNAME
import com.coder.toolbox.store.TLS_CA_PATH
import com.coder.toolbox.util.ConnectionMonitoringService
import com.coder.toolbox.util.pluginTestSettingsStore
import com.coder.toolbox.util.sslContextFromPEMs
import com.jetbrains.toolbox.api.core.diagnostics.Logger
Expand Down Expand Up @@ -122,7 +123,9 @@ class CoderRestClientTest {

override fun removeProxyChangeListener(listener: Runnable) {
}
})
},
mockk<ConnectionMonitoringService>()
)


data class TestWorkspace(var workspace: Workspace, var resources: List<WorkspaceResource>? = emptyList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ internal class CoderProtocolHandlerTest {
mockk<LocalizableStringFactory>(relaxed = true),
CoderSettingsStore(pluginTestSettingsStore(), Environment(), mockk<Logger>(relaxed = true)),
mockk<CoderSecretsStore>(),
mockk<ToolboxProxySettings>()
mockk<ToolboxProxySettings>(),
mockk<ConnectionMonitoringService>()
)

private val protocolHandler = CoderProtocolHandler(
Expand Down
Loading
Loading