Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2d1328a

Browse files
authored
Merge pull request #136 from coder/list-installed-ides
List installed ides
2 parents a4432f0 + f523d51 commit 2d1328a

File tree

7 files changed

+215
-102
lines changed

7 files changed

+215
-102
lines changed

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- ability to open a template in the Dashboard
99
- ability to sort by workspace name, or by template name or by workspace status
1010
- a new token is requested when the one persisted is expired
11+
- support for re-using already installed IDE backends
1112

1213
### Changed
1314
- renamed the plugin from `Coder Gateway` to `Gateway`

‎gradle.properties‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pluginGroup=com.coder.gateway
44
pluginName=coder-gateway
55
# SemVer format -> https://semver.org
6-
pluginVersion=2.1.3
6+
pluginVersion=2.1.4
77
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
88
# for insight into build numbers and IntelliJ Platform versions.
99
pluginSinceBuild=222.3739.54

‎src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt‎

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,39 @@
22

33
package com.coder.gateway
44

5-
import com.coder.gateway.models.RecentWorkspaceConnection
65
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
76
import com.intellij.openapi.components.service
87
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
9-
import com.intellij.remote.AuthType
10-
import com.intellij.remote.RemoteCredentialsHolder
11-
import com.intellij.ssh.config.unified.SshConfig
128
import com.jetbrains.gateway.api.ConnectionRequestor
139
import com.jetbrains.gateway.api.GatewayConnectionHandle
1410
import com.jetbrains.gateway.api.GatewayConnectionProvider
1511
import com.jetbrains.gateway.api.GatewayUI
16-
import com.jetbrains.gateway.ssh.HighLevelHostAccessor
17-
import com.jetbrains.gateway.ssh.HostDeployInputs
18-
import com.jetbrains.gateway.ssh.IdeInfo
19-
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
2012
import com.jetbrains.gateway.ssh.SshDeployFlowUtil
2113
import com.jetbrains.gateway.ssh.SshMultistagePanelContext
22-
import com.jetbrains.gateway.ssh.deploy.DeployTargetInfo.DeployWithDownload
2314
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
2415
import kotlinx.coroutines.launch
25-
import java.net.URI
2616
import java.time.Duration
27-
import java.time.LocalDateTime
28-
import java.time.format.DateTimeFormatter
2917

3018
class CoderGatewayConnectionProvider : GatewayConnectionProvider {
3119
private val recentConnectionsService = service<CoderRecentWorkspaceConnectionsService>()
3220

33-
private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm")
34-
3521
override suspend fun connect(parameters: Map<String, String>, requestor: ConnectionRequestor): GatewayConnectionHandle? {
36-
val coderWorkspaceHostname = parameters["coder_workspace_hostname"]
37-
val projectPath = parameters["project_path"]
38-
val ideProductCode = parameters["ide_product_code"]!!
39-
val ideBuildNumber = parameters["ide_build_number"]!!
40-
val ideDownloadLink = parameters["ide_download_link"]!!
41-
val webTerminalLink = parameters["web_terminal_link"]!!
42-
43-
if (coderWorkspaceHostname != null && projectPath != null) {
44-
val sshConfiguration = SshConfig(true).apply {
45-
setHost(coderWorkspaceHostname)
46-
setUsername("coder")
47-
port = 22
48-
authType = AuthType.OPEN_SSH
49-
}
50-
51-
val clientLifetime = LifetimeDefinition()
52-
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
53-
val context = SshMultistagePanelContext(
54-
HostDeployInputs.FullySpecified(
55-
remoteProjectPath = projectPath,
56-
deployTarget = DeployWithDownload(
57-
URI(ideDownloadLink),
58-
null,
59-
IdeInfo(
60-
product = IntelliJPlatformProduct.fromProductCode(ideProductCode)!!,
61-
buildNumber = ideBuildNumber
62-
)
63-
),
64-
remoteInfo = HostDeployInputs.WithDeployedWorker(
65-
HighLevelHostAccessor.create(
66-
RemoteCredentialsHolder().apply {
67-
setHost(coderWorkspaceHostname)
68-
userName = "coder"
69-
port = 22
70-
authType = AuthType.OPEN_SSH
71-
},
72-
true
73-
),
74-
HostDeployInputs.WithHostInfo(sshConfiguration)
75-
)
76-
)
22+
val clientLifetime = LifetimeDefinition()
23+
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
24+
val context = SshMultistagePanelContext(parameters.toHostDeployInputs())
25+
launch {
26+
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
27+
clientLifetime, context, Duration.ofMinutes(10)
7728
)
78-
launch {
79-
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
80-
clientLifetime, context, Duration.ofMinutes(10)
81-
)
82-
}
8329
}
84-
85-
recentConnectionsService.addRecentConnection(RecentWorkspaceConnection(coderWorkspaceHostname, projectPath, localTimeFormatter.format(LocalDateTime.now()), ideProductCode, ideBuildNumber, ideDownloadLink, webTerminalLink))
86-
GatewayUI.getInstance().reset()
8730
}
31+
32+
recentConnectionsService.addRecentConnection(parameters.toRecentWorkspaceConnection())
33+
GatewayUI.getInstance().reset()
8834
return null
8935
}
9036

