diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt
index a0d5c160..b1d1e59b 100644
--- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt
+++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt
@@ -77,8 +77,10 @@ import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.URL
import java.net.UnknownHostException
+import javax.net.ssl.SSLHandshakeException
import javax.swing.Icon
import javax.swing.JCheckBox
+import javax.swing.JLabel
import javax.swing.JTable
import javax.swing.JTextField
import javax.swing.ListSelectionModel
@@ -101,6 +103,7 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
private val appPropertiesService: PropertiesComponent = service()
private var tfUrl: JTextField? = null
+ private var tfUrlComment: JLabel? = null
private var cbExistingToken: JCheckBox? = null
private val notificationBanner = NotificationBanner()
@@ -116,7 +119,7 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
minWidth = JBUI.scale(52)
}
rowHeight = 48
- setEmptyState("Disconnected")
+ setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.disconnected"))
setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener {
setNextButtonEnabled(selectedObject?.agentStatus?.ready() == true && selectedObject?.agentOS == OS.LINUX)
@@ -186,6 +189,16 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
background = WelcomeScreenUIManager.getMainAssociatedComponentBackground()
}
}.layout(RowLayout.PARENT_GRID)
+ row {
+ cell() // Empty cells for alignment.
+ tfUrlComment = cell(
+ ComponentPanelBuilder.createCommentComponent(
+ CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.comment",
+ CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text")),
+ false, -1, true
+ )
+ ).resizableColumn().align(AlignX.FILL).component
+ }.layout(RowLayout.PARENT_GRID)
row {
cell() // Empty cell for alignment.
cbExistingToken = checkBox(CoderGatewayBundle.message("gateway.connector.view.login.existing-token.label"))
@@ -410,7 +423,9 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
// Clear out old deployment details.
cliManager = null
poller?.cancel()
- tableOfWorkspaces.setEmptyState("Connecting to $deploymentURL...")
+ tfUrlComment?.foreground = UIUtil.getContextHelpForeground()
+ tfUrlComment?.text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connecting", deploymentURL.host)
+ tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connecting", deploymentURL.host))
tableOfWorkspaces.listTableModel.items = emptyList()
// Authenticate and load in a background process with progress.
@@ -444,44 +459,55 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
triggerWorkspacePolling(false)
cliManager = cli
- tableOfWorkspaces.setEmptyState("Connected to $deploymentURL")
+ tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host))
+ tfUrlComment?.text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host)
} catch (e: Exception) {
- val errorSummary = when (e) {
- is java.nio.file.AccessDeniedException -> "Access denied to ${e.file}"
- is UnknownHostException -> "Unknown host ${e.message}"
- is InvalidExitValueException -> "CLI exited unexpectedly with ${e.exitValue}"
- else -> e.message ?: "No reason was provided"
- }
- var msg = CoderGatewayBundle.message(
- "gateway.connector.view.workspaces.connect.failed",
- deploymentURL,
- errorSummary,
- )
- when (e) {
+ val reason = e.message ?: CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.no-reason")
+ val msg = when (e) {
+ is java.nio.file.AccessDeniedException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.access-denied", e.file)
+ is UnknownHostException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unknown-host", e.message ?: deploymentURL.host)
+ is InvalidExitValueException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unexpected-exit", e.exitValue)
is AuthenticationResponseException -> {
- msg = CoderGatewayBundle.message(
+ CoderGatewayBundle.message(
"gateway.connector.view.workspaces.connect.unauthorized",
deploymentURL,
)
- cs.launch { onAuthFailure?.invoke() }
}
-
is SocketTimeoutException -> {
- msg = CoderGatewayBundle.message(
+ CoderGatewayBundle.message(
"gateway.connector.view.workspaces.connect.timeout",
deploymentURL,
)
}
-
is ResponseException, is ConnectException -> {
- msg = CoderGatewayBundle.message(
+ CoderGatewayBundle.message(
"gateway.connector.view.workspaces.connect.download-failed",
- errorSummary,
+ reason,
+ )
+ }
+ is SSLHandshakeException -> {
+ CoderGatewayBundle.message(
+ "gateway.connector.view.workspaces.connect.ssl-error",
+ deploymentURL.host,
+ reason,
)
}
+ else -> reason
}
- tableOfWorkspaces.setEmptyState(msg)
+ // It would be nice to place messages directly into the table
+ // but it does not support wrapping or markup so place it in the
+ // comment field of the URL input instead.
+ tfUrlComment?.foreground = UIUtil.getErrorForeground()
+ tfUrlComment?.text = msg
+ tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message(
+ "gateway.connector.view.workspaces.connect.failed",
+ deploymentURL.host,
+ ))
logger.error(msg, e)
+
+ if (e is AuthenticationResponseException) {
+ cs.launch { onAuthFailure?.invoke() }
+ }
}
}
}
diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties
index 443af9c1..b2ac2026 100644
--- a/src/main/resources/messages/CoderGatewayBundle.properties
+++ b/src/main/resources/messages/CoderGatewayBundle.properties
@@ -10,6 +10,10 @@ gateway.connector.view.login.token.label=Session Token:
gateway.connector.view.coder.workspaces.header.text=Coder Workspaces
gateway.connector.view.coder.workspaces.comment=Self-hosted developer workspaces in the cloud or on-premises. Coder empowers developers with secure, consistent, and fast developer workspaces.
gateway.connector.view.coder.workspaces.connect.text=Connect
+gateway.connector.view.coder.workspaces.connect.text.comment=Please enter your deployment URL and press "{0}".
+gateway.connector.view.coder.workspaces.connect.text.disconnected=Disconnected
+gateway.connector.view.coder.workspaces.connect.text.connected=Connected to {0}
+gateway.connector.view.coder.workspaces.connect.text.connecting=Connecting to {0}...
gateway.connector.view.coder.workspaces.cli.downloader.dialog.title=Authenticate and setup Coder
gateway.connector.view.coder.workspaces.next.text=Select IDE and project
gateway.connector.view.coder.workspaces.dashboard.text=Open dashboard
@@ -19,12 +23,19 @@ gateway.connector.view.coder.workspaces.stop.text=Stop workspace
gateway.connector.view.coder.workspaces.update.text=Update workspace template
gateway.connector.view.coder.workspaces.create.text=Create workspace
gateway.connector.view.coder.workspaces.unsupported.os.info=Gateway supports only Linux machines. Support for macOS and Windows is planned.
-gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version. Connect to a Coder workspace manually
-gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version. Connect to a Coder workspace manually
+gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version. Connect to a Coder workspace manually
+gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version. Connect to a Coder workspace manually
+gateway.connector.view.workspaces.connect.failed=Connection to {0} failed. See above for details.
+gateway.connector.view.workspaces.connect.no-reason=No reason was provided.
+gateway.connector.view.workspaces.connect.access-denied=Access denied to {0}.
+gateway.connector.view.workspaces.connect.unknown-host=Unknown host {0}.
+gateway.connector.view.workspaces.connect.unexpected-exit=CLI exited unexpectedly with {0}.
gateway.connector.view.workspaces.connect.unauthorized=Token was rejected by {0}; has your token expired?
gateway.connector.view.workspaces.connect.timeout=Unable to connect to {0}; is it up?
gateway.connector.view.workspaces.connect.download-failed=Failed to download Coder CLI: {0}
-gateway.connector.view.workspaces.connect.failed=Failed to connect to {0}: {1}
+gateway.connector.view.workspaces.connect.ssl-error=Connection to {0} failed: {1}. See the \
+ documentation for TLS certificates \
+ for information on how to make your system trust certificates coming from your deployment.
gateway.connector.view.workspaces.token.comment=The last used token is shown above.
gateway.connector.view.workspaces.token.rejected=This token was rejected.
gateway.connector.view.workspaces.token.injected=This token was pulled from your CLI config.