@@ -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 {
@@ -223,6 +222,17 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
223222 }
224223 cell()
225224 }
225+ row {
226+ cbExistingToken = checkBox(CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" ))
227+ .bindSelected(localWizardModel::useExistingToken)
228+ .component
229+ }
230+ row {
231+ cell(ComponentPanelBuilder .createCommentComponent(
232+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.tooltip" ,
233+ CoderGatewayBundle .message(" gateway.connector.view.login.existing-token.label" )),
234+ false , - 1 , true ))
235+ }
226236 row {
227237 scrollCell(toolbar.createPanel().apply {
228238 add(notificationBanner.component.apply { isVisible = false }, " South" )
@@ -313,18 +323,70 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
313323 if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token.isNotBlank()) {
314324 triggerWorkspacePolling()
315325 } else {
316- val url = appPropertiesService.getValue(CODER_URL_KEY )
317- val token = appPropertiesService.getValue(SESSION_TOKEN )
318- if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
326+ val (url, token) = readStorageOrConfig()
327+ if (! url.isNullOrBlank()) {
319328 localWizardModel.coderURL = url
320- localWizardModel.token = token
321329 tfUrl?.text = url
330+ }
331+ if (! token.isNullOrBlank()) {
332+ localWizardModel.token = token
333+ }
334+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
322335 loginAndLoadWorkspace(token, true )
323336 }
324337 }
325338 updateWorkspaceActions()
326339 }
327340
341+ /* *
342+ * Return the URL and token from storage or the CLI config.
343+ */
344+ private fun readStorageOrConfig (): Pair <String ?, String ?> {
345+ val url = appPropertiesService.getValue(CODER_URL_KEY )
346+ val token = appPropertiesService.getValue(SESSION_TOKEN )
347+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
348+ return url to token
349+ }
350+ return readConfig()
351+ }
352+
353+ /* *
354+ * Return the URL and token from the CLI config.
355+ */
356+ private fun readConfig (): Pair <String ?, String ?> {
357+ val configDir = getConfigDir()
358+ try {
359+ val url = Files .readString(configDir.resolve(" url" ))
360+ val token = Files .readString(configDir.resolve(" session" ))
361+ return url to token
362+ } catch (e: Exception ) {
363+ return null to null // Probably has not configured the CLI yet.
364+ }
365+ }
366+
367+ /* *
368+ * Return the config directory used by the CLI.
369+ */
370+ private fun getConfigDir (): Path {
371+ var dir = System .getenv(" CODER_CONFIG_DIR" )
372+ if (! dir.isNullOrBlank()) {
373+ return Path .of(dir)
374+ }
375+ // The Coder CLI uses https://github.com/kirsle/configdir so this should
376+ // match how it behaves.
377+ return when (getOS()) {
378+ OS .WINDOWS -> Paths .get(System .getenv(" APPDATA" ), " coderv2" )
379+ OS .MAC -> Paths .get(System .getenv(" HOME" ), " Library/Application Support/coderv2" )
380+ else -> {
381+ dir = System .getenv(" XDG_CACHE_HOME" )
382+ if (! dir.isNullOrBlank()) {
383+ return Paths .get(dir, " coderv2" )
384+ }
385+ return Paths .get(System .getenv(" HOME" ), " .config/coderv2" )
386+ }
387+ }
388+ }
389+
328390 private fun updateWorkspaceActions () {
329391 goToDashboardAction.isEnabled = coderClient.isReady
330392 createWorkspaceAction.isEnabled = coderClient.isReady
@@ -440,8 +502,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
440502
441503 private fun askToken (openBrowser : Boolean ): String? {
442504 val getTokenUrl = localWizardModel.coderURL.toURL().withPath(" /login?redirect=%2Fcli-auth" )
443- if (openBrowser) {
505+ if (openBrowser && ! localWizardModel.useExistingToken ) {
444506 BrowserUtil .browse(getTokenUrl)
507+ } else if (localWizardModel.useExistingToken) {
508+ val (url, token) = readConfig()
509+ if (url == localWizardModel.coderURL && ! token.isNullOrBlank()) {
510+ localWizardModel.token = token
511+ }
445512 }
446513 var tokenFromUser: String? = null
447514 ApplicationManager .getApplication().invokeAndWait({
0 commit comments