9137
override fun isApplicable(parameters: Map<String, String>): Boolean {
92-
return parameters["type"] =="coder"
38+
return parameters.areCoderType()
9339
}
9440
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package com.coder.gateway
2+
3+
import com.coder.gateway.models.RecentWorkspaceConnection
4+
import com.intellij.remote.AuthType
5+
import com.intellij.remote.RemoteCredentialsHolder
6+
import com.intellij.ssh.config.unified.SshConfig
7+
import com.jetbrains.gateway.ssh.HighLevelHostAccessor
8+
import com.jetbrains.gateway.ssh.HostDeployInputs
9+
import com.jetbrains.gateway.ssh.IdeInfo
10+
import com.jetbrains.gateway.ssh.IdeWithStatus
11+
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
12+
import com.jetbrains.gateway.ssh.deploy.DeployTargetInfo
13+
import java.net.URI
14+
import java.time.LocalDateTime
15+
import java.time.format.DateTimeFormatter
16+
17+
private const val CODER_WORKSPACE_HOSTNAME = "coder_workspace_hostname"
18+
private const val TYPE = "type"
19+
private const val VALUE_FOR_TYPE = "coder"
20+
private const val PROJECT_PATH = "project_path"
21+
private const val IDE_DOWNLOAD_LINK = "ide_download_link"
22+
private const val IDE_PRODUCT_CODE = "ide_product_code"
23+
private const val IDE_BUILD_NUMBER = "ide_build_number"
24+
private const val IDE_PATH_ON_HOST = "ide_path_on_host"
25+
private const val WEB_TERMINAL_LINK = "web_terminal_link"
26+
27+
private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm")
28+
29+
fun RecentWorkspaceConnection.toWorkspaceParams(): Map<String, String> {
30+
val map = mutableMapOf(
31+
TYPE to VALUE_FOR_TYPE,
32+
CODER_WORKSPACE_HOSTNAME to "${this.coderWorkspaceHostname}",
33+
PROJECT_PATH to this.projectPath!!,
34+
IDE_PRODUCT_CODE to IntelliJPlatformProduct.fromProductCode(this.ideProductCode!!)!!.productCode,
35+
IDE_BUILD_NUMBER to "${this.ideBuildNumber}",
36+
WEB_TERMINAL_LINK to "${this.webTerminalLink}"
37+
)
38+
39+
if (!this.downloadSource.isNullOrBlank()) {
40+
map[IDE_DOWNLOAD_LINK] = this.downloadSource!!
41+
} else {
42+
map[IDE_PATH_ON_HOST] = this.idePathOnHost!!
43+
}
44+
return map
45+
}
46+
47+
fun IdeWithStatus.toWorkspaceParams(): Map<String, String> {
48+
val workspaceParams = mutableMapOf(
49+
TYPE to VALUE_FOR_TYPE,
50+
IDE_PRODUCT_CODE to this.product.productCode,
51+
IDE_BUILD_NUMBER to this.buildNumber
52+
)
53+
54+
if (this.download != null) {
55+
workspaceParams[IDE_DOWNLOAD_LINK] = this.download!!.link
56+
}
57+
58+
if (!this.pathOnHost.isNullOrBlank()) {
59+
workspaceParams[IDE_PATH_ON_HOST] = this.pathOnHost!!
60+
}
61+
62+
return workspaceParams
63+
}
64+
65+
fun Map<String, String>.withWorkspaceHostname(hostname: String): Map<String, String> {
66+
val map = this.toMutableMap()
67+
map[CODER_WORKSPACE_HOSTNAME] = hostname
68+
return map
69+
}
70+
71+
fun Map<String, String>.withProjectPath(projectPath: String): Map<String, String> {
72+
val map = this.toMutableMap()
73+
map[PROJECT_PATH] = projectPath
74+
return map
75+
}
76+
77+
fun Map<String, String>.withWebTerminalLink(webTerminalLink: String): Map<String, String> {
78+
val map = this.toMutableMap()
79+
map[WEB_TERMINAL_LINK] = webTerminalLink
80+
return map
81+
}
82+
83+
fun Map<String, String>.areCoderType(): Boolean {
84+
return this[TYPE] == VALUE_FOR_TYPE && !this[CODER_WORKSPACE_HOSTNAME].isNullOrBlank() && !this[PROJECT_PATH].isNullOrBlank()
85+
}
86+
87+
fun Map<String, String>.toSshConfig(): SshConfig {
88+
return SshConfig(true).apply {
89+
setHost(this@toSshConfig.workspaceHostname())
90+
setUsername("coder")
91+
port = 22
92+
authType = AuthType.OPEN_SSH
93+
}
94+
}
95+
96+
suspend fun Map<String, String>.toHostDeployInputs(): HostDeployInputs {
97+
return HostDeployInputs.FullySpecified(
98+
remoteProjectPath = this[PROJECT_PATH]!!,
99+
deployTarget = this.toDeployTargetInfo(),
100+
remoteInfo = HostDeployInputs.WithDeployedWorker(
101+
HighLevelHostAccessor.create(
102+
RemoteCredentialsHolder().apply {
103+
setHost(this@toHostDeployInputs.workspaceHostname())
104+
userName = "coder"
105+
port = 22
106+
authType = AuthType.OPEN_SSH
107+
},
108+
true
109+
),
110+
HostDeployInputs.WithHostInfo(this.toSshConfig())
111+
)
112+
)
113+
}
114+
115+
private fun Map<String, String>.toIdeInfo(): IdeInfo {
116+
return IdeInfo(
117+
product = IntelliJPlatformProduct.fromProductCode(this[IDE_PRODUCT_CODE]!!)!!,
118+
buildNumber = this[IDE_BUILD_NUMBER]!!
119+
)
120+
}
121+
122+
private fun Map<String, String>.toDeployTargetInfo(): DeployTargetInfo {
123+
return if (!this[IDE_DOWNLOAD_LINK].isNullOrBlank()) DeployTargetInfo.DeployWithDownload(
124+
URI(this[IDE_DOWNLOAD_LINK]),
125+
null,
126+
this.toIdeInfo()
127+
)
128+
else DeployTargetInfo.NoDeploy(this[IDE_PATH_ON_HOST]!!, this.toIdeInfo())
129+
}
130+
131+
private fun Map<String, String>.workspaceHostname() = this[CODER_WORKSPACE_HOSTNAME]!!
132+
private fun Map<String, String>.projectPath() = this[PROJECT_PATH]!!
133+
134+
fun Map<String, String>.toRecentWorkspaceConnection(): RecentWorkspaceConnection {
135+
return if (!this[IDE_DOWNLOAD_LINK].isNullOrBlank()) RecentWorkspaceConnection(
136+
this.workspaceHostname(),
137+
this.projectPath(),
138+
localTimeFormatter.format(LocalDateTime.now()),
139+
this[IDE_PRODUCT_CODE]!!,
140+
this[IDE_BUILD_NUMBER]!!,
141+
this[IDE_DOWNLOAD_LINK]!!,
142+
null,
143+
this[WEB_TERMINAL_LINK]!!
144+
) else RecentWorkspaceConnection(
145+
this.workspaceHostname(),
146+
this.projectPath(),
147+
localTimeFormatter.format(LocalDateTime.now()),
148+
this[IDE_PRODUCT_CODE]!!,
149+
this[IDE_BUILD_NUMBER]!!,
150+
null,
151+
this[IDE_PATH_ON_HOST],
152+
this[WEB_TERMINAL_LINK]!!
153+
)
154+
}

