Skip to content

Commit bd483e6

Browse files
committed
Add settings page for CLI locations
1 parent 32d047b commit bd483e6

File tree

7 files changed

+160
-6
lines changed

7 files changed

+160
-6
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.coder.gateway
2+
3+
import com.coder.gateway.sdk.CoderCLIManager
4+
import com.coder.gateway.sdk.canWrite
5+
import com.coder.gateway.services.CoderSettingsState
6+
import com.intellij.openapi.components.service
7+
import com.intellij.openapi.options.BoundConfigurable
8+
import com.intellij.openapi.ui.DialogPanel
9+
import com.intellij.openapi.ui.ValidationInfo
10+
import com.intellij.ui.components.JBTextField
11+
import com.intellij.ui.dsl.builder.AlignX
12+
import com.intellij.ui.dsl.builder.bindText
13+
import com.intellij.ui.dsl.builder.panel
14+
import com.intellij.ui.layout.ValidationInfoBuilder
15+
import java.net.URL
16+
import java.nio.file.Path
17+
18+
class CoderSettingsConfigurable : BoundConfigurable("Coder") {
19+
override fun createPanel(): DialogPanel {
20+
val state: CoderSettingsState = service()
21+
return panel {
22+
row(CoderGatewayBundle.message("gateway.connector.settings.binary-source.title")) {
23+
textField().resizableColumn().align(AlignX.FILL)
24+
.bindText(state::binarySource)
25+
.comment(
26+
CoderGatewayBundle.message(
27+
"gateway.connector.settings.binary-source.comment",
28+
CoderCLIManager(URL("http://localhost")).remoteBinaryURL.path,
29+
)
30+
)
31+
}
32+
row(CoderGatewayBundle.message("gateway.connector.settings.binary-destination.title")) {
33+
textField().resizableColumn().align(AlignX.FILL)
34+
.bindText(state::binaryDestination)
35+
.validationOnApply(validateBinaryDestination())
36+
.validationOnInput(validateBinaryDestination())
37+
.comment(
38+
CoderGatewayBundle.message(
39+
"gateway.connector.settings.binary-destination.comment",
40+
CoderCLIManager.getDataDir(),
41+
)
42+
)
43+
}
44+
}
45+
}
46+
47+
private fun validateBinaryDestination(): ValidationInfoBuilder.(JBTextField) -> ValidationInfo? = {
48+
if (it.text.isNotBlank() && !Path.of(it.text).canWrite()) {
49+
error("Cannot write to this path")
50+
} else {
51+
null
52+
}
53+
}
54+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.coder.gateway.sdk
2+
3+
import java.nio.file.Files
4+
import java.nio.file.Path
5+
6+
/**
7+
* Return true if the path can be created.
8+
*
9+
* Unlike File.canWrite() or Files.isWritable() the file does not need to exist;
10+
* it only needs a writable parent.
11+
*/
12+
fun Path.canWrite(): Boolean {
13+
var current: Path? = this.toAbsolutePath()
14+
while (current != null && !Files.exists(current)) {
15+
current = current.parent
16+
}
17+
// On Windows File.canWrite() only checks read-only while Files.isWritable()
18+
// actually checks permissions.
19+
return current != null && Files.isWritable(current)
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.coder.gateway.services
2+
3+
import com.intellij.openapi.components.PersistentStateComponent
4+
import com.intellij.openapi.components.RoamingType
5+
import com.intellij.openapi.components.Service
6+
import com.intellij.openapi.components.State
7+
import com.intellij.openapi.components.Storage
8+
import com.intellij.util.xmlb.XmlSerializerUtil
9+
10+
@Service(Service.Level.APP)
11+
@State(
12+
name = "CoderSettingsState",
13+
storages = [Storage("coder-settings.xml", roamingType = RoamingType.DISABLED, exportable = true)]
14+
)
15+
class CoderSettingsState : PersistentStateComponent<CoderSettingsState> {
16+
var binarySource: String = ""
17+
var binaryDestination: String = ""
18+
override fun getState(): CoderSettingsState {
19+
return this
20+
}
21+
22+
override fun loadState(state: CoderSettingsState) {
23+
XmlSerializerUtil.copyBean(state, this)
24+
}
25+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
<depends optional="true">com.jetbrains.gateway</depends>
1616

1717
<extensions defaultExtensionNs="com.intellij">
18-
<applicationService serviceImplementation="com.coder.gateway.sdk.CoderRestClientService"></applicationService>
19-
<applicationService serviceImplementation="com.coder.gateway.sdk.TemplateIconDownloader"></applicationService>
20-
<applicationService serviceImplementation="com.coder.gateway.services.CoderRecentWorkspaceConnectionsService"></applicationService>
21-
<webHelpProvider implementation="com.coder.gateway.help.CoderWebHelp"></webHelpProvider>
18+
<applicationService serviceImplementation="com.coder.gateway.sdk.CoderRestClientService"/>
19+
<applicationService serviceImplementation="com.coder.gateway.sdk.TemplateIconDownloader"/>
20+
<applicationService serviceImplementation="com.coder.gateway.services.CoderRecentWorkspaceConnectionsService"/>
21+
<applicationService serviceImplementation="com.coder.gateway.services.CoderSettingsState"/>
22+
<applicationConfigurable parentId="tools" instance="com.coder.gateway.CoderSettingsConfigurable"/>
23+
<webHelpProvider implementation="com.coder.gateway.help.CoderWebHelp"/>
2224
</extensions>
2325
<extensions defaultExtensionNs="com.jetbrains">
2426
<gatewayConnector implementation="com.coder.gateway.CoderGatewayMainView"/>
2527
<gatewayConnectionProvider implementation="com.coder.gateway.CoderGatewayConnectionProvider"/>
2628
</extensions>
27-
</idea-plugin>
29+
</idea-plugin>

src/main/resources/messages/CoderGatewayBundle.properties

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,15 @@ gateway.connector.recentconnections.new.wizard.button.tooltip=Open a new Coder W
3434
gateway.connector.recentconnections.remove.button.tooltip=Remove from Recent Connections
3535
gateway.connector.recentconnections.terminal.button.tooltip=Open SSH Web Terminal
3636
gateway.connector.coder.connection.provider.title=Connecting to Coder workspace...
37+
gateway.connector.settings.binary-source.title=CLI source:
38+
gateway.connector.settings.binary-source.comment=Used to download the Coder \
39+
CLI which is necessary to make SSH connections. The If-None-Matched header \
40+
will be set to the SHA1 of the CLI and can be used for caching. Absolute \
41+
URLs will be used as-is; otherwise this value will be resolved against the \
42+
deployment domain. \
43+
Defaults to {0}.
44+
gateway.connector.settings.binary-destination.title=Data directory:
45+
gateway.connector.settings.binary-destination.comment=Directories are created \
46+
here that store the CLI and credentials for each domain to which the plugin \
47+
connects. \
48+
Defaults to {0}.

src/test/groovy/CoderCLIManagerTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import java.security.MessageDigest
2020
@Unroll
2121
class CoderCLIManagerTest extends Specification {
2222
@Shared
23-
private Path tmpdir = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test")
23+
private Path tmpdir = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test/cli-manager")
2424

2525
/**
2626
* Create, start, and return a server that mocks Coder.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.coder.gateway.sdk
2+
3+
import spock.lang.Shared
4+
import spock.lang.Specification
5+
import spock.lang.Unroll
6+
7+
import java.nio.file.Files
8+
import java.nio.file.Path
9+
10+
@Unroll
11+
class PathExtensionsTest extends Specification {
12+
@Shared
13+
private Path tmpdir = Path.of(System.getProperty("java.io.tmpdir"))
14+
@Shared
15+
private Path unwritable = tmpdir.resolve("coder-gateway-test/path-extensions/unwritable")
16+
17+
def "canWrite"() {
18+
setup:
19+
unwritable.parent.toFile().deleteDir()
20+
Files.createDirectories(unwritable.parent)
21+
unwritable.toFile().write("text")
22+
unwritable.toFile().setWritable(true)
23+
24+
expect:
25+
use(PathExtensionsKt) {
26+
path.canWrite() == expected
27+
}
28+
29+
where:
30+
path | expected
31+
unwritable | false
32+
unwritable.resolve("probably/nonexistent") | false
33+
Path.of("relative to project") | true
34+
tmpdir.resolve("./foo/bar/../..") | true
35+
tmpdir | true
36+
tmpdir.resolve("some/nested/non-existent/path") | true
37+
tmpdir.resolve("with space") | true
38+
CoderCLIManager.getConfigDir() | true
39+
CoderCLIManager.getDataDir() | true
40+
}
41+
}

0 commit comments

Comments
 (0)