From a6d9332bab6f268b0f872bfc2109494298b8d897 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Fri, 13 Jun 2025 00:21:33 +0300 Subject: [PATCH 1/8] chore: rename misleading method The REST client doesn't do any authentication but rather retrieves the user and build info - the user is already authenticated via the API token. So `authenticate` is misleading here. --- .../kotlin/com/coder/toolbox/CoderRemoteProvider.kt | 2 +- .../kotlin/com/coder/toolbox/cli/CoderCLIManager.kt | 2 +- .../kotlin/com/coder/toolbox/sdk/CoderRestClient.kt | 12 ++++++++---- .../com/coder/toolbox/util/CoderProtocolHandler.kt | 2 +- .../kotlin/com/coder/toolbox/views/ConnectStep.kt | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index d72b130..33d5cec 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -346,7 +346,7 @@ class CoderRemoteProvider( // Show sign in page if we have not configured the client yet. if (client == null) { val errorBuffer = mutableListOf<Throwable>() - // When coming back to the application, authenticate immediately. + // When coming back to the application, initializeSession immediately. val autologin = shouldDoAutoLogin() context.secrets.lastToken.let { lastToken -> context.secrets.lastDeploymentURL.let { lastDeploymentURL -> diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt index 2898179..54b505c 100644 --- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt +++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt @@ -203,7 +203,7 @@ class CoderCLIManager( } /** - * Use the provided token to authenticate the CLI. + * Use the provided token to initializeSession the CLI. */ fun login(token: String): String { logger.info("Storing CLI credentials in $coderConfigPath") diff --git a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt index 9f619bc..365e1ed 100644 --- a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt +++ b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt @@ -131,12 +131,11 @@ open class CoderRestClient( } /** - * Authenticate and load information about the current user and the build - * version. + * Load information about the current user and the build version. * * @throws [APIResponseException]. */ - suspend fun authenticate(): User { + suspend fun initializeSession(): User { me = me() buildVersion = buildInfo().version return me @@ -149,7 +148,12 @@ open class CoderRestClient( suspend fun me(): User { val userResponse = retroRestClient.me() if (!userResponse.isSuccessful) { - throw APIResponseException("authenticate", url, userResponse.code(), userResponse.parseErrorBody(moshi)) + throw APIResponseException( + "initializeSession", + url, + userResponse.code(), + userResponse.parseErrorBody(moshi) + ) } return userResponse.body()!! diff --git a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt index 90f3465..f8359dc 100644 --- a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt +++ b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt @@ -143,7 +143,7 @@ open class CoderProtocolHandler( if (settings.requireTokenAuth) token else null, PluginManager.pluginInfo.version ) - client.authenticate() + client.initializeSession() return client } diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index 58e154e..c096052 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -86,7 +86,7 @@ class ConnectStep( ) // allows interleaving with the back/cancel action yield() - client.authenticate() + client.initializeSession() statusField.textState.update { (context.i18n.ptrl("Checking Coder binary...")) } val cli = ensureCLI(context, client.url, client.buildVersion) // We only need to log in if we are using token-based auth. From 01651f0d01189eb8201c1c89fc0fc17cbf1f30e4 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Fri, 13 Jun 2025 00:22:40 +0300 Subject: [PATCH 2/8] chore: remove misleading message --- src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index c096052..25e9d3b 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -77,7 +77,6 @@ class ConnectStep( signInJob?.cancel() signInJob = context.cs.launch { try { - statusField.textState.update { (context.i18n.ptrl("Authenticating to ${AuthContext.url!!.host}...")) } val client = CoderRestClient( context, AuthContext.url!!, From afdc641795388c424ec0d2c87b7645905782df36 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Fri, 13 Jun 2025 01:41:26 +0300 Subject: [PATCH 3/8] impl: visual text progress during Coder CLI downloading This PR implements a mechanism to provide recurrent stats about the number of the KB and MB of Coder CLI downloaded. --- CHANGELOG.md | 4 ++ .../com/coder/toolbox/cli/CoderCLIManager.kt | 58 +++++++++++++++---- .../toolbox/util/CoderProtocolHandler.kt | 4 +- .../com/coder/toolbox/views/ConnectStep.kt | 11 +++- .../coder/toolbox/cli/CoderCLIManagerTest.kt | 36 ++++++------ 5 files changed, 80 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61780a..62352c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- visual text progress during Coder CLI downloading + ### Changed - the plugin will now remember the SSH connection state for each workspace, and it will try to automatically diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt index 54b505c..21b7bea 100644 --- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt +++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt @@ -32,7 +32,7 @@ import java.net.HttpURLConnection import java.net.URL import java.nio.file.Files import java.nio.file.Path -import java.nio.file.StandardCopyOption +import java.nio.file.StandardOpenOption import java.util.zip.GZIPInputStream import javax.net.ssl.HttpsURLConnection @@ -44,6 +44,8 @@ internal data class Version( @Json(name = "version") val version: String, ) +private const val DOWNLOADING_CODER_CLI = "Downloading Coder CLI..." + /** * Do as much as possible to get a valid, up-to-date CLI. * @@ -60,6 +62,7 @@ fun ensureCLI( context: CoderToolboxContext, deploymentURL: URL, buildVersion: String, + showTextProgress: (String) -> Unit ): CoderCLIManager { val settings = context.settingsStore.readOnly() val cli = CoderCLIManager(deploymentURL, context.logger, settings) @@ -76,9 +79,10 @@ fun ensureCLI( // If downloads are enabled download the new version. if (settings.enableDownloads) { - context.logger.info("Downloading Coder CLI...") + context.logger.info(DOWNLOADING_CODER_CLI) + showTextProgress(DOWNLOADING_CODER_CLI) try { - cli.download() + cli.download(showTextProgress) return cli } catch (e: java.nio.file.AccessDeniedException) { // Might be able to fall back to the data directory. @@ -98,8 +102,9 @@ fun ensureCLI( } if (settings.enableDownloads) { - context.logger.info("Downloading Coder CLI...") - dataCLI.download() + context.logger.info(DOWNLOADING_CODER_CLI) + showTextProgress(DOWNLOADING_CODER_CLI) + dataCLI.download(showTextProgress) return dataCLI } @@ -137,7 +142,7 @@ class CoderCLIManager( /** * Download the CLI from the deployment if necessary. */ - fun download(): Boolean { + fun download(showTextProgress: (String) -> Unit): Boolean { val eTag = getBinaryETag() val conn = remoteBinaryURL.openConnection() as HttpURLConnection if (!settings.headerCommand.isNullOrBlank()) { @@ -163,12 +168,25 @@ class CoderCLIManager( HttpURLConnection.HTTP_OK -> { logger.info("Downloading binary to $localBinaryPath") Files.createDirectories(localBinaryPath.parent) - conn.inputStream.use { - Files.copy( - if (conn.contentEncoding == "gzip") GZIPInputStream(it) else it, - localBinaryPath, - StandardCopyOption.REPLACE_EXISTING, - ) + val outputStream = Files.newOutputStream( + localBinaryPath, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ) + val sourceStream = if (conn.isGzip()) GZIPInputStream(conn.inputStream) else conn.inputStream + + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytesRead: Int + var totalRead = 0L + + sourceStream.use { source -> + outputStream.use { sink -> + while (source.read(buffer).also { bytesRead = it } != -1) { + sink.write(buffer, 0, bytesRead) + totalRead += bytesRead + showTextProgress("Downloaded ${totalRead.toHumanReadableSize()}...") + } + } } if (getOS() != OS.WINDOWS) { localBinaryPath.toFile().setExecutable(true) @@ -178,6 +196,7 @@ class CoderCLIManager( HttpURLConnection.HTTP_NOT_MODIFIED -> { logger.info("Using cached binary at $localBinaryPath") + showTextProgress("Using cached binary") return false } } @@ -190,6 +209,21 @@ class CoderCLIManager( throw ResponseException("Unexpected response from $remoteBinaryURL", conn.responseCode) } + private fun HttpURLConnection.isGzip(): Boolean = this.contentEncoding.equals("gzip", ignoreCase = true) + + fun Long.toHumanReadableSize(): String { + if (this < 1024) return "$this B" + + val kb = this / 1024.0 + if (kb < 1024) return String.format("%.1f KB", kb) + + val mb = kb / 1024.0 + if (mb < 1024) return String.format("%.1f MB", mb) + + val gb = mb / 1024.0 + return String.format("%.1f GB", gb) + } + /** * Return the entity tag for the binary on disk, if any. */ diff --git a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt index f8359dc..cd54305 100644 --- a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt +++ b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt @@ -24,6 +24,7 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI" +private val noOpTextProgress: (String) -> Unit = { _ -> } @Suppress("UnstableApiUsage") open class CoderProtocolHandler( @@ -304,7 +305,8 @@ open class CoderProtocolHandler( val cli = ensureCLI( context, deploymentURL.toURL(), - restClient.buildInfo().version + restClient.buildInfo().version, + noOpTextProgress ) // We only need to log in if we are using token-based auth. diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index 25e9d3b..4c1c81a 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -86,11 +86,16 @@ class ConnectStep( // allows interleaving with the back/cancel action yield() client.initializeSession() - statusField.textState.update { (context.i18n.ptrl("Checking Coder binary...")) } - val cli = ensureCLI(context, client.url, client.buildVersion) + statusField.textState.update { (context.i18n.ptrl("Checking Coder CLI...")) } + val cli = ensureCLI( + context, client.url, + client.buildVersion + ) { progress -> + statusField.textState.update { (context.i18n.pnotr(progress)) } + } // We only need to log in if we are using token-based auth. if (client.token != null) { - statusField.textState.update { (context.i18n.ptrl("Configuring CLI...")) } + statusField.textState.update { (context.i18n.ptrl("Configuring Coder CLI...")) } // allows interleaving with the back/cancel action yield() cli.login(client.token) diff --git a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt index 4603fda..6a4ce12 100644 --- a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt +++ b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt @@ -62,6 +62,8 @@ import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue +private val noOpTextProgress: (String) -> Unit = { _ -> } + internal class CoderCLIManagerTest { private val context = CoderToolboxContext( mockk<ToolboxUi>(), @@ -145,7 +147,7 @@ internal class CoderCLIManagerTest { val ex = assertFailsWith( exceptionClass = ResponseException::class, - block = { ccm.download() }, + block = { ccm.download(noOpTextProgress) }, ) assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code) @@ -200,7 +202,7 @@ internal class CoderCLIManagerTest { assertFailsWith( exceptionClass = AccessDeniedException::class, - block = { ccm.download() }, + block = { ccm.download(noOpTextProgress) }, ) srv.stop(0) @@ -229,11 +231,11 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertTrue(ccm.download()) + assertTrue(ccm.download(noOpTextProgress)) assertDoesNotThrow { ccm.version() } // It should skip the second attempt. - assertFalse(ccm.download()) + assertFalse(ccm.download(noOpTextProgress)) // Make sure login failures propagate. assertFailsWith( @@ -258,11 +260,11 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertEquals(true, ccm.download()) + assertEquals(true, ccm.download(noOpTextProgress)) assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version()) // It should skip the second attempt. - assertEquals(false, ccm.download()) + assertEquals(false, ccm.download(noOpTextProgress)) // Should use the source override. ccm = CoderCLIManager( @@ -278,7 +280,7 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertEquals(true, ccm.download()) + assertEquals(true, ccm.download(noOpTextProgress)) assertContains(ccm.localBinaryPath.toFile().readText(), "0.0.0") srv.stop(0) @@ -326,7 +328,7 @@ internal class CoderCLIManagerTest { assertEquals("cli", ccm.localBinaryPath.toFile().readText()) assertEquals(0, ccm.localBinaryPath.toFile().lastModified()) - assertTrue(ccm.download()) + assertTrue(ccm.download(noOpTextProgress)) assertNotEquals("cli", ccm.localBinaryPath.toFile().readText()) assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified()) @@ -351,8 +353,8 @@ internal class CoderCLIManagerTest { val ccm1 = CoderCLIManager(url1, context.logger, settings) val ccm2 = CoderCLIManager(url2, context.logger, settings) - assertTrue(ccm1.download()) - assertTrue(ccm2.download()) + assertTrue(ccm1.download(noOpTextProgress)) + assertTrue(ccm2.download(noOpTextProgress)) srv1.stop(0) srv2.stop(0) @@ -883,12 +885,12 @@ internal class CoderCLIManagerTest { Result.ERROR -> { assertFailsWith( exceptionClass = AccessDeniedException::class, - block = { ensureCLI(localContext, url, it.buildVersion) }, + block = { ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) }, ) } Result.NONE -> { - val ccm = ensureCLI(localContext, url, it.buildVersion) + val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) assertEquals(settings.binPath(url), ccm.localBinaryPath) assertFailsWith( exceptionClass = ProcessInitException::class, @@ -897,25 +899,25 @@ internal class CoderCLIManagerTest { } Result.DL_BIN -> { - val ccm = ensureCLI(localContext, url, it.buildVersion) + val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) assertEquals(settings.binPath(url), ccm.localBinaryPath) assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version()) } Result.DL_DATA -> { - val ccm = ensureCLI(localContext, url, it.buildVersion) + val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) assertEquals(settings.binPath(url, true), ccm.localBinaryPath) assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version()) } Result.USE_BIN -> { - val ccm = ensureCLI(localContext, url, it.buildVersion) + val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) assertEquals(settings.binPath(url), ccm.localBinaryPath) assertEquals(SemVer.parse(it.version ?: ""), ccm.version()) } Result.USE_DATA -> { - val ccm = ensureCLI(localContext, url, it.buildVersion) + val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) assertEquals(settings.binPath(url, true), ccm.localBinaryPath) assertEquals(SemVer.parse(it.fallbackVersion ?: ""), ccm.version()) } @@ -955,7 +957,7 @@ internal class CoderCLIManagerTest { context.logger, ).readOnly(), ) - assertEquals(true, ccm.download()) + assertEquals(true, ccm.download(noOpTextProgress)) assertEquals(it.second, ccm.features, "version: ${it.first}") srv.stop(0) From c65ac6c31941f98cba7ca43e80a9027f718c000a Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Tue, 17 Jun 2025 22:42:37 +0300 Subject: [PATCH 4/8] fix: delete previous binaries Keeps the previous behavior which was removing the binary before download. --- src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt index 21b7bea..401307d 100644 --- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt +++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt @@ -167,6 +167,7 @@ class CoderCLIManager( when (conn.responseCode) { HttpURLConnection.HTTP_OK -> { logger.info("Downloading binary to $localBinaryPath") + Files.deleteIfExists(localBinaryPath) Files.createDirectories(localBinaryPath.parent) val outputStream = Files.newOutputStream( localBinaryPath, From b95fc4c1421f3d1ce12bcedf52fe361c3aad82fb Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Wed, 18 Jun 2025 23:14:21 +0300 Subject: [PATCH 5/8] impl: report cli name and version Alongside the content size downloaded --- .../com/coder/toolbox/cli/CoderCLIManager.kt | 8 +++---- .../coder/toolbox/cli/CoderCLIManagerTest.kt | 23 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt index 401307d..e4ef501 100644 --- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt +++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt @@ -82,7 +82,7 @@ fun ensureCLI( context.logger.info(DOWNLOADING_CODER_CLI) showTextProgress(DOWNLOADING_CODER_CLI) try { - cli.download(showTextProgress) + cli.download(buildVersion, showTextProgress) return cli } catch (e: java.nio.file.AccessDeniedException) { // Might be able to fall back to the data directory. @@ -104,7 +104,7 @@ fun ensureCLI( if (settings.enableDownloads) { context.logger.info(DOWNLOADING_CODER_CLI) showTextProgress(DOWNLOADING_CODER_CLI) - dataCLI.download(showTextProgress) + dataCLI.download(buildVersion, showTextProgress) return dataCLI } @@ -142,7 +142,7 @@ class CoderCLIManager( /** * Download the CLI from the deployment if necessary. */ - fun download(showTextProgress: (String) -> Unit): Boolean { + fun download(buildVersion: String, showTextProgress: (String) -> Unit): Boolean { val eTag = getBinaryETag() val conn = remoteBinaryURL.openConnection() as HttpURLConnection if (!settings.headerCommand.isNullOrBlank()) { @@ -185,7 +185,7 @@ class CoderCLIManager( while (source.read(buffer).also { bytesRead = it } != -1) { sink.write(buffer, 0, bytesRead) totalRead += bytesRead - showTextProgress("Downloaded ${totalRead.toHumanReadableSize()}...") + showTextProgress("${settings.defaultCliBinaryNameByOsAndArch} $buildVersion - ${totalRead.toHumanReadableSize()} downloaded") } } } diff --git a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt index 6a4ce12..5c37c9e 100644 --- a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt +++ b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt @@ -62,6 +62,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue +private const val VERSION_FOR_PROGRESS_REPORTING = "v2.23.1-devel+de07351b8" private val noOpTextProgress: (String) -> Unit = { _ -> } internal class CoderCLIManagerTest { @@ -147,7 +148,7 @@ internal class CoderCLIManagerTest { val ex = assertFailsWith( exceptionClass = ResponseException::class, - block = { ccm.download(noOpTextProgress) }, + block = { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) }, ) assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code) @@ -202,7 +203,7 @@ internal class CoderCLIManagerTest { assertFailsWith( exceptionClass = AccessDeniedException::class, - block = { ccm.download(noOpTextProgress) }, + block = { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) }, ) srv.stop(0) @@ -231,11 +232,11 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertTrue(ccm.download(noOpTextProgress)) + assertTrue(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) assertDoesNotThrow { ccm.version() } // It should skip the second attempt. - assertFalse(ccm.download(noOpTextProgress)) + assertFalse(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) // Make sure login failures propagate. assertFailsWith( @@ -260,11 +261,11 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertEquals(true, ccm.download(noOpTextProgress)) + assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version()) // It should skip the second attempt. - assertEquals(false, ccm.download(noOpTextProgress)) + assertEquals(false, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) // Should use the source override. ccm = CoderCLIManager( @@ -280,7 +281,7 @@ internal class CoderCLIManagerTest { ).readOnly(), ) - assertEquals(true, ccm.download(noOpTextProgress)) + assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) assertContains(ccm.localBinaryPath.toFile().readText(), "0.0.0") srv.stop(0) @@ -328,7 +329,7 @@ internal class CoderCLIManagerTest { assertEquals("cli", ccm.localBinaryPath.toFile().readText()) assertEquals(0, ccm.localBinaryPath.toFile().lastModified()) - assertTrue(ccm.download(noOpTextProgress)) + assertTrue(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) assertNotEquals("cli", ccm.localBinaryPath.toFile().readText()) assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified()) @@ -353,8 +354,8 @@ internal class CoderCLIManagerTest { val ccm1 = CoderCLIManager(url1, context.logger, settings) val ccm2 = CoderCLIManager(url2, context.logger, settings) - assertTrue(ccm1.download(noOpTextProgress)) - assertTrue(ccm2.download(noOpTextProgress)) + assertTrue(ccm1.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) + assertTrue(ccm2.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) srv1.stop(0) srv2.stop(0) @@ -957,7 +958,7 @@ internal class CoderCLIManagerTest { context.logger, ).readOnly(), ) - assertEquals(true, ccm.download(noOpTextProgress)) + assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress)) assertEquals(it.second, ccm.features, "version: ${it.first}") srv.stop(0) From 9ab03a34d657deed8857aaa0d9903f0aa8c71170 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Wed, 18 Jun 2025 23:16:22 +0300 Subject: [PATCH 6/8] impl: change page title to `Setting up Coder` We are not really doing authentication, instead we download the cli, we initialize the cli with the token, re-generate the ssh config file, initialize a rest client with the deployment url and the token and retrieve basic information about the deployment like the version and the user authenticated by the token. --- src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt | 2 +- src/main/resources/localization/defaultMessages.po | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt index affa96e..ed2922e 100644 --- a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt @@ -25,7 +25,7 @@ class AuthWizardPage( client: CoderRestClient, cli: CoderCLIManager, ) -> Unit, -) : CoderPage(context.i18n.ptrl("Authenticate to Coder"), false) { +) : CoderPage(context.i18n.ptrl("Setting up Coder"), false) { private val shouldAutoLogin = MutableStateFlow(initialAutoLogin) private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = { context.ui.showUiPage(settingsPage) diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 1b04695..e1b3f90 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -143,4 +143,7 @@ msgid "Error encountered while handling Coder URI" msgstr "" msgid "Error encountered during authentication" +msgstr "" + +msgid "Setting up Coder" msgstr "" \ No newline at end of file From 2015addbb091ab0fe5ab8aee7f08a17e69a8509d Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Wed, 18 Jun 2025 23:33:14 +0300 Subject: [PATCH 7/8] chore: refactor classes to reflect we are not actually doing auth --- .../com/coder/toolbox/CoderRemoteProvider.kt | 27 +++++++------- ...zardPage.kt => CoderCliSetupWizardPage.kt} | 36 +++++++++---------- .../com/coder/toolbox/views/ConnectStep.kt | 32 ++++++++--------- .../{SignInStep.kt => DeploymentUrlStep.kt} | 12 +++---- .../com/coder/toolbox/views/TokenStep.kt | 16 ++++----- ...AuthContext.kt => CoderCliSetupContext.kt} | 4 +-- ...rdState.kt => CoderCliSetupWizardState.kt} | 6 ++-- .../resources/localization/defaultMessages.po | 4 +-- 8 files changed, 69 insertions(+), 68 deletions(-) rename src/main/kotlin/com/coder/toolbox/views/{AuthWizardPage.kt => CoderCliSetupWizardPage.kt} (81%) rename src/main/kotlin/com/coder/toolbox/views/{SignInStep.kt => DeploymentUrlStep.kt} (89%) rename src/main/kotlin/com/coder/toolbox/views/state/{AuthContext.kt => CoderCliSetupContext.kt} (89%) rename src/main/kotlin/com/coder/toolbox/views/state/{AuthWizardState.kt => CoderCliSetupWizardState.kt} (82%) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 33d5cec..101cf71 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -9,10 +9,10 @@ import com.coder.toolbox.util.CoderProtocolHandler 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.CoderCliSetupWizardPage import com.coder.toolbox.views.CoderSettingsPage import com.coder.toolbox.views.NewEnvironmentPage -import com.coder.toolbox.views.state.AuthWizardState +import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.coder.toolbox.views.state.WizardStep import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType @@ -242,7 +242,7 @@ class CoderRemoteProvider( environments.value = LoadableState.Value(emptyList()) isInitialized.update { false } client = null - AuthWizardState.resetSteps() + CoderCliSetupWizardState.resetSteps() } override val svgIcon: SvgIcon = @@ -301,7 +301,7 @@ class CoderRemoteProvider( */ override suspend fun handleUri(uri: URI) { linkHandler.handle( - uri, shouldDoAutoLogin(), + uri, shouldDoAutoSetup(), { coderHeaderPage.isBusyCreatingNewEnvironment.update { true @@ -343,17 +343,17 @@ class CoderRemoteProvider( * list. */ override fun getOverrideUiPage(): UiPage? { - // Show sign in page if we have not configured the client yet. + // Show the setup page if we have not configured the client yet. if (client == null) { val errorBuffer = mutableListOf<Throwable>() // When coming back to the application, initializeSession immediately. - val autologin = shouldDoAutoLogin() + val autoSetup = shouldDoAutoSetup() context.secrets.lastToken.let { lastToken -> context.secrets.lastDeploymentURL.let { lastDeploymentURL -> - if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) { + if (autoSetup && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) { try { - AuthWizardState.goToStep(WizardStep.LOGIN) - return AuthWizardPage(context, settingsPage, visibilityState, true, ::onConnect) + CoderCliSetupWizardState.goToStep(WizardStep.CONNECT) + return CoderCliSetupWizardPage(context, settingsPage, visibilityState, true, ::onConnect) } catch (ex: Exception) { errorBuffer.add(ex) } @@ -363,18 +363,19 @@ class CoderRemoteProvider( firstRun = false // Login flow. - val authWizard = AuthWizardPage(context, settingsPage, visibilityState, onConnect = ::onConnect) + val setupWizardPage = + CoderCliSetupWizardPage(context, settingsPage, visibilityState, onConnect = ::onConnect) // We might have navigated here due to a polling error. errorBuffer.forEach { - authWizard.notify("Error encountered", it) + setupWizardPage.notify("Error encountered", it) } // and now reset the errors, otherwise we show it every time on the screen - return authWizard + return setupWizardPage } return null } - private fun shouldDoAutoLogin(): Boolean = firstRun && context.secrets.rememberMe == true + private fun shouldDoAutoSetup(): Boolean = firstRun && context.secrets.rememberMe == true private suspend fun onConnect(client: CoderRestClient, cli: CoderCLIManager) { // Store the URL and token for use next time. diff --git a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt similarity index 81% rename from src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt rename to src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt index ed2922e..c6193da 100644 --- a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt @@ -5,8 +5,8 @@ import com.coder.toolbox.cli.CoderCLIManager import com.coder.toolbox.sdk.CoderRestClient import com.coder.toolbox.sdk.ex.APIResponseException import com.coder.toolbox.util.toURL -import com.coder.toolbox.views.state.AuthContext -import com.coder.toolbox.views.state.AuthWizardState +import com.coder.toolbox.views.state.CoderCliSetupContext +import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.coder.toolbox.views.state.WizardStep import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription @@ -16,26 +16,26 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.util.UUID -class AuthWizardPage( +class CoderCliSetupWizardPage( private val context: CoderToolboxContext, private val settingsPage: CoderSettingsPage, private val visibilityState: MutableStateFlow<ProviderVisibilityState>, - initialAutoLogin: Boolean = false, + initialAutoSetup: Boolean = false, onConnect: suspend ( client: CoderRestClient, cli: CoderCLIManager, ) -> Unit, ) : CoderPage(context.i18n.ptrl("Setting up Coder"), false) { - private val shouldAutoLogin = MutableStateFlow(initialAutoLogin) + private val shouldAutoSetup = MutableStateFlow(initialAutoSetup) private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = { context.ui.showUiPage(settingsPage) }) - private val signInStep = SignInStep(context, this::notify) + private val deploymentUrlStep = DeploymentUrlStep(context, this::notify) private val tokenStep = TokenStep(context) private val connectStep = ConnectStep( context, - shouldAutoLogin, + shouldAutoSetup, this::notify, this::displaySteps, onConnect @@ -50,9 +50,9 @@ class AuthWizardPage( private val errorBuffer = mutableListOf<Throwable>() init { - if (shouldAutoLogin.value) { - AuthContext.url = context.secrets.lastDeploymentURL.toURL() - AuthContext.token = context.secrets.lastToken + if (shouldAutoSetup.value) { + CoderCliSetupContext.url = context.secrets.lastDeploymentURL.toURL() + CoderCliSetupContext.token = context.secrets.lastToken } } @@ -67,22 +67,22 @@ class AuthWizardPage( } private fun displaySteps() { - when (AuthWizardState.currentStep()) { + when (CoderCliSetupWizardState.currentStep()) { WizardStep.URL_REQUEST -> { fields.update { - listOf(signInStep.panel) + listOf(deploymentUrlStep.panel) } actionButtons.update { listOf( - Action(context.i18n.ptrl("Sign In"), closesPage = false, actionBlock = { - if (signInStep.onNext()) { + Action(context.i18n.ptrl("Next"), closesPage = false, actionBlock = { + if (deploymentUrlStep.onNext()) { displaySteps() } }), settingsAction ) } - signInStep.onVisible() + deploymentUrlStep.onVisible() } WizardStep.TOKEN_REQUEST -> { @@ -106,7 +106,7 @@ class AuthWizardPage( tokenStep.onVisible() } - WizardStep.LOGIN -> { + WizardStep.CONNECT -> { fields.update { listOf(connectStep.panel) } @@ -115,7 +115,7 @@ class AuthWizardPage( settingsAction, Action(context.i18n.ptrl("Back"), closesPage = false, actionBlock = { connectStep.onBack() - shouldAutoLogin.update { + shouldAutoSetup.update { false } displaySteps() @@ -150,7 +150,7 @@ class AuthWizardPage( context.cs.launch { context.ui.showSnackbar( UUID.randomUUID().toString(), - context.i18n.ptrl("Error encountered during authentication"), + context.i18n.ptrl("Error encountered while setting up Coder"), context.i18n.pnotr(textError ?: ""), context.i18n.ptrl("Dismiss") ) diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index 4c1c81a..c4973d7 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -5,8 +5,8 @@ import com.coder.toolbox.cli.CoderCLIManager import com.coder.toolbox.cli.ensureCLI import com.coder.toolbox.plugin.PluginManager import com.coder.toolbox.sdk.CoderRestClient -import com.coder.toolbox.views.state.AuthContext -import com.coder.toolbox.views.state.AuthWizardState +import com.coder.toolbox.views.state.CoderCliSetupContext +import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.LabelField import com.jetbrains.toolbox.api.ui.components.RowGroup @@ -50,14 +50,14 @@ class ConnectStep( context.i18n.pnotr("") } - if (AuthContext.isNotReadyForAuth()) { + if (CoderCliSetupContext.isNotReadyForAuth()) { errorField.textState.update { context.i18n.pnotr("URL and token were not properly configured. Please go back and provide a proper URL and token!") } return } - statusField.textState.update { context.i18n.pnotr("Connecting to ${AuthContext.url!!.host}...") } + statusField.textState.update { context.i18n.pnotr("Connecting to ${CoderCliSetupContext.url!!.host}...") } connect() } @@ -65,12 +65,12 @@ class ConnectStep( * Try connecting to Coder with the provided URL and token. */ private fun connect() { - if (!AuthContext.hasUrl()) { + if (!CoderCliSetupContext.hasUrl()) { errorField.textState.update { context.i18n.ptrl("URL is required") } return } - if (!AuthContext.hasToken()) { + if (!CoderCliSetupContext.hasToken()) { errorField.textState.update { context.i18n.ptrl("Token is required") } return } @@ -79,8 +79,8 @@ class ConnectStep( try { val client = CoderRestClient( context, - AuthContext.url!!, - AuthContext.token!!, + CoderCliSetupContext.url!!, + CoderCliSetupContext.token!!, PluginManager.pluginInfo.version, ) // allows interleaving with the back/cancel action @@ -100,20 +100,20 @@ class ConnectStep( yield() cli.login(client.token) } - statusField.textState.update { (context.i18n.ptrl("Successfully configured ${AuthContext.url!!.host}...")) } + statusField.textState.update { (context.i18n.ptrl("Successfully configured ${CoderCliSetupContext.url!!.host}...")) } // allows interleaving with the back/cancel action yield() - AuthContext.reset() - AuthWizardState.resetSteps() + CoderCliSetupContext.reset() + CoderCliSetupWizardState.resetSteps() onConnect(client, cli) } catch (ex: CancellationException) { if (ex.message != USER_HIT_THE_BACK_BUTTON) { - notify("Connection to ${AuthContext.url!!.host} was configured", ex) + notify("Connection to ${CoderCliSetupContext.url!!.host} was configured", ex) onBack() refreshWizard() } } catch (ex: Exception) { - notify("Failed to configure ${AuthContext.url!!.host}", ex) + notify("Failed to configure ${CoderCliSetupContext.url!!.host}", ex) onBack() refreshWizard() } @@ -129,11 +129,11 @@ class ConnectStep( signInJob?.cancel(CancellationException(USER_HIT_THE_BACK_BUTTON)) } finally { if (shouldAutoLogin.value) { - AuthContext.reset() - AuthWizardState.resetSteps() + CoderCliSetupContext.reset() + CoderCliSetupWizardState.resetSteps() context.secrets.rememberMe = false } else { - AuthWizardState.goToPreviousStep() + CoderCliSetupWizardState.goToPreviousStep() } } } diff --git a/src/main/kotlin/com/coder/toolbox/views/SignInStep.kt b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt similarity index 89% rename from src/main/kotlin/com/coder/toolbox/views/SignInStep.kt rename to src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt index 34cdf2d..2b8d5f2 100644 --- a/src/main/kotlin/com/coder/toolbox/views/SignInStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt @@ -2,8 +2,8 @@ package com.coder.toolbox.views import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.util.toURL -import com.coder.toolbox.views.state.AuthContext -import com.coder.toolbox.views.state.AuthWizardState +import com.coder.toolbox.views.state.CoderCliSetupContext +import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.RowGroup import com.jetbrains.toolbox.api.ui.components.TextField @@ -19,7 +19,7 @@ import java.net.URL * Populates with the provided URL, at which point the user can accept or * enter their own. */ -class SignInStep( +class DeploymentUrlStep( private val context: CoderToolboxContext, private val notify: (String, Throwable) -> Unit ) : @@ -32,7 +32,7 @@ class SignInStep( RowGroup.RowField(errorField) ) - override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Sign In") + override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Next") override fun onVisible() { errorField.textState.update { @@ -55,12 +55,12 @@ class SignInStep( url } try { - AuthContext.url = validateRawUrl(url) + CoderCliSetupContext.url = validateRawUrl(url) } catch (e: MalformedURLException) { notify("URL is invalid", e) return false } - AuthWizardState.goToNextStep() + CoderCliSetupWizardState.goToNextStep() return true } diff --git a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt index b02e9ed..da1f8b0 100644 --- a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt @@ -2,8 +2,8 @@ package com.coder.toolbox.views import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.util.withPath -import com.coder.toolbox.views.state.AuthContext -import com.coder.toolbox.views.state.AuthWizardState +import com.coder.toolbox.views.state.CoderCliSetupContext +import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.LinkField import com.jetbrains.toolbox.api.ui.components.RowGroup @@ -37,9 +37,9 @@ class TokenStep( errorField.textState.update { context.i18n.pnotr("") } - if (AuthContext.hasUrl()) { + if (CoderCliSetupContext.hasUrl()) { tokenField.textState.update { - context.secrets.tokenFor(AuthContext.url!!) ?: "" + context.secrets.tokenFor(CoderCliSetupContext.url!!) ?: "" } } else { errorField.textState.update { @@ -48,7 +48,7 @@ class TokenStep( } } (linkField.urlState as MutableStateFlow).update { - AuthContext.url!!.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: "" + CoderCliSetupContext.url!!.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: "" } } @@ -59,12 +59,12 @@ class TokenStep( return false } - AuthContext.token = token - AuthWizardState.goToNextStep() + CoderCliSetupContext.token = token + CoderCliSetupWizardState.goToNextStep() return true } override fun onBack() { - AuthWizardState.goToPreviousStep() + CoderCliSetupWizardState.goToPreviousStep() } } diff --git a/src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt similarity index 89% rename from src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt rename to src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt index 320bd63..8d503b9 100644 --- a/src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt +++ b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt @@ -3,13 +3,13 @@ package com.coder.toolbox.views.state import java.net.URL /** - * Singleton that holds authentication context (URL and token) across multiple + * Singleton that holds Coder CLI setup context (URL and token) across multiple * Toolbox window lifecycle events. * * This ensures that user input (URL and token) is not lost when the Toolbox * window is temporarily closed or recreated. */ -object AuthContext { +object CoderCliSetupContext { /** * The currently entered URL. */ diff --git a/src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt similarity index 82% rename from src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt rename to src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt index c29fbc9..f1efca4 100644 --- a/src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt +++ b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt @@ -2,13 +2,13 @@ package com.coder.toolbox.views.state /** - * A singleton that maintains the state of the authorization wizard across Toolbox window lifecycle events. + * A singleton that maintains the state of the coder setup wizard across Toolbox window lifecycle events. * * This is used to persist the wizard's progress (i.e., current step) between visibility changes * of the Toolbox window. Without this object, closing and reopening the window would reset the wizard * to its initial state by creating a new instance. */ -object AuthWizardState { +object CoderCliSetupWizardState { private var currentStep = WizardStep.URL_REQUEST fun currentStep(): WizardStep = currentStep @@ -31,5 +31,5 @@ object AuthWizardState { } enum class WizardStep { - URL_REQUEST, TOKEN_REQUEST, LOGIN; + URL_REQUEST, TOKEN_REQUEST, CONNECT; } \ No newline at end of file diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index e1b3f90..fe1f90c 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -106,7 +106,7 @@ msgstr "" msgid "Configuring CLI..." msgstr "" -msgid "Sign In" +msgid "Next" msgstr "" msgid "Token" @@ -142,7 +142,7 @@ msgstr "" msgid "Error encountered while handling Coder URI" msgstr "" -msgid "Error encountered during authentication" +msgid "Error encountered while setting up Coder" msgstr "" msgid "Setting up Coder" From 2797652f47679aca98bc30cd4314e78a8ea38cfc Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel <fioan89@gmail.com> Date: Wed, 18 Jun 2025 23:39:45 +0300 Subject: [PATCH 8/8] chore: remove unused field --- src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt | 3 --- src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt | 3 --- src/main/kotlin/com/coder/toolbox/views/TokenStep.kt | 2 -- src/main/kotlin/com/coder/toolbox/views/WizardStep.kt | 2 -- 4 files changed, 10 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index c4973d7..9964d0c 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -7,7 +7,6 @@ import com.coder.toolbox.plugin.PluginManager import com.coder.toolbox.sdk.CoderRestClient import com.coder.toolbox.views.state.CoderCliSetupContext import com.coder.toolbox.views.state.CoderCliSetupWizardState -import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.LabelField import com.jetbrains.toolbox.api.ui.components.RowGroup import com.jetbrains.toolbox.api.ui.components.ValidationErrorField @@ -43,8 +42,6 @@ class ConnectStep( RowGroup.RowField(errorField) ) - override val nextButtonTitle: LocalizableString? = null - override fun onVisible() { errorField.textState.update { context.i18n.pnotr("") diff --git a/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt index 2b8d5f2..aa87b57 100644 --- a/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt @@ -4,7 +4,6 @@ import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.util.toURL import com.coder.toolbox.views.state.CoderCliSetupContext import com.coder.toolbox.views.state.CoderCliSetupWizardState -import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.RowGroup import com.jetbrains.toolbox.api.ui.components.TextField import com.jetbrains.toolbox.api.ui.components.TextType @@ -32,8 +31,6 @@ class DeploymentUrlStep( RowGroup.RowField(errorField) ) - override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Next") - override fun onVisible() { errorField.textState.update { context.i18n.pnotr("") diff --git a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt index da1f8b0..b449f40 100644 --- a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt @@ -4,7 +4,6 @@ import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.util.withPath import com.coder.toolbox.views.state.CoderCliSetupContext import com.coder.toolbox.views.state.CoderCliSetupWizardState -import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.LinkField import com.jetbrains.toolbox.api.ui.components.RowGroup import com.jetbrains.toolbox.api.ui.components.TextField @@ -31,7 +30,6 @@ class TokenStep( RowGroup.RowField(linkField), RowGroup.RowField(errorField) ) - override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Connect") override fun onVisible() { errorField.textState.update { diff --git a/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt b/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt index 6ba3d52..bb19281 100644 --- a/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt @@ -1,11 +1,9 @@ package com.coder.toolbox.views -import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.components.RowGroup interface WizardStep { val panel: RowGroup - val nextButtonTitle: LocalizableString? /** * Callback when step is visible