‎src/main/kotlin/com/coder/gateway/models/RecentWorkspaceConnection.kt‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import com.intellij.openapi.components.BaseState
44
import com.intellij.util.xmlb.annotations.Attribute
55

66
class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConnection> {
7-
constructor(hostname: String, prjPath: String, openedAt: String, productCode: String, buildNumber: String, source: String, terminalLink: String) : this() {
7+
constructor(hostname: String, prjPath: String, openedAt: String, productCode: String, buildNumber: String, source: String?, idePath:String?, terminalLink: String) : this() {
88
coderWorkspaceHostname = hostname
99
projectPath = prjPath
1010
lastOpened = openedAt
1111
ideProductCode = productCode
1212
ideBuildNumber = buildNumber
1313
downloadSource = source
14+
idePathOnHost = idePath
1415
webTerminalLink = terminalLink
1516
}
1617

@@ -32,6 +33,10 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
3233
@get:Attribute
3334
var downloadSource by string()
3435

36+
37+
@get:Attribute
38+
var idePathOnHost by string()
39+
3540
@get:Attribute
3641
var webTerminalLink by string()
3742

@@ -47,6 +52,7 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
4752
if (ideProductCode != other.ideProductCode) return false
4853
if (ideBuildNumber != other.ideBuildNumber) return false
4954
if (downloadSource != other.downloadSource) return false
55+
if (idePathOnHost != other.idePathOnHost) return false
5056
if (webTerminalLink != other.webTerminalLink) return false
5157

5258
return true
@@ -59,6 +65,7 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
5965
result = 31 * result + (ideProductCode?.hashCode() ?: 0)
6066
result = 31 * result + (ideBuildNumber?.hashCode() ?: 0)
6167
result = 31 * result + (downloadSource?.hashCode() ?: 0)
68+
result = 31 * result + (idePathOnHost?.hashCode() ?: 0)
6269
result = 31 * result + (webTerminalLink?.hashCode() ?: 0)
6370

6471
return result
@@ -80,8 +87,11 @@ class RecentWorkspaceConnection() : BaseState(), Comparable<RecentWorkspaceConne
8087
val m = other.downloadSource?.let { downloadSource?.compareTo(it) }
8188
if (m != null && m != 0) return m
8289

83-
val n = other.webTerminalLink?.let { webTerminalLink?.compareTo(it) }
84-
if (n != null && n != 0) return n
90+
val n = other.idePathOnHost?.let { idePathOnHost?.compareTo(it) }
91+
if (n != null && m != 0) return n
92+
93+
val o = other.webTerminalLink?.let { webTerminalLink?.compareTo(it) }
94+
if (o != null && n != 0) return o
8595

8696
return 0
8797
}

‎src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.coder.gateway.CoderGatewayConstants
77
import com.coder.gateway.icons.CoderIcons
88
import com.coder.gateway.models.RecentWorkspaceConnection
99
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
10+
import com.coder.gateway.toWorkspaceParams
1011
import com.intellij.icons.AllIcons
1112
import com.intellij.ide.BrowserUtil
1213
import com.intellij.openapi.Disposable
@@ -37,6 +38,7 @@ import kotlinx.coroutines.cancel
3738
import kotlinx.coroutines.launch
3839
import java.awt.Component
3940
import java.awt.Dimension
41+
import java.util.Locale
4042
import javax.swing.JComponent
4143
import javax.swing.JLabel
4244
import javax.swing.event.DocumentEvent
@@ -70,7 +72,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
7072
addDocumentListener(object : DocumentAdapter() {
7173
override fun textChanged(e: DocumentEvent) {
7274
val toSearchFor = this@applyToComponent.text
73-
val filteredConnections = recentConnectionsService.getAllRecentConnections().filter { it.coderWorkspaceHostname?.toLowerCase()?.contains(toSearchFor) ?: false || it.projectPath?.toLowerCase()?.contains(toSearchFor) ?: false }
75+
val filteredConnections = recentConnectionsService.getAllRecentConnections().filter { it.coderWorkspaceHostname?.lowercase(Locale.getDefault())?.contains(toSearchFor) ?: false || it.projectPath?.lowercase(Locale.getDefault())?.contains(toSearchFor) ?: false }
7476
updateContentView(filteredConnections.groupBy { it.coderWorkspaceHostname })
7577
}
7678
})
@@ -127,17 +129,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
127129
icon(product.icon)
128130
cell(ActionLink(connectionDetails.projectPath!!) {
129131
cs.launch {
130-
GatewayUI.getInstance().connect(
131-
mapOf(
132-
"type" to "coder",
133-
"coder_workspace_hostname" to "${connectionDetails.coderWorkspaceHostname}",
134-
"project_path" to connectionDetails.projectPath!!,
135-
"ide_product_code" to product.productCode,
136-
"ide_build_number" to "${connectionDetails.ideBuildNumber}",
137-
"ide_download_link" to "${connectionDetails.downloadSource}",
138-
"web_terminal_link" to "${connectionDetails.webTerminalLink}"
139-
)
140-
)
132+
GatewayUI.getInstance().connect(connectionDetails.toWorkspaceParams())
141133
}
142134
})
143135
label("").resizableColumn().horizontalAlign(HorizontalAlign.FILL)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /