diff --git a/build.gradle.kts b/build.gradle.kts index e0ca598..4c89011 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,6 @@ jvmWrapper { dependencies { compileOnly(libs.bundles.toolbox.plugin.api) implementation(libs.slf4j) - implementation(libs.tinylog) implementation(libs.bundles.serialization) implementation(libs.coroutines.core) implementation(libs.okhttp) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bed6d8a..fc96a97 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,16 @@ [versions] -toolbox-plugin-api = "0.6.2.6.0.37447" -kotlin = "2.0.10" -coroutines = "1.7.3" -serialization = "1.5.0" +toolbox-plugin-api = "0.7.2.6.0.38311" +kotlin = "2.1.0" +coroutines = "1.10.1" +serialization = "1.8.0" okhttp = "4.10.0" slf4j = "2.0.3" -tinylog = "2.7.0" dependency-license-report = "2.5" marketplace-client = "2.0.38" gradle-wrapper = "0.14.0" exec = "1.12" moshi = "1.15.1" -ksp = "2.0.10-1.0.24" +ksp = "2.1.0-1.0.29" retrofit = "2.8.2" [libraries] @@ -24,7 +23,6 @@ serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } -tinylog = {module = "org.tinylog:slf4j-tinylog", version.ref = "tinylog"} exec = { module = "org.zeroturnaround:zt-exec", version.ref = "exec" } moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi"} moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi"} diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 07a9000..c3e5f64 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -16,7 +16,6 @@ import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateConsumer import com.jetbrains.toolbox.api.ui.ToolboxUi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import java.util.concurrent.CompletableFuture /** * Represents an agent and workspace combination. @@ -29,12 +28,12 @@ class CoderRemoteEnvironment( private var workspace: Workspace, private var agent: WorkspaceAgent, private var cs: CoroutineScope, -) : AbstractRemoteProviderEnvironment() { +) : AbstractRemoteProviderEnvironment("${workspace.name}.${agent.name}") { private var status = WorkspaceAndAgentStatus.from(workspace, agent) private val ui: ToolboxUi = serviceLocator.getService(ToolboxUi::class.java) - override fun getId(): String = "${workspace.name}.${agent.name}" - override fun getName(): String = "${workspace.name}.${agent.name}" + + override var name: String = "${workspace.name}.${agent.name}" init { actionsList.add( @@ -105,12 +104,11 @@ class CoderRemoteEnvironment( * The contents are provided by the SSH view provided by Toolbox, all we * have to do is provide it a host name. */ - override fun getContentsView(): CompletableFuture = - CompletableFuture.completedFuture(EnvironmentView(client.url, workspace, agent)) + override suspend fun getContentsView(): EnvironmentContentsView = EnvironmentView(client.url, workspace, agent) /** - * Does nothing. In theory we could do something like start the workspace - * when you click into the workspace but you would still need to press + * Does nothing. In theory, we could do something like start the workspace + * when you click into the workspace, but you would still need to press * "connect" anyway before the content is populated so there does not seem * to be much value. */ @@ -140,12 +138,12 @@ class CoderRemoteEnvironment( if (other == null) return false if (this === other) return true // Note the triple === if (other !is CoderRemoteEnvironment) return false - if (getId() != other.getId()) return false + if (id != other.id) return false return true } /** * Companion to equals, for sets. */ - override fun hashCode(): Int = getId().hashCode() + override fun hashCode(): Int = id.hashCode() } diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 54556f1..a360229 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -1,6 +1,7 @@ package com.coder.toolbox import com.coder.toolbox.cli.CoderCLIManager +import com.coder.toolbox.logger.CoderLoggerFactory import com.coder.toolbox.sdk.CoderRestClient import com.coder.toolbox.sdk.v2.models.WorkspaceStatus import com.coder.toolbox.services.CoderSecretsService @@ -34,7 +35,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import okhttp3.OkHttpClient -import org.slf4j.LoggerFactory import java.net.URI import java.net.URL import kotlin.coroutines.cancellation.CancellationException @@ -43,8 +43,8 @@ import kotlin.time.Duration.Companion.seconds class CoderRemoteProvider( private val serviceLocator: ServiceLocator, private val httpClient: OkHttpClient, -) : RemoteProvider { - private val logger = LoggerFactory.getLogger(javaClass) +) : RemoteProvider("Coder") { + private val logger = CoderLoggerFactory.getLogger(javaClass) private val ui: ToolboxUi = serviceLocator.getService(ToolboxUi::class.java) private val consumer: RemoteEnvironmentConsumer = serviceLocator.getService(RemoteEnvironmentConsumer::class.java) @@ -185,18 +185,18 @@ class CoderRemoteProvider( consumer.consumeEnvironments(emptyList(), true) } - override fun getName(): String = "Coder" - override fun getSvgIcon(): SvgIcon = + override val svgIcon: SvgIcon = SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) - override fun getNoEnvironmentsSvgIcon(): ByteArray = - this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf() + override val noEnvironmentsSvgIcon: SvgIcon? = + SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) /** * TODO@JB: It would be nice to show "loading workspaces" at first but it * appears to be only called once. */ - override fun getNoEnvironmentsDescription(): String = "No workspaces yet" + override val noEnvironmentsDescription: String? = "No workspaces yet" + /** * TODO@JB: Supposedly, setting this to false causes the new environment @@ -205,7 +205,7 @@ class CoderRemoteProvider( * this changes it would be nice to have a new spot to show the * URL. */ - override fun canCreateNewEnvironments(): Boolean = false + override val canCreateNewEnvironments: Boolean = false /** * Just displays the deployment URL at the moment, but we could use this as @@ -216,7 +216,7 @@ class CoderRemoteProvider( /** * We always show a list of environments. */ - override fun isSingleEnvironment(): Boolean = false + override val isSingleEnvironment: Boolean = false /** * TODO: Possibly a good idea to start/stop polling based on visibility, at @@ -241,9 +241,11 @@ class CoderRemoteProvider( */ override fun handleUri(uri: URI) { val params = uri.toQueryParameters() - val name = linkHandler.handle(params) - // TODO@JB: Now what? How do we actually connect this workspace? - logger.debug("External request for {}: {}", name, uri) + coroutineScope.launch { + val name = linkHandler.handle(params) + // TODO@JB: Now what? How do we actually connect this workspace? + logger.debug("External request for {}: {}", name, uri) + } } /** diff --git a/src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt b/src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt index f7e6cd1..7875cf7 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt @@ -1,6 +1,8 @@ package com.coder.toolbox +import com.coder.toolbox.logger.CoderLoggerFactory import com.jetbrains.toolbox.api.core.ServiceLocator +import com.jetbrains.toolbox.api.core.diagnostics.Logger import com.jetbrains.toolbox.api.remoteDev.RemoteDevExtension import com.jetbrains.toolbox.api.remoteDev.RemoteProvider import okhttp3.OkHttpClient @@ -11,6 +13,9 @@ import okhttp3.OkHttpClient class CoderToolboxExtension : RemoteDevExtension { // All services must be passed in here and threaded as necessary. override fun createRemoteProviderPluginInstance(serviceLocator: ServiceLocator): RemoteProvider { + // initialize logger factory + CoderLoggerFactory.tLogger = serviceLocator.getService(Logger::class.java) + return CoderRemoteProvider( serviceLocator, OkHttpClient(), diff --git a/src/main/kotlin/com/coder/toolbox/browser/BrowserUtil.kt b/src/main/kotlin/com/coder/toolbox/browser/BrowserUtil.kt index 57de42f..000263c 100644 --- a/src/main/kotlin/com/coder/toolbox/browser/BrowserUtil.kt +++ b/src/main/kotlin/com/coder/toolbox/browser/BrowserUtil.kt @@ -6,7 +6,7 @@ import org.zeroturnaround.exec.ProcessExecutor class BrowserUtil { companion object { - fun browse(url: String, errorHandler: (BrowserException) -> Unit) { + suspend fun browse(url: String, errorHandler: suspend (BrowserException) -> Unit) { val os = getOS() if (os == null) { errorHandler(BrowserException("Failed to open the URL because we can't detect the OS")) @@ -19,7 +19,7 @@ class BrowserUtil { } } - private fun linuxBrowse(url: String, errorHandler: (BrowserException) -> Unit) { + private suspend fun linuxBrowse(url: String, errorHandler: suspend (BrowserException) -> Unit) { try { if (OS.LINUX.getDesktopEnvironment()?.uppercase()?.contains("GNOME") == true) { exec("gnome-open", url) @@ -36,7 +36,7 @@ class BrowserUtil { } } - private fun macBrowse(url: String, errorHandler: (BrowserException) -> Unit) { + private suspend fun macBrowse(url: String, errorHandler: suspend (BrowserException) -> Unit) { try { exec("open", url) } catch (e: Exception) { @@ -44,7 +44,7 @@ class BrowserUtil { } } - private fun windowsBrowse(url: String, errorHandler: (BrowserException) -> Unit) { + private suspend fun windowsBrowse(url: String, errorHandler: suspend (BrowserException) -> Unit) { try { exec("cmd", "start \"$url\"") } catch (e: Exception) { diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt index e62cd95..707cb5b 100644 --- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt +++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt @@ -3,6 +3,7 @@ package com.coder.toolbox.cli import com.coder.toolbox.cli.ex.MissingVersionException import com.coder.toolbox.cli.ex.ResponseException import com.coder.toolbox.cli.ex.SSHConfigFormatException +import com.coder.toolbox.logger.CoderLoggerFactory import com.coder.toolbox.settings.CoderSettings import com.coder.toolbox.settings.CoderSettingsState import com.coder.toolbox.util.CoderHostnameVerifier @@ -20,7 +21,6 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi -import org.slf4j.LoggerFactory import org.zeroturnaround.exec.ProcessExecutor import java.io.EOFException import java.io.FileInputStream @@ -126,7 +126,7 @@ class CoderCLIManager( // manager to download to the data directory instead. forceDownloadToData: Boolean = false, ) { - private val logger = LoggerFactory.getLogger(javaClass) + private val logger = CoderLoggerFactory.getLogger(javaClass) val remoteBinaryURL: URL = settings.binSource(deploymentURL) val localBinaryPath: Path = settings.binPath(deploymentURL, forceDownloadToData) diff --git a/src/main/kotlin/com/coder/toolbox/logger/CoderLoggerFactory.kt b/src/main/kotlin/com/coder/toolbox/logger/CoderLoggerFactory.kt new file mode 100644 index 0000000..58b7fb4 --- /dev/null +++ b/src/main/kotlin/com/coder/toolbox/logger/CoderLoggerFactory.kt @@ -0,0 +1,12 @@ +package com.coder.toolbox.logger + +import org.slf4j.ILoggerFactory +import org.slf4j.Logger +import com.jetbrains.toolbox.api.core.diagnostics.Logger as ToolboxLogger + +object CoderLoggerFactory : ILoggerFactory { + var tLogger: ToolboxLogger? = null + + fun getLogger(clazz: Class): Logger = getLogger(clazz.name) + override fun getLogger(clazzName: String): Logger = LoggerImpl(clazzName, tLogger) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/toolbox/logger/LoggerImpl.kt b/src/main/kotlin/com/coder/toolbox/logger/LoggerImpl.kt new file mode 100644 index 0000000..a476666 --- /dev/null +++ b/src/main/kotlin/com/coder/toolbox/logger/LoggerImpl.kt @@ -0,0 +1,235 @@ +package com.coder.toolbox.logger + +import org.slf4j.Logger +import org.slf4j.Marker +import com.jetbrains.toolbox.api.core.diagnostics.Logger as ToolboxLogger + +class LoggerImpl(private val clazzName: String, private val tLogger: ToolboxLogger?) : Logger { + override fun getName(): String = clazzName + + override fun isTraceEnabled(): Boolean = true + + override fun trace(message: String) { + tLogger?.trace(message) + } + + override fun trace(message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(message: String, exception: Throwable) { + tLogger?.trace(exception, message) + } + + override fun isTraceEnabled(marker: Marker): Boolean = true + + override fun trace(marker: Marker, message: String) { + tLogger?.trace(message) + } + + override fun trace(marker: Marker, message: String, arg: Any) { + extractThrowable(arg)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(marker: Marker, message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(marker: Marker, message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.trace(it, message) } ?: tLogger?.trace(message) + } + + override fun trace(marker: Marker, message: String, exception: Throwable) { + tLogger?.trace(exception, message) + } + + override fun isDebugEnabled(): Boolean = true + + override fun debug(message: String) { + tLogger?.debug(message) + } + + override fun debug(message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(message: String, exception: Throwable) { + tLogger?.debug(exception, message) + } + + override fun isDebugEnabled(marker: Marker): Boolean = true + + override fun debug(marker: Marker, message: String) { + tLogger?.debug(message) + } + + override fun debug(marker: Marker, message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(marker: Marker, message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(marker: Marker, message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.debug(it, message) } ?: tLogger?.debug(message) + } + + override fun debug(marker: Marker, message: String, exception: Throwable) { + tLogger?.debug(exception, message) + } + + override fun isInfoEnabled(): Boolean = true + + override fun info(message: String) { + tLogger?.info(message) + } + + override fun info(message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(message: String, exception: Throwable) { + tLogger?.info(exception, message) + } + + override fun isInfoEnabled(marker: Marker): Boolean = true + + override fun info(marker: Marker, message: String) { + tLogger?.info(message) + } + + override fun info(marker: Marker, message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(marker: Marker, message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(marker: Marker, message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.info(it, message) } ?: tLogger?.info(message) + } + + override fun info(marker: Marker, message: String, exception: Throwable) { + tLogger?.info(exception, message) + } + + override fun isWarnEnabled(): Boolean = true + + override fun warn(message: String) { + tLogger?.warn(message) + } + + override fun warn(message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(message: String, exception: Throwable) { + tLogger?.warn(exception, message) + } + + override fun isWarnEnabled(marker: Marker): Boolean = true + + override fun warn(marker: Marker, message: String) { + tLogger?.warn(message) + } + + override fun warn(marker: Marker, message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(marker: Marker, message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(marker: Marker, message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.warn(it, message) } ?: tLogger?.warn(message) + } + + override fun warn(marker: Marker, message: String, exception: Throwable) { + tLogger?.warn(exception, message) + } + + override fun isErrorEnabled(): Boolean = true + + override fun error(message: String) { + tLogger?.error(message) + } + + override fun error(message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(message: String, exception: Throwable) { + tLogger?.error(exception, message) + } + + override fun isErrorEnabled(marker: Marker): Boolean = true + + override fun error(marker: Marker, message: String) { + tLogger?.error(message) + } + + override fun error(marker: Marker, message: String, arg: Any?) { + extractThrowable(arg)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(marker: Marker, message: String, firstArg: Any?, secondArg: Any?) { + extractThrowable(firstArg, secondArg)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(marker: Marker, message: String, vararg args: Any?) { + extractThrowable(args)?.let { tLogger?.error(it, message) } ?: tLogger?.error(message) + } + + override fun error(marker: Marker, message: String, exception: Throwable) { + tLogger?.error(exception, message) + } + + companion object { + fun extractThrowable(vararg args: Any?): Throwable? = args.firstOrNull { it is Throwable } as? Throwable + + fun extractThrowable(arg: Any?): Throwable? = arg as? Throwable + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt b/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt index 209fd55..ddcd269 100644 --- a/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt +++ b/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt @@ -1,5 +1,6 @@ package com.coder.toolbox.settings +import com.coder.toolbox.logger.CoderLoggerFactory import com.coder.toolbox.util.Arch import com.coder.toolbox.util.OS import com.coder.toolbox.util.expand @@ -8,7 +9,6 @@ import com.coder.toolbox.util.getOS import com.coder.toolbox.util.safeHost import com.coder.toolbox.util.toURL import com.coder.toolbox.util.withPath -import org.slf4j.LoggerFactory import java.net.URL import java.nio.file.Files import java.nio.file.Path @@ -127,7 +127,7 @@ open class CoderSettings( // Overrides the default binary name (for tests). private val binaryName: String? = null, ) { - private val logger = LoggerFactory.getLogger(javaClass) + private val logger = CoderLoggerFactory.getLogger(javaClass) val tls = CoderTLSSettings(state) diff --git a/src/main/kotlin/com/coder/toolbox/util/Dialogs.kt b/src/main/kotlin/com/coder/toolbox/util/Dialogs.kt index 886ce45..8414e9d 100644 --- a/src/main/kotlin/com/coder/toolbox/util/Dialogs.kt +++ b/src/main/kotlin/com/coder/toolbox/util/Dialogs.kt @@ -16,12 +16,11 @@ class DialogUi( private val settings: CoderSettings, private val ui: ToolboxUi, ) { - fun confirm(title: String, description: String): Boolean { - val f = ui.showOkCancelPopup(title, description, "Yes", "No") - return f.get() + suspend fun confirm(title: String, description: String): Boolean { + return ui.showOkCancelPopup(title, description, "Yes", "No") } - fun ask( + suspend fun ask( title: String, description: String, placeholder: String? = null, @@ -30,12 +29,10 @@ class DialogUi( isError: Boolean = false, link: Pair? = null, ): String? { - val f = ui.showTextInputPopup(title, description, placeholder, TextType.General, "OK", "Cancel") - return f.get() + return ui.showTextInputPopup(title, description, placeholder, TextType.General, "OK", "Cancel") } - private fun openUrl(url: URL) { - // TODO - check this later + private suspend fun openUrl(url: URL) { BrowserUtil.browse(url.toString()) { ui.showErrorInfoPopup(it) } @@ -53,7 +50,7 @@ class DialogUi( * other existing token) unless this is a retry to avoid clobbering the * token that just failed. */ - fun askToken( + suspend fun askToken( url: URL, token: Pair?, useExisting: Boolean, diff --git a/src/main/kotlin/com/coder/toolbox/util/LinkHandler.kt b/src/main/kotlin/com/coder/toolbox/util/LinkHandler.kt index 128c26d..9c6342e 100644 --- a/src/main/kotlin/com/coder/toolbox/util/LinkHandler.kt +++ b/src/main/kotlin/com/coder/toolbox/util/LinkHandler.kt @@ -26,11 +26,12 @@ open class LinkHandler( * Throw if required arguments are not supplied or the workspace is not in a * connectable state. */ - fun handle( + suspend fun handle( parameters: Map, indicator: ((t: String) -> Unit)? = null, ): String { - val deploymentURL = parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") + val deploymentURL = + parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") if (deploymentURL.isNullOrBlank()) { throw MissingArgumentException("Query parameter \"$URL\" is missing") } @@ -44,11 +45,12 @@ open class LinkHandler( val client = try { authenticate(deploymentURL, queryToken) } catch (ex: MissingArgumentException) { - throw MissingArgumentException("Query parameter \"$TOKEN\" is missing") + throw MissingArgumentException("Query parameter \"$TOKEN\" is missing", ex) } // TODO: Show a dropdown and ask for the workspace if missing. - val workspaceName = parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") + val workspaceName = + parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") val workspaces = client.workspaces() val workspace = @@ -60,19 +62,28 @@ open class LinkHandler( WorkspaceStatus.PENDING, WorkspaceStatus.STARTING -> // TODO: Wait for the workspace to turn on. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please wait then try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please wait then try again", ) + WorkspaceStatus.STOPPING, WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED, - -> + -> // TODO: Turn on the workspace. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please start the workspace and try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please start the workspace and try again", ) + WorkspaceStatus.FAILED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; unable to connect", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; unable to connect", ) + WorkspaceStatus.RUNNING -> Unit // All is well } @@ -83,10 +94,16 @@ open class LinkHandler( if (status.pending()) { // TODO: Wait for the agent to be ready. throw IllegalArgumentException( - "The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; please wait then try again", + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; please wait then try again", ) } else if (!status.ready()) { - throw IllegalArgumentException("The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; unable to connect") + throw IllegalArgumentException( + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; unable to connect" + ) } val cli = @@ -120,7 +137,7 @@ open class LinkHandler( * Throw MissingArgumentException if the user aborts. Any network or invalid * token error may also be thrown. */ - private fun authenticate( + private suspend fun authenticate( deploymentURL: String, tryToken: Pair?, error: String? = null, @@ -172,7 +189,7 @@ open class LinkHandler( /** * Check that the link is allowlisted. If not, confirm with the user. */ - private fun verifyDownloadLink(parameters: Map) { + private suspend fun verifyDownloadLink(parameters: Map) { val link = parameters.ideDownloadLink() if (link.isNullOrBlank()) { return // Nothing to verify @@ -233,7 +250,7 @@ private fun isAllowlisted(url: URL): Triple { val allowlisted = domainAllowlist.any { url.host == it || url.host.endsWith(".$it") } && - domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } + domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } val https = url.protocol == "https" && finalUrl.protocol == "https" return Triple(allowlisted, https, linkWithRedirect) } @@ -308,4 +325,4 @@ internal fun getMatchingAgent( return agent } -class MissingArgumentException(message: String) : IllegalArgumentException(message) +class MissingArgumentException(message: String, ex: Throwable? = null) : IllegalArgumentException(message, ex) diff --git a/src/main/kotlin/com/coder/toolbox/util/TLS.kt b/src/main/kotlin/com/coder/toolbox/util/TLS.kt index 9c38350..0d17560 100644 --- a/src/main/kotlin/com/coder/toolbox/util/TLS.kt +++ b/src/main/kotlin/com/coder/toolbox/util/TLS.kt @@ -1,8 +1,8 @@ package com.coder.toolbox.util +import com.coder.toolbox.logger.CoderLoggerFactory import com.coder.toolbox.settings.CoderTLSSettings import okhttp3.internal.tls.OkHostnameVerifier -import org.slf4j.LoggerFactory import java.io.File import java.io.FileInputStream import java.net.InetAddress @@ -182,7 +182,7 @@ class AlternateNameSSLSocketFactory(private val delegate: SSLSocketFactory, priv } class CoderHostnameVerifier(private val alternateName: String) : HostnameVerifier { - private val logger = LoggerFactory.getLogger(javaClass) + private val logger = CoderLoggerFactory.getLogger(javaClass) override fun verify( host: String, diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 59b19d4..f2ce937 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -1,11 +1,11 @@ package com.coder.toolbox.views +import com.coder.toolbox.logger.CoderLoggerFactory import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription import com.jetbrains.toolbox.api.ui.components.UiField import com.jetbrains.toolbox.api.ui.components.UiPage import com.jetbrains.toolbox.api.ui.components.ValidationErrorField -import org.slf4j.LoggerFactory import java.util.function.Consumer /** @@ -19,9 +19,10 @@ import java.util.function.Consumer * to use the mouse. */ abstract class CoderPage( - private val showIcon: Boolean = true, -) : UiPage { - private val logger = LoggerFactory.getLogger(javaClass) + title: String, + showIcon: Boolean = true, +) : UiPage(title) { + private val logger = CoderLoggerFactory.getLogger(javaClass) /** * An error to display on the page. @@ -44,12 +45,10 @@ abstract class CoderPage( * * This seems to only work on the first page. */ - override fun getSvgIcon(): SvgIcon { - return if (showIcon) { - SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) - } else { - SvgIcon(byteArrayOf()) - } + override val svgIcon: SvgIcon? = if (showIcon) { + SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) + } else { + SvgIcon(byteArrayOf()) } /** @@ -87,14 +86,14 @@ abstract class CoderPage( * An action that simply runs the provided callback. */ class Action( - private val label: String, - private val closesPage: Boolean = false, - private val enabled: () -> Boolean = { true }, + description: String, + closesPage: Boolean = false, + enabled: () -> Boolean = { true }, private val actionBlock: () -> Unit, ) : RunnableActionDescription { - override fun getLabel(): String = label - override fun getShouldClosePage(): Boolean = closesPage - override fun isEnabled(): Boolean = enabled() + override val label: String = description + override val shouldClosePage: Boolean = closesPage + override val isEnabled: Boolean = enabled() override fun run() { actionBlock() } diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt index 8b49275..a4d7f19 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt @@ -14,7 +14,7 @@ import com.jetbrains.toolbox.api.ui.components.UiField * TODO@JB: There is no scroll, and our settings do not fit. As a consequence, * I have not been able to test this page. */ -class CoderSettingsPage(private val settings: CoderSettingsService) : CoderPage(false) { +class CoderSettingsPage(private val settings: CoderSettingsService) : CoderPage("Coder Settings", false) { // TODO: Copy over the descriptions, holding until I can test this page. private val binarySourceField = TextField("Binary source", settings.binarySource, TextType.General) private val binaryDirectoryField = TextField("Binary directory", settings.binaryDirectory, TextType.General) @@ -30,7 +30,7 @@ class CoderSettingsPage(private val settings: CoderSettingsService) : CoderPage( TextField("TLS alternate hostname", settings.tlsAlternateHostname, TextType.General) private val disableAutostartField = CheckboxField(settings.disableAutostart, "Disable autostart") - override fun getFields(): MutableList = mutableListOf( + override val fields: MutableList = mutableListOf( binarySourceField, enableDownloadsField, binaryDirectoryField, @@ -44,9 +44,7 @@ class CoderSettingsPage(private val settings: CoderSettingsService) : CoderPage( disableAutostartField, ) - override fun getTitle(): String = "Coder Settings" - - override fun getActionButtons(): MutableList = mutableListOf( + override val actionButtons: MutableList = mutableListOf( Action("Save", closesPage = true) { settings.binarySource = binarySourceField.text.value settings.binaryDirectory = binaryDirectoryField.text.value diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectPage.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectPage.kt index fcf51b1..5270578 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectPage.kt @@ -29,13 +29,12 @@ class ConnectPage( client: CoderRestClient, cli: CoderCLIManager, ) -> Unit, -) : CoderPage() { +) : CoderPage("Connecting to Coder") { private var signInJob: Job? = null private var statusField = LabelField("Connecting to ${url.host}...") - override fun getTitle(): String = "Connecting to Coder" - override fun getDescription(): String = "Please wait while we configure Toolbox for ${url.host}." + override val description: String = "Please wait while we configure Toolbox for ${url.host}." init { connect() @@ -46,7 +45,7 @@ class ConnectPage( * * TODO@JB: This looks kinda sparse. A centered spinner would be welcome. */ - override fun getFields(): MutableList = listOfNotNull( + override val fields: MutableList = listOfNotNull( statusField, errorField, ).toMutableList() @@ -54,7 +53,7 @@ class ConnectPage( /** * Show a retry button on error. */ - override fun getActionButtons(): MutableList = listOfNotNull( + override val actionButtons: MutableList = listOfNotNull( if (errorField != null) Action("Retry", closesPage = false) { retry() } else null, if (errorField != null) Action("Cancel", closesPage = false) { onCancel() } else null, ).toMutableList() diff --git a/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt b/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt index 89a0916..ebee9fe 100644 --- a/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt +++ b/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt @@ -6,7 +6,6 @@ import com.coder.toolbox.sdk.v2.models.WorkspaceAgent import com.jetbrains.toolbox.api.remoteDev.environments.SshEnvironmentContentsView import com.jetbrains.toolbox.api.remoteDev.ssh.SshConnectionInfo import java.net.URL -import java.util.concurrent.CompletableFuture /** * A view for a single environment. It displays the projects and IDEs. @@ -21,20 +20,21 @@ class EnvironmentView( private val workspace: Workspace, private val agent: WorkspaceAgent, ) : SshEnvironmentContentsView { - override fun getConnectionInfo(): CompletableFuture = CompletableFuture.completedFuture(object : SshConnectionInfo { + override suspend fun getConnectionInfo(): SshConnectionInfo = object : SshConnectionInfo { /** * The host name generated by the cli manager for this workspace. */ - override fun getHost() = CoderCLIManager.getHostName(url, "${workspace.name}.${agent.name}") + override val host: String = CoderCLIManager.getHostName(url, "${workspace.name}.${agent.name}") /** * The port is ignored by the Coder proxy command. */ - override fun getPort() = 22 + override val port: Int = 22 /** * The username is ignored by the Coder proxy command. */ - override fun getUserName() = "coder" - }) + override val userName: String? = "coder" + + } } diff --git a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt index f9f6f44..efe4279 100644 --- a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt @@ -10,7 +10,6 @@ import com.jetbrains.toolbox.api.ui.components.UiField * For now we just use this to display the deployment URL since we do not * support creating environments from the plugin. */ -class NewEnvironmentPage(private val deploymentURL: String?) : CoderPage() { - override fun getFields(): MutableList = mutableListOf() - override fun getTitle(): String = deploymentURL ?: "" +class NewEnvironmentPage(private val deploymentURL: String?) : CoderPage(deploymentURL ?: "") { + override val fields: MutableList = mutableListOf() } diff --git a/src/main/kotlin/com/coder/toolbox/views/SignInPage.kt b/src/main/kotlin/com/coder/toolbox/views/SignInPage.kt index b45de84..2fdbf60 100644 --- a/src/main/kotlin/com/coder/toolbox/views/SignInPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/SignInPage.kt @@ -17,18 +17,16 @@ import java.net.URL class SignInPage( private val deploymentURL: Pair?, private val onSignIn: (deploymentURL: URL) -> Unit, -) : CoderPage() { +) : CoderPage("Sign In to Coder") { private val urlField = TextField("Deployment URL", deploymentURL?.first ?: "", TextType.General) - override fun getTitle(): String = "Sign In to Coder" - /** * Fields for this page, displayed in order. * * TODO@JB: Fields are reset when you navigate back. * Ideally they remember what the user entered. */ - override fun getFields(): MutableList = listOfNotNull( + override val fields: MutableList = listOfNotNull( urlField, deploymentURL?.let { LabelField(deploymentURL.second.description("URL")) }, errorField, @@ -37,7 +35,7 @@ class SignInPage( /** * Buttons displayed at the bottom of the page. */ - override fun getActionButtons(): MutableList = mutableListOf( + override val actionButtons: MutableList = mutableListOf( Action("Sign In", closesPage = false) { submit() }, ) diff --git a/src/main/kotlin/com/coder/toolbox/views/TokenPage.kt b/src/main/kotlin/com/coder/toolbox/views/TokenPage.kt index 16f4231..d0da1fc 100644 --- a/src/main/kotlin/com/coder/toolbox/views/TokenPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/TokenPage.kt @@ -17,21 +17,19 @@ import java.net.URL * enter their own. */ class TokenPage( - private val deploymentURL: URL, - private val token: Pair?, + deploymentURL: URL, + token: Pair?, private val onToken: ((token: String) -> Unit), -) : CoderPage() { +) : CoderPage("Enter your token") { private val tokenField = TextField("Token", token?.first ?: "", TextType.General) - override fun getTitle(): String = "Enter your token" - /** * Fields for this page, displayed in order. * * TODO@JB: Fields are reset when you navigate back. * Ideally they remember what the user entered. */ - override fun getFields(): MutableList = listOfNotNull( + override val fields: MutableList = listOfNotNull( tokenField, LabelField( token?.second?.description("token") @@ -45,7 +43,7 @@ class TokenPage( /** * Buttons displayed at the bottom of the page. */ - override fun getActionButtons(): MutableList = mutableListOf( + override val actionButtons: MutableList = mutableListOf( Action("Connect", closesPage = false) { submit(tokenField.text.value) }, ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy