Skip to content

Commit 72cb519

Browse files
committed
Move CLI init into background process
This lets you see the initialization progress so if it hangs due to a timeout for example you are not stuck on the main screen while it completes. Next step is probably to make it cancelable. As a side effect this does change the flow a little; if the token is invalid/expired we now always show the token dialog again. I think that could be a good change anyway though. Because showing the dialog again results in the browser window opening repeatedly I made it only open the first time and added a link so they can open it themselves if they do need a new token. I also show the current token so users can validate the bad token. Also added an error log for when the connection times out.
1 parent 14019eb commit 72cb519

File tree

1 file changed

+66
-58
lines changed

1 file changed

+66
-58
lines changed

src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import java.awt.event.MouseListener
7575
import java.awt.event.MouseMotionListener
7676
import java.awt.font.TextAttribute
7777
import java.awt.font.TextAttribute.UNDERLINE_ON
78+
import java.net.SocketTimeoutException
7879
import javax.swing.Icon
7980
import javax.swing.JTable
8081
import javax.swing.JTextField
@@ -214,12 +215,12 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
214215
tfUrl = textField().resizableColumn().horizontalAlign(HorizontalAlign.FILL).gap(RightGap.SMALL).bindText(localWizardModel::coderURL).applyToComponent {
215216
addActionListener {
216217
poller?.cancel()
217-
askTokenAndOpenSession()
218+
askTokenAndOpenSession(true)
218219
}
219220
}.component
220221
button(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text")) {
221222
poller?.cancel()
222-
askTokenAndOpenSession()
223+
askTokenAndOpenSession(true)
223224
}.applyToComponent {
224225
background = WelcomeScreenUIManager.getMainAssociatedComponentBackground()
225226
}
@@ -316,25 +317,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
316317
localWizardModel.coderURL = url
317318
localWizardModel.token = token
318319
tfUrl?.text = url
319-
320-
poller?.cancel()
321-
try {
322-
coderClient.initClientSession(url.toURL(), token)
323-
loginAndLoadWorkspace(token)
324-
} catch (e: Exception) {
325-
when (e) {
326-
is AuthenticationResponseException -> {
327-
// probably the token is expired
328-
askTokenAndOpenSession()
329-
}
330-
331-
else -> {
332-
logger.warn("An exception was encountered while opening ${localWizardModel.coderURL}. Reason: ${e.message}")
333-
localWizardModel.token = ""
334-
}
335-
}
336-
337-
}
320+
loginAndLoadWorkspace(token, true)
338321
}
339322
}
340323
updateWorkspaceActions()
@@ -372,49 +355,44 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
372355
ActivityTracker.getInstance().inc()
373356
}
374357

