@@ -46,13 +46,7 @@ import com.intellij.ui.RelativeFont
46
46
import com.intellij.ui.ToolbarDecorator
47
47
import com.intellij.ui.components.JBTextField
48
48
import 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.*
56
50
import com.intellij.ui.table.TableView
57
51
import com.intellij.util.ui.ColumnInfo
58
52
import com.intellij.util.ui.JBFont
@@ -76,8 +70,12 @@ import java.awt.event.MouseListener
76
70
import java.awt.event.MouseMotionListener
77
71
import java.awt.font.TextAttribute
78
72
import java.awt.font.TextAttribute.UNDERLINE_ON
73
+ import java.nio.file.Files
74
+ import java.nio.file.Path
75
+ import java.nio.file.Paths
79
76
import java.net.SocketTimeoutException
80
77
import javax.swing.Icon
78
+ import javax.swing.JCheckBox
81
79
import javax.swing.JTable
82
80
import javax.swing.JTextField
83
81
import javax.swing.ListSelectionModel
@@ -100,6 +98,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
100
98
private val appPropertiesService: PropertiesComponent = service()
101
99
102
100
private var tfUrl: JTextField ? = null
101
+ private var cbExistingToken: JCheckBox ? = null
103
102
private var listTableModelOfWorkspaces = ListTableModel <WorkspaceAgentModel >(
104
103
WorkspaceIconColumnInfo (" " ),
105
104
WorkspaceNameColumnInfo (" Name" ),
@@ -201,13 +200,13 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
201
200
font = JBFont .h3().asBold()
202
201
icon = CoderIcons .LOGO_16
203
202
}
204
- }.topGap(TopGap .SMALL ).bottomGap( BottomGap . MEDIUM )
203
+ }.topGap(TopGap .SMALL )
205
204
row {
206
205
cell(ComponentPanelBuilder .createCommentComponent(CoderGatewayBundle .message(" gateway.connector.view.coder.workspaces.comment" ), false , - 1 , true ))
207
206
}
208
207
row {
209
208
browserLink(CoderGatewayBundle .message(" gateway.connector.view.login.documentation.action" ), " https://coder.com/docs/coder-oss/latest/workspaces" )
210
- }.bottomGap( BottomGap . MEDIUM )
209
+ }
211
210
row(CoderGatewayBundle .message(" gateway.connector.view.login.url.label" )) {
212
211
tfUrl = textField().resizableColumn().align(AlignX .FILL ).gap(RightGap .SMALL ).bindText(localWizardModel::coderURL).applyToComponent {
213
212
addActionListener {
@@ -225,6 +224,17 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
225
224
}
226
225
cell()
227
226
}
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
+ }
228
238
row {
229
239
scrollCell(toolbar.createPanel().apply {
230
240
add(notificationBanner.component.apply { isVisible = false }, " South" )
@@ -315,18 +325,71 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
315
325
if (localWizardModel.coderURL.isNotBlank() && localWizardModel.token.isNotBlank()) {
316
326
triggerWorkspacePolling(true )
317
327
} 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()) {
321
330
localWizardModel.coderURL = url
322
- localWizardModel.token = token
323
331
tfUrl?.text = url
332
+ }
333
+ if (! token.isNullOrBlank()) {
334
+ localWizardModel.token = token
335
+ }
336
+ if (! url.isNullOrBlank() && ! token.isNullOrBlank()) {
324
337
loginAndLoadWorkspace(token, true )
325
338
}
326
339
}
327
340
updateWorkspaceActions()
328
341
}
329
342
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
+
330
393
private fun updateWorkspaceActions () {
331
394
goToDashboardAction.isEnabled = coderClient.isReady
332
395
createWorkspaceAction.isEnabled = coderClient.isReady
@@ -442,8 +505,14 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
442
505
443
506
private fun askToken (openBrowser : Boolean ): String? {
444
507
val getTokenUrl = localWizardModel.coderURL.toURL().withPath(" /login?redirect=%2Fcli-auth" )
445
- if (openBrowser) {
508
+ if (openBrowser && ! localWizardModel.useExistingToken ) {
446
509
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
+ }
447
516
}
448
517
var tokenFromUser: String? = null
449
518
ApplicationManager .getApplication().invokeAndWait({
0 commit comments