@@ -76,8 +76,12 @@ import java.awt.event.MouseListener
7676import java.awt.event.MouseMotionListener
7777import java.awt.font.TextAttribute
7878import java.awt.font.TextAttribute.UNDERLINE_ON
79+ import java.nio.file.Files
80+ import java.nio.file.Path
81+ import java.nio.file.Paths
7982import java.net.SocketTimeoutException
8083import javax.swing.Icon
84+ import javax.swing.JCheckBox
8185import javax.swing.JTable
8286import javax.swing.JTextField
8387import javax.swing.ListSelectionModel
@@ -100,6 +104,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
100104 private val appPropertiesService: PropertiesComponent = service()
101105
102106 private var tfUrl: JTextField ? = null
107+ private var cbExistingToken: JCheckBox ? = null
103108 private var listTableModelOfWorkspaces = ListTableModel <WorkspaceAgentModel >(
104109 WorkspaceIconColumnInfo (" " ),
105110 WorkspaceNameColumnInfo (" Name" ),
@@ -201,13 +206,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
201206 font = JBFont .h3().asBold()
202207 icon = CoderIcons .LOGO_16
203208 }
204- }.topGap(TopGap .SMALL ).bottomGap( BottomGap . MEDIUM )
209+ }.topGap(TopGap .SMALL )
205210 row {
206211 cell(ComponentPanelBuilder .createCommentComponent(CoderGatewayBundle .message(" gateway.connector.view.coder.workspaces.comment" ), false , - 1 , true ))
207212 }
208213 row {
209214 browserLink(CoderGatewayBundle .message(" gateway.connector.view.login.documentation.action" ), " https://coder.com/docs/coder-oss/latest/workspaces" )
210- }.bottomGap( BottomGap . MEDIUM )
215+ }
211216 row(CoderGatewayBundle .message(" gateway.connector.view.login.url.label" )) {
212217 tfUrl = textField().resizableColumn().align(AlignX .FILL ).gap(RightGap .SMALL ).bindText(localWizardModel::coderURL).applyToComponent {
213218 addActionListener {
@@ -223,6 +228,17 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
223228 }
224229 cell()
225230 }
231+ row {
232+ cbExistingToken = checkBox(CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" ))
233+ .bindSelected(localWizardModel::useExistingToken)
234+ .component
235+ }
236+ row {
237+ cell(ComponentPanelBuilder .createCommentComponent(
238+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.tooltip" ,
239+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" )),
240+ false , - 1 , true ))
241+ }
226242 row {
227243 scrollCell(toolbar.createPanel().apply {
228244 add(notificationBanner.component.apply { isVisible = false }, " South" )
@@ -313,18 +329,70 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
313329 if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token.isNotBlank()) {
314330 triggerWorkspacePolling()
315331 } else {
316- val url = appPropertiesService.getValue(CODER_URL_KEY )
317- val token = appPropertiesService.getValue(SESSION_TOKEN )
318- if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
332+ val (url, token) = readStorageOrConfig()
333+ if (! url.isNullOrBlank()) {
319334 localWizardModel.coderURL = url
320- localWizardModel.token = token
321335 tfUrl?.text = url
336+ }
337+ if (! token.isNullOrBlank()) {
338+ localWizardModel.token = token
339+ }
340+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
322341 loginAndLoadWorkspace(token, true )
323342 }
324343 }
325344 updateWorkspaceActions()
326345 }
327346
347+ /* *
348+ * Return the URL and token from storage or the CLI config.
349+ */
350+ private fun readStorageOrConfig (): Pair <String ?, String ?> {
351+ val url = appPropertiesService.getValue(CODER_URL_KEY )
352+ val token = appPropertiesService.getValue(SESSION_TOKEN )
353+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
354+ return url to token
355+ }
356+ return readConfig()
357+ }
358+
359+ /* *
360+ * Return the URL and token from the CLI config.
361+ */
362+ private fun readConfig (): Pair <String ?, String ?> {
363+ val configDir = getConfigDir()
364+ try {
365+ val url = Files .readString(configDir.resolve(" url" ))
366+ val token = Files .readString(configDir.resolve(" session" ))
367+ return url to token
368+ } catch (e: Exception ) {
369+ return null to null // Probably has not configured the CLI yet.
370+ }
371+ }
372+
373+ /* *
374+ * Return the config directory used by the CLI.
375+ */
376+ private fun getConfigDir (): Path {
377+ var dir = System .getenv(" CODER_CONFIG_DIR" )
378+ if (! dir.isNullOrBlank()) {
379+ return Path .of(dir)
380+ }
381+ // The Coder CLI uses https://github.com/kirsle/configdir so this should
382+ // match how it behaves.
383+ return when (getOS()) {
384+ OS .WINDOWS -> Paths .get(System .getenv(" APPDATA" ), " coderv2" )
385+ OS .MAC -> Paths .get(System .getenv(" HOME" ), " Library/Application Support/coderv2" )
386+ else -> {
387+ dir = System .getenv(" XDG_CACHE_HOME" )
388+ if (! dir.isNullOrBlank()) {
389+ return Paths .get(dir, " coderv2" )
390+ }
391+ return Paths .get(System .getenv(" HOME" ), " .config/coderv2" )
392+ }
393+ }
394+ }
395+
328396 private fun updateWorkspaceActions () {
329397 goToDashboardAction.isEnabled = coderClient.isReady
330398 createWorkspaceAction.isEnabled = coderClient.isReady
@@ -440,8 +508,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
440508
441509 private fun askToken (openBrowser : Boolean ): String? {
442510 val getTokenUrl = localWizardModel.coderURL.toURL().withPath(" /login?redirect=%2Fcli-auth" )
443- if (openBrowser) {
511+ if (openBrowser && ! localWizardModel.useExistingToken ) {
444512 BrowserUtil .browse(getTokenUrl)
513+ } else if (localWizardModel.useExistingToken) {
514+ val (url, token) = readConfig()
515+ if (url == localWizardModel.coderURL && ! token.isNullOrBlank()) {
516+ localWizardModel.token = token
517+ }
445518 }
446519 var tokenFromUser: String? = null
447520 ApplicationManager .getApplication().invokeAndWait({
0 commit comments