375-
private fun askTokenAndOpenSession() {
358+
private fun askTokenAndOpenSession(openBrowser: Boolean) {
376359
// force bindings to be filled
377360
component.apply()
378361

379-
val pastedToken = askToken()
362+
val pastedToken = askToken(openBrowser)
380363
if (pastedToken.isNullOrBlank()) {
381364
return
382365
}
383-
loginAndLoadWorkspace(pastedToken)
366+
// False so that subsequent authentication failures do not keep opening
367+
// the browser as it was already opened earlier.
368+
loginAndLoadWorkspace(pastedToken, false)
384369
}
385370

386-
private fun loginAndLoadWorkspace(token: String) {
387-
try {
388-
coderClient.initClientSession(localWizardModel.coderURL.toURL(), token)
389-
if (!CoderSemVer.isValidVersion(coderClient.buildVersion)) {
390-
notificationBanner.apply {
391-
component.isVisible = true
392-
showWarning(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.invalid.coder.version", coderClient.buildVersion))
393-
}
394-
} else {
395-
val coderVersion = CoderSemVer.parse(coderClient.buildVersion)
396-
if (!coderVersion.isInClosedRange(CoderSupportedVersions.minCompatibleCoderVersion, CoderSupportedVersions.maxCompatibleCoderVersion)) {
397-
notificationBanner.apply {
398-
component.isVisible = true
399-
showWarning(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.unsupported.coder.version", coderClient.buildVersion))
400-
}
401-
}
371+
private fun loginAndLoadWorkspace(token: String, openBrowser: Boolean) {
372+
LifetimeDefinition().launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), canBeCancelled = false, isIndeterminate = true) {
373+
this.indicator.apply {
374+
text = "Authenticating..."
402375
}
403-
} catch (e: AuthenticationResponseException) {
404-
logger.error("Could not authenticate on ${localWizardModel.coderURL}. Reason $e")
405-
return
406-
}
407-
appPropertiesService.setValue(CODER_URL_KEY, localWizardModel.coderURL)
408-
appPropertiesService.setValue(SESSION_TOKEN, token)
409-
val cliManager = CoderCLIManager(localWizardModel.coderURL.toURL(), coderClient.buildVersion)
410376

411-
localWizardModel.apply {
412-
this.token = token
413-
buildVersion = coderClient.buildVersion
414-
localCliPath = cliManager.localCli.toAbsolutePath().toString()
415-
}
377+
try {
378+
authenticate(token)
379+
} catch (e: AuthenticationResponseException) {
380+
logger.error("Unable to authenticate to ${localWizardModel.coderURL}; has your token expired?", e)
381+
askTokenAndOpenSession(openBrowser)
382+
return@launchUnderBackgroundProgress
383+
} catch (e: SocketTimeoutException) {
384+
logger.error("Unable to connect to ${localWizardModel.coderURL}; is it up?", e)
385+
return@launchUnderBackgroundProgress
386+
}
387+
388+
val cliManager = CoderCLIManager(localWizardModel.coderURL.toURL(), coderClient.buildVersion)
389+
390+
localWizardModel.apply {
391+
this.token = token
392+
buildVersion = coderClient.buildVersion
393+
localCliPath = cliManager.localCli.toAbsolutePath().toString()
394+
}
416395

417-
LifetimeDefinition().launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), canBeCancelled = false, isIndeterminate = true) {
418396
this.indicator.apply {
419397
isIndeterminate = false
420398
text = "Retrieving Workspaces..."
@@ -458,15 +436,18 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
458436
}
459437
}
460438

461-
private fun askToken(): String? {
462-
BrowserUtil.browse(localWizardModel.coderURL.toURL().withPath("/login?redirect=%2Fcli-auth"))
439+
private fun askToken(openBrowser: Boolean): String? {
440+
val getTokenUrl = localWizardModel.coderURL.toURL().withPath("/login?redirect=%2Fcli-auth")
441+
if (openBrowser) {
442+
BrowserUtil.browse(getTokenUrl)
443+
}
463444
return invokeAndWaitIfNeeded(ModalityState.any()) {
464445
lateinit var sessionTokenTextField: JBTextField
465446

466447
val panel = panel {
467448
row {
468-
label(CoderGatewayBundle.message("gateway.connector.view.login.token.label"))
469-
sessionTokenTextField = textField().applyToComponent {
449+
browserLink(CoderGatewayBundle.message("gateway.connector.view.login.token.label"), getTokenUrl.toString())
450+
sessionTokenTextField = textField().bindText(localWizardModel::token).applyToComponent {
470451
minimumSize = Dimension(320, -1)
471452
}.component
472453
}
@@ -491,6 +472,33 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
491472
}
492473
}
493474

475+
/**
476+
* Check that the token is valid for the URL in the wizard and throw if not.
477+
* On success store the URL and token and display warning banners if
478+
* versions do not match.
479+
*/
480+
private fun authenticate(token: String) {
481+
coderClient.initClientSession(localWizardModel.coderURL.toURL(), token)
482+
483+
if (!CoderSemVer.isValidVersion(coderClient.buildVersion)) {
484+
notificationBanner.apply {
485+
component.isVisible = true
486+
showWarning(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.invalid.coder.version", coderClient.buildVersion))
487+
}
488+
} else {
489+
val coderVersion = CoderSemVer.parse(coderClient.buildVersion)
490+
if (!coderVersion.isInClosedRange(CoderSupportedVersions.minCompatibleCoderVersion, CoderSupportedVersions.maxCompatibleCoderVersion)) {
491+
notificationBanner.apply {
492+
component.isVisible = true
493+
showWarning(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.unsupported.coder.version", coderClient.buildVersion))
494+
}
495+
}
496+
}
497+
498+
appPropertiesService.setValue(CODER_URL_KEY, localWizardModel.coderURL)
499+
appPropertiesService.setValue(SESSION_TOKEN, token)
500+
}
501+
494502
private suspend fun loadWorkspaces() {
495503
val ws = withContext(Dispatchers.IO) {
496504
val timeBeforeRequestingWorkspaces = System.currentTimeMillis()
@@ -809,4 +817,4 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
809817
companion object {
810818
val logger = Logger.getInstance(CoderWorkspacesStepView::class.java.simpleName)
811819
}
812-
}
820+
}

0 commit comments

Comments
 (0)