@@ -46,13 +46,7 @@ import com.intellij.ui.RelativeFont
4646import com.intellij.ui.ToolbarDecorator
4747import com.intellij.ui.components.JBTextField
4848import com.intellij.ui.components.dialog
49- import com.intellij.ui.dsl.builder.AlignX
50- import com.intellij.ui.dsl.builder.AlignY
51- import com.intellij.ui.dsl.builder.BottomGap
52- import com.intellij.ui.dsl.builder.RightGap
53- import com.intellij.ui.dsl.builder.TopGap
54- import com.intellij.ui.dsl.builder.bindText
55- import com.intellij.ui.dsl.builder.panel
49+ import com.intellij.ui.dsl.builder.*
5650import com.intellij.ui.table.TableView
5751import com.intellij.util.ui.ColumnInfo
5852import com.intellij.util.ui.JBFont
@@ -76,8 +70,12 @@ import java.awt.event.MouseListener
7670import java.awt.event.MouseMotionListener
7771import java.awt.font.TextAttribute
7872import java.awt.font.TextAttribute.UNDERLINE_ON
73+ import java.nio.file.Files
74+ import java.nio.file.Path
75+ import java.nio.file.Paths
7976import java.net.SocketTimeoutException
8077import javax.swing.Icon
78+ import javax.swing.JCheckBox
8179import javax.swing.JTable
8280import javax.swing.JTextField
8381import javax.swing.ListSelectionModel
@@ -100,6 +98,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
10098 private val appPropertiesService: PropertiesComponent = service()
10199
102100 private var tfUrl: JTextField ? = null
101+ private var cbExistingToken: JCheckBox ? = null
103102 private var listTableModelOfWorkspaces = ListTableModel <WorkspaceAgentModel >(
104103 WorkspaceIconColumnInfo (" " ),
105104 WorkspaceNameColumnInfo (" Name" ),
@@ -201,13 +200,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
201200 font = JBFont .h3().asBold()
202201 icon = CoderIcons .LOGO_16
203202 }
204- }.topGap(TopGap .SMALL ).bottomGap( BottomGap . MEDIUM )
203+ }.topGap(TopGap .SMALL )
205204 row {
206205 cell(ComponentPanelBuilder .createCommentComponent(CoderGatewayBundle .message(" gateway.connector.view.coder.workspaces.comment" ), false , - 1 , true ))
207206 }
208207 row {
209208 browserLink(CoderGatewayBundle .message(" gateway.connector.view.login.documentation.action" ), " https://coder.com/docs/coder-oss/latest/workspaces" )
210- }.bottomGap( BottomGap . MEDIUM )
209+ }
211210 row(CoderGatewayBundle .message(" gateway.connector.view.login.url.label" )) {
212211 tfUrl = textField().resizableColumn().align(AlignX .FILL ).gap(RightGap .SMALL ).bindText(localWizardModel::coderURL).applyToComponent {
213212 addActionListener {
@@ -225,6 +224,17 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
225224 }
226225 cell()
227226 }
227+ row {
228+ cbExistingToken = checkBox(CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" ))
229+ .bindSelected(localWizardModel::useExistingToken)
230+ .component
231+ }
232+ row {
233+ cell(ComponentPanelBuilder .createCommentComponent(
234+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.tooltip" ,
235+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" )),
236+ false , - 1 , true ))
237+ }
228238 row {
229239 scrollCell(toolbar.createPanel().apply {
230240 add(notificationBanner.component.apply { isVisible = false }, " South" )
@@ -315,18 +325,71 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
315325 if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token.isNotBlank()) {
316326 triggerWorkspacePolling(true )
317327 } else {
318- val url = appPropertiesService.getValue(CODER_URL_KEY )
319- val token = appPropertiesService.getValue(SESSION_TOKEN )
320- if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
328+ val (url, token) = readStorageOrConfig()
329+ if (! url.isNullOrBlank()) {
321330 localWizardModel.coderURL = url
322- localWizardModel.token = token
323331 tfUrl?.text = url
332+ }
333+ if (! token.isNullOrBlank()) {
334+ localWizardModel.token = token
335+ }
336+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
324337 loginAndLoadWorkspace(token, true )
325338 }
326339 }
327340 updateWorkspaceActions()
328341 }
329342
343+ /* *
344+ * Return the URL and token from storage or the CLI config.
345+ */
346+ private fun readStorageOrConfig (): Pair <String ?, String ?> {
347+ val url = appPropertiesService.getValue(CODER_URL_KEY )
348+ val token = appPropertiesService.getValue(SESSION_TOKEN )
349+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
350+ return url to token
351+ }
352+ return readConfig()
353+ }
354+
355+ /* *
356+ * Return the URL and token from the CLI config.
357+ */
358+ private fun readConfig (): Pair <String ?, String ?> {
359+ val configDir = getConfigDir()
360+ logger.info(" Reading config from $configDir " )
361+ try {
362+ val url = Files .readString(configDir.resolve(" url" ))
363+ val token = Files .readString(configDir.resolve(" session" ))
364+ return url to token
365+ } catch (e: Exception ) {
366+ return null to null // Probably has not configured the CLI yet.
367+ }
368+ }
369+
370+ /* *
371+ * Return the config directory used by the CLI.
372+ */
373+ private fun getConfigDir (): Path {
374+ var dir = System .getenv(" CODER_CONFIG_DIR" )
375+ if (! dir.isNullOrBlank()) {
376+ return Path .of(dir)
377+ }
378+ // The Coder CLI uses https://github.com/kirsle/configdir so this should
379+ // match how it behaves.
380+ return when (getOS()) {
381+ OS .WINDOWS -> Paths .get(System .getenv(" APPDATA" ), " coderv2" )
382+ OS .MAC -> Paths .get(System .getenv(" HOME" ), " Library/Application Support/coderv2" )
383+ else -> {
384+ dir = System .getenv(" XDG_CACHE_HOME" )
385+ if (! dir.isNullOrBlank()) {
386+ return Paths .get(dir, " coderv2" )
387+ }
388+ return Paths .get(System .getenv(" HOME" ), " .config/coderv2" )
389+ }
390+ }
391+ }
392+
330393 private fun updateWorkspaceActions () {
331394 goToDashboardAction.isEnabled = coderClient.isReady
332395 createWorkspaceAction.isEnabled = coderClient.isReady
@@ -442,8 +505,14 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
442505
443506 private fun askToken (openBrowser : Boolean ): String? {
444507 val getTokenUrl = localWizardModel.coderURL.toURL().withPath(" /login?redirect=%2Fcli-auth" )
445- if (openBrowser) {
508+ if (openBrowser && ! localWizardModel.useExistingToken ) {
446509 BrowserUtil .browse(getTokenUrl)
510+ } else if (localWizardModel.useExistingToken) {
511+ val (url, token) = readConfig()
512+ if (url == localWizardModel.coderURL && ! token.isNullOrBlank()) {
513+ logger.info(" Injecting valid token from CLI config" )
514+ localWizardModel.token = token
515+ }
447516 }
448517 var tokenFromUser: String? = null
449518 ApplicationManager .getApplication().invokeAndWait({
0 commit comments