mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: supports importing hosts from CSV (#291)
This commit is contained in:
@@ -38,6 +38,10 @@ commons-text 1.13.0
|
||||
Apache License 2.0
|
||||
https://github.com/apache/commons-text/blob/master/LICENSE.txt
|
||||
|
||||
commons-csv 1.13.0
|
||||
Apache License 2.0
|
||||
https://github.com/apache/commons-csv/blob/master/LICENSE.txt
|
||||
|
||||
eddsa 0.3.0
|
||||
Creative Commons Zero v1.0 Universal
|
||||
https://github.com/str4d/ed25519-java/blob/master/LICENSE.txt
|
||||
|
||||
@@ -59,6 +59,7 @@ dependencies {
|
||||
implementation(libs.commons.codec)
|
||||
implementation(libs.commons.io)
|
||||
implementation(libs.commons.lang3)
|
||||
implementation(libs.commons.csv)
|
||||
implementation(libs.commons.net)
|
||||
implementation(libs.commons.text)
|
||||
implementation(libs.commons.compress)
|
||||
|
||||
@@ -9,6 +9,7 @@ trove4j = "1.0.20200330"
|
||||
kotlinx-serialization-json = "1.8.0"
|
||||
commons-codec = "1.18.0"
|
||||
commons-lang3 = "3.17.0"
|
||||
commons-csv = "1.13.0"
|
||||
commons-net = "3.11.1"
|
||||
commons-text = "1.13.0"
|
||||
commons-compress = "1.27.1"
|
||||
@@ -41,7 +42,7 @@ rhino = "1.7.15"
|
||||
delight-rhino-sandbox = "0.0.17"
|
||||
testcontainers = "1.20.4"
|
||||
mixpanel = "1.5.3"
|
||||
jSerialComm="2.11.0"
|
||||
jSerialComm = "2.11.0"
|
||||
|
||||
[libraries]
|
||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||
@@ -53,6 +54,7 @@ tinylog-impl = { group = "org.tinylog", name = "tinylog-impl", version.ref = "ti
|
||||
commons-codec = { group = "commons-codec", name = "commons-codec", version.ref = "commons-codec" }
|
||||
commons-net = { group = "commons-net", name = "commons-net", version.ref = "commons-net" }
|
||||
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" }
|
||||
commons-csv = { group = "org.apache.commons", name = "commons-csv", version.ref = "commons-csv" }
|
||||
commons-text = { group = "org.apache.commons", name = "commons-text", version.ref = "commons-text" }
|
||||
commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commons-compress" }
|
||||
pty4j = { group = "org.jetbrains.pty4j", name = "pty4j", version.ref = "pty4j" }
|
||||
|
||||
@@ -17,7 +17,11 @@ class HostManager private constructor() {
|
||||
fun addHost(host: Host) {
|
||||
assertEventDispatchThread()
|
||||
database.addHost(host)
|
||||
setHost(host)
|
||||
if (host.deleted) {
|
||||
hosts.entries.removeIf { it.value.id == host.id || it.value.parentId == host.id }
|
||||
} else {
|
||||
hosts[host.id] = host
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,12 +43,4 @@ class HostManager private constructor() {
|
||||
return hosts[id]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 仅修改缓存中的
|
||||
*/
|
||||
fun setHost(host: Host) {
|
||||
assertEventDispatchThread()
|
||||
hosts[host.id] = host
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
package app.termora
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.TreeNode
|
||||
|
||||
class HostTreeNode(host: Host) : DefaultMutableTreeNode(host) {
|
||||
companion object {
|
||||
private val hostManager get() = HostManager.getInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果要重新赋值,记得修改 [Host.updateDate] 否则下次取出时可能时缓存的
|
||||
*/
|
||||
var host: Host
|
||||
get() = hostManager.getHost((userObject as Host).id) ?: userObject as Host
|
||||
set(value) {
|
||||
setUserObject(value)
|
||||
hostManager.setHost(value)
|
||||
get() {
|
||||
val cacheHost = hostManager.getHost((userObject as Host).id)
|
||||
val myHost = userObject as Host
|
||||
if (cacheHost == null) {
|
||||
return myHost
|
||||
}
|
||||
return if (cacheHost.updateDate > myHost.updateDate) cacheHost else myHost
|
||||
}
|
||||
set(value) = setUserObject(value)
|
||||
|
||||
val folderCount
|
||||
get() = children().toList().count { if (it is HostTreeNode) it.host.protocol == Protocol.Folder else false }
|
||||
@@ -32,6 +40,29 @@ class HostTreeNode(host: Host) : DefaultMutableTreeNode(host) {
|
||||
return children
|
||||
}
|
||||
|
||||
fun childrenNode(): List<HostTreeNode> {
|
||||
return children?.map { it as HostTreeNode } ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 深度克隆
|
||||
* @param scopes 克隆的范围
|
||||
*/
|
||||
fun clone(scopes: Set<Protocol> = emptySet()): HostTreeNode {
|
||||
val newNode = clone() as HostTreeNode
|
||||
deepClone(newNode, this, scopes)
|
||||
return newNode
|
||||
}
|
||||
|
||||
private fun deepClone(newNode: HostTreeNode, oldNode: HostTreeNode, scopes: Set<Protocol> = emptySet()) {
|
||||
for (child in oldNode.childrenNode()) {
|
||||
if (scopes.isNotEmpty() && !scopes.contains(child.host.protocol)) continue
|
||||
val newChildNode = child.clone() as HostTreeNode
|
||||
deepClone(newChildNode, child, scopes)
|
||||
newNode.add(newChildNode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clone(): Any {
|
||||
val newNode = HostTreeNode(host)
|
||||
@@ -40,6 +71,17 @@ class HostTreeNode(host: Host) : DefaultMutableTreeNode(host) {
|
||||
return newNode
|
||||
}
|
||||
|
||||
override fun isNodeChild(aNode: TreeNode?): Boolean {
|
||||
if (aNode is HostTreeNode) {
|
||||
for (node in childrenNode()) {
|
||||
if (node.host == aNode.host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.isNodeChild(aNode)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
@@ -11,12 +11,16 @@ import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import org.apache.commons.csv.CSVFormat
|
||||
import org.apache.commons.csv.CSVParser
|
||||
import org.apache.commons.csv.CSVPrinter
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||
import org.jdesktop.swingx.JXTree
|
||||
import org.jdesktop.swingx.action.ActionManager
|
||||
import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
@@ -26,7 +30,7 @@ import java.awt.event.ActionEvent
|
||||
import java.awt.event.ActionListener
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.io.File
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
import javax.swing.*
|
||||
@@ -41,6 +45,11 @@ import kotlin.math.min
|
||||
|
||||
class NewHostTree : JXTree() {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(NewHostTree::class.java)
|
||||
private val CSV_HEADERS = arrayOf("Folders", "Label", "Hostname", "Port", "Username", "Protocol")
|
||||
}
|
||||
|
||||
private val tree = this
|
||||
private val editor = OutlineTextField(64)
|
||||
private val hostManager get() = HostManager.getInstance()
|
||||
@@ -207,7 +216,7 @@ class NewHostTree : JXTree() {
|
||||
if (lastHost !is HostTreeNode || editor.text.isBlank() || editor.text == lastHost.host.name) {
|
||||
return
|
||||
}
|
||||
lastHost.host = lastHost.host.copy(name = editor.text)
|
||||
lastHost.host = lastHost.host.copy(name = editor.text, updateDate = System.currentTimeMillis())
|
||||
hostManager.addHost(lastHost.host)
|
||||
}
|
||||
|
||||
@@ -298,7 +307,7 @@ class NewHostTree : JXTree() {
|
||||
// 转移
|
||||
for (e in nodes) {
|
||||
model.removeNodeFromParent(e)
|
||||
e.host = e.host.copy(parentId = node.host.id)
|
||||
e.host = e.host.copy(parentId = node.host.id, updateDate = System.currentTimeMillis())
|
||||
hostManager.addHost(e.host)
|
||||
|
||||
if (dropLocation.childIndex == -1) {
|
||||
@@ -347,6 +356,7 @@ class NewHostTree : JXTree() {
|
||||
val newFolder = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.folder"))
|
||||
val newHost = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.host"))
|
||||
val importMenu = JMenu(I18n.getString("termora.welcome.contextmenu.import"))
|
||||
val csvMenu = importMenu.add("CSV")
|
||||
val windTermMenu = importMenu.add("WindTerm")
|
||||
|
||||
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
||||
@@ -375,6 +385,9 @@ class NewHostTree : JXTree() {
|
||||
popupMenu.add(showMoreInfo)
|
||||
val property = popupMenu.add(I18n.getString("termora.welcome.contextmenu.property"))
|
||||
|
||||
csvMenu.addActionListener {
|
||||
importHosts(lastNode, ImportType.CSV)
|
||||
}
|
||||
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
||||
open.addActionListener { openHosts(it, false) }
|
||||
openInNewWindow.addActionListener { openHosts(it, true) }
|
||||
@@ -604,6 +617,17 @@ class NewHostTree : JXTree() {
|
||||
}
|
||||
|
||||
private fun importHosts(folder: HostTreeNode, type: ImportType) {
|
||||
try {
|
||||
doImportHosts(folder, type)
|
||||
} catch (e: Exception) {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
OptionPane.showMessageDialog(owner, ExceptionUtils.getMessage(e), messageType = JOptionPane.ERROR_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doImportHosts(folder: HostTreeNode, type: ImportType) {
|
||||
val chooser = JFileChooser()
|
||||
chooser.fileSelectionMode = JFileChooser.FILES_ONLY
|
||||
chooser.isAcceptAllFileFilterUsed = false
|
||||
@@ -611,6 +635,8 @@ class NewHostTree : JXTree() {
|
||||
|
||||
if (type == ImportType.WindTerm) {
|
||||
chooser.fileFilter = FileNameExtensionFilter("WindTerm(*.sessions)", "sessions")
|
||||
} else if (type == ImportType.CSV) {
|
||||
chooser.fileFilter = FileNameExtensionFilter("CSV(*.csv)", "csv")
|
||||
}
|
||||
|
||||
val dir = properties.getString("NewHostTree.ImportHosts.defaultDir", StringUtils.EMPTY)
|
||||
@@ -621,7 +647,47 @@ class NewHostTree : JXTree() {
|
||||
}
|
||||
}
|
||||
|
||||
// csv template
|
||||
if (type == ImportType.CSV) {
|
||||
val code = OptionPane.showConfirmDialog(
|
||||
owner,
|
||||
I18n.getString("termora.welcome.contextmenu.import.csv.download-template"),
|
||||
optionType = JOptionPane.YES_NO_OPTION,
|
||||
messageType = JOptionPane.QUESTION_MESSAGE,
|
||||
options = arrayOf(
|
||||
I18n.getString("termora.welcome.contextmenu.import"),
|
||||
I18n.getString("termora.welcome.contextmenu.download")
|
||||
),
|
||||
initialValue = I18n.getString("termora.welcome.contextmenu.import")
|
||||
)
|
||||
if (code == JOptionPane.DEFAULT_OPTION) {
|
||||
return
|
||||
} else if (code != JOptionPane.YES_OPTION) {
|
||||
chooser.setSelectedFile(File("termora_import.csv"))
|
||||
if (chooser.showSaveDialog(owner) == JFileChooser.APPROVE_OPTION) {
|
||||
CSVPrinter(
|
||||
FileWriter(chooser.selectedFile, Charsets.UTF_8),
|
||||
CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).get()
|
||||
).use { printer ->
|
||||
printer.printRecord("Projects/Dev", "Web Server", "192.168.1.1", "22", "root", "SSH")
|
||||
printer.printRecord("Projects/Prod", "Web Server", "serverhost.com", "2222", "root", "SSH")
|
||||
printer.printRecord(StringUtils.EMPTY, "Web Server", "serverhost.com", "2222", "user", "SSH")
|
||||
}
|
||||
OptionPane.openFileInFolder(
|
||||
owner,
|
||||
chooser.selectedFile,
|
||||
I18n.getString("termora.welcome.contextmenu.import.csv.download-template-done-open-folder"),
|
||||
I18n.getString("termora.welcome.contextmenu.import.csv.download-template-done")
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 选择文件
|
||||
val code = chooser.showOpenDialog(owner)
|
||||
|
||||
// 记住目录
|
||||
properties.putString("NewHostTree.ImportHosts.defaultDir", chooser.currentDirectory.absolutePath)
|
||||
|
||||
if (code != JFileChooser.APPROVE_OPTION) {
|
||||
@@ -630,15 +696,16 @@ class NewHostTree : JXTree() {
|
||||
|
||||
val file = chooser.selectedFile
|
||||
|
||||
val nodes = if (type == ImportType.WindTerm) {
|
||||
parseFromWindTerm(file)
|
||||
} else {
|
||||
emptyList()
|
||||
val nodes = when (type) {
|
||||
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
||||
ImportType.CSV -> file.bufferedReader().use { parseFromCSV(folder, it) }
|
||||
}
|
||||
|
||||
|
||||
for (node in nodes) {
|
||||
node.host = node.host.copy(parentId = folder.host.id)
|
||||
node.host = node.host.copy(parentId = folder.host.id, updateDate = System.currentTimeMillis())
|
||||
if (folder.getIndex(node) != -1) {
|
||||
continue
|
||||
}
|
||||
model.insertNodeInto(
|
||||
node,
|
||||
folder,
|
||||
@@ -650,36 +717,73 @@ class NewHostTree : JXTree() {
|
||||
hostManager.addHost(node.host)
|
||||
node.getAllChildren().forEach { hostManager.addHost(it.host) }
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
model.reload(folder)
|
||||
}
|
||||
|
||||
private fun parseFromWindTerm(file: File): List<HostTreeNode> {
|
||||
private fun parseFromWindTerm(folder: HostTreeNode, file: File): List<HostTreeNode> {
|
||||
val sessions = ohMyJson.runCatching { ohMyJson.parseToJsonElement(file.readText()).jsonArray }
|
||||
.onFailure { OptionPane.showMessageDialog(owner, ExceptionUtils.getMessage(it)) }
|
||||
.getOrNull() ?: return emptyList()
|
||||
val nodes = mutableListOf<HostTreeNode>()
|
||||
|
||||
for (i in 0 until sessions.size) {
|
||||
val json = sessions[i].jsonObject
|
||||
val protocol = json["session.protocol"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
if (protocol != "SSH") continue
|
||||
val label = json["session.label"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val target = json["session.target"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val port = json["session.port"]?.jsonPrimitive?.intOrNull ?: 22
|
||||
val group = json["session.group"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val groups = group.split(">")
|
||||
val sw = StringWriter()
|
||||
CSVPrinter(sw, CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).get()).use { printer ->
|
||||
for (i in 0 until sessions.size) {
|
||||
val json = sessions[i].jsonObject
|
||||
val protocol = json["session.protocol"]?.jsonPrimitive?.content ?: "SSH"
|
||||
if (!StringUtils.equalsIgnoreCase("SSH", protocol)) continue
|
||||
val label = json["session.label"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val target = json["session.target"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val port = json["session.port"]?.jsonPrimitive?.intOrNull ?: 22
|
||||
val group = json["session.group"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
|
||||
val groups = group.split(">")
|
||||
printer.printRecord(groups.joinToString("/"), label, target, port, StringUtils.EMPTY, "SSH")
|
||||
}
|
||||
}
|
||||
|
||||
return parseFromCSV(folder, StringReader(sw.toString()))
|
||||
}
|
||||
|
||||
private fun parseFromCSV(folderNode: HostTreeNode, sr: Reader): List<HostTreeNode> {
|
||||
val records = CSVParser.builder()
|
||||
.setFormat(CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).setSkipHeaderRecord(true).get())
|
||||
.setCharset(Charsets.UTF_8)
|
||||
.setReader(sr)
|
||||
.get()
|
||||
.use { it.records }
|
||||
// 把现有目录提取出来,避免重复创建
|
||||
val nodes = folderNode.clone(setOf(Protocol.Folder))
|
||||
.childrenNode().filter { it.host.protocol == Protocol.Folder }
|
||||
.toMutableList()
|
||||
|
||||
for (record in records) {
|
||||
val map = mutableMapOf<String, String>()
|
||||
for (e in record.parser.headerMap.keys) {
|
||||
map[e] = record.get(e)
|
||||
}
|
||||
|
||||
val folder = map["Folders"] ?: StringUtils.EMPTY
|
||||
val label = map["Label"] ?: StringUtils.EMPTY
|
||||
val hostname = map["Hostname"] ?: StringUtils.EMPTY
|
||||
val port = map["Port"]?.toIntOrNull() ?: 22
|
||||
val username = map["Username"] ?: StringUtils.EMPTY
|
||||
val protocol = map["Protocol"] ?: "SSH"
|
||||
if (!StringUtils.equalsIgnoreCase(protocol, "SSH")) continue
|
||||
if (StringUtils.isAllBlank(hostname, label)) continue
|
||||
|
||||
var p: HostTreeNode? = null
|
||||
if (group.isNotBlank()) {
|
||||
for (j in groups.indices) {
|
||||
if (folder.isNotBlank()) {
|
||||
for ((j, name) in folder.split("/").withIndex()) {
|
||||
val folders = if (j == 0 || p == null) nodes
|
||||
else p.children().toList().filterIsInstance<HostTreeNode>()
|
||||
val n = HostTreeNode(
|
||||
Host(
|
||||
name = groups[j], protocol = Protocol.Folder,
|
||||
name = name, protocol = Protocol.Folder,
|
||||
parentId = p?.host?.id ?: StringUtils.EMPTY
|
||||
)
|
||||
)
|
||||
val cp = folders.find { it.host.protocol == Protocol.Folder && it.host.name == groups[j] }
|
||||
val cp = folders.find { it.host.protocol == Protocol.Folder && it.host.name == name }
|
||||
if (cp != null) {
|
||||
p = cp
|
||||
continue
|
||||
@@ -696,9 +800,10 @@ class NewHostTree : JXTree() {
|
||||
|
||||
val n = HostTreeNode(
|
||||
Host(
|
||||
name = StringUtils.defaultIfBlank(label, target),
|
||||
host = target,
|
||||
name = StringUtils.defaultIfBlank(label, hostname),
|
||||
host = hostname,
|
||||
port = port,
|
||||
username = username,
|
||||
protocol = Protocol.SSH,
|
||||
parentId = p?.host?.id ?: StringUtils.EMPTY,
|
||||
)
|
||||
@@ -716,7 +821,8 @@ class NewHostTree : JXTree() {
|
||||
|
||||
|
||||
private enum class ImportType {
|
||||
WindTerm
|
||||
WindTerm,
|
||||
CSV,
|
||||
}
|
||||
|
||||
private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
|
||||
|
||||
@@ -73,7 +73,7 @@ class NewHostTreeModel : DefaultTreeModel(
|
||||
for ((i, c) in parent.children().toList().filterIsInstance<HostTreeNode>().withIndex()) {
|
||||
val sort = i.toLong()
|
||||
if (c.host.sort == sort) continue
|
||||
c.host = c.host.copy(sort = sort)
|
||||
c.host = c.host.copy(sort = sort, updateDate = System.currentTimeMillis())
|
||||
hostManager.addHost(c.host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
|
||||
// 如果配置了跳板机或者代理,那么通过 SSH 的端口转发到本地
|
||||
if (useJumpHosts) {
|
||||
host = host.copy(
|
||||
updateDate = System.currentTimeMillis(),
|
||||
tunnelings = listOf(
|
||||
Tunneling(
|
||||
type = TunnelingType.Local,
|
||||
@@ -66,7 +67,11 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
|
||||
// 打开通道
|
||||
for (tunneling in host.tunnelings) {
|
||||
val address = SshClients.openTunneling(sshSession, host, tunneling)
|
||||
host = host.copy(host = address.hostName, port = address.port)
|
||||
host = host.copy(
|
||||
host = address.hostName,
|
||||
port = address.port,
|
||||
updateDate = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +133,9 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
|
||||
private fun setAuthentication(commands: MutableList<String>, host: Host) {
|
||||
// 如果通过公钥连接
|
||||
if (host.authentication.type == AuthenticationType.PublicKey) {
|
||||
val keyPair = keyManager.getOhKeyPair(host.authentication.password)
|
||||
if (keyPair != null) {
|
||||
val keyPair = OhKeyPairKeyPairProvider.generateKeyPair(keyPair)
|
||||
val ohKeyPair = keyManager.getOhKeyPair(host.authentication.password)
|
||||
if (ohKeyPair != null) {
|
||||
val keyPair = OhKeyPairKeyPairProvider.generateKeyPair(ohKeyPair)
|
||||
val privateKeyPath = Application.createSubTemporaryDir()
|
||||
val privateKeyFile = Files.createTempFile(privateKeyPath, Application.getName(), StringUtils.EMPTY)
|
||||
Files.newOutputStream(privateKeyFile)
|
||||
|
||||
@@ -89,7 +89,8 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
terminal.write("SSH client is opening...\r\n")
|
||||
}
|
||||
|
||||
var host = this.host.copy(authentication = this.host.authentication.copy())
|
||||
var host =
|
||||
this.host.copy(authentication = this.host.authentication.copy(), updateDate = System.currentTimeMillis())
|
||||
val owner = SwingUtilities.getWindowAncestor(terminalPanel)
|
||||
val client = SshClients.openClient(host).also { sshClient = it }
|
||||
client.serverKeyVerifier = DialogServerKeyVerifier(owner)
|
||||
@@ -103,13 +104,14 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
host = host.copy(
|
||||
authentication = authentication,
|
||||
username = dialog.getUsername(),
|
||||
updateDate = System.currentTimeMillis(),
|
||||
)
|
||||
// save
|
||||
if (dialog.isRemembered()) {
|
||||
HostManager.getInstance().addHost(
|
||||
tab.host.copy(
|
||||
authentication = authentication,
|
||||
username = dialog.getUsername(),
|
||||
username = dialog.getUsername(), updateDate = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ object SshClients {
|
||||
log.info("jump host: ${currentHost.host}:${currentHost.port} , next host: ${nextHost.host}:${nextHost.port} , local address: ${address.hostName}:${address.port}")
|
||||
}
|
||||
// 映射完毕之后修改Host和端口
|
||||
jumpHosts[i + 1] = nextHost.copy(host = address.hostName, port = address.port)
|
||||
jumpHosts[i + 1] = nextHost.copy(host = address.hostName, port = address.port, updateDate = System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ class TerminalTabbed(
|
||||
}
|
||||
|
||||
host = host.copy(
|
||||
protocol = Protocol.SFTPPty,
|
||||
protocol = Protocol.SFTPPty, updateDate = System.currentTimeMillis(),
|
||||
options = host.options.copy(env = envs.toPropertiesString())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class SFTPAction : AnAction("SFTP", Icons.folder) {
|
||||
val tab = openOrCreateSFTPTerminalTab(evt) ?: return
|
||||
|
||||
if (host != null) {
|
||||
connectHost(host.copy(protocol = Protocol.SSH), tab)
|
||||
connectHost(host.copy(protocol = Protocol.SSH, updateDate = System.currentTimeMillis()), tab)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ class SftpFileSystemPanel(
|
||||
private suspend fun doConnect() {
|
||||
|
||||
val thisHost = this.host ?: return
|
||||
var host = thisHost.copy(authentication = thisHost.authentication.copy())
|
||||
var host = thisHost.copy(authentication = thisHost.authentication.copy(), updateDate = System.currentTimeMillis())
|
||||
|
||||
closeIO()
|
||||
|
||||
@@ -123,14 +123,14 @@ class SftpFileSystemPanel(
|
||||
val authentication = dialog.getAuthentication()
|
||||
host = host.copy(
|
||||
authentication = authentication,
|
||||
username = dialog.getUsername(),
|
||||
username = dialog.getUsername(), updateDate = System.currentTimeMillis(),
|
||||
)
|
||||
// save
|
||||
if (dialog.isRemembered()) {
|
||||
HostManager.getInstance().addHost(
|
||||
host.copy(
|
||||
authentication = authentication,
|
||||
username = dialog.getUsername(),
|
||||
username = dialog.getUsername(), updateDate = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ termora.welcome.contextmenu.new.host=Host
|
||||
termora.welcome.contextmenu.new.folder.name=New Folder
|
||||
termora.welcome.contextmenu.property=Properties
|
||||
termora.welcome.contextmenu.show-more-info=Show more info
|
||||
termora.welcome.contextmenu.download=Download
|
||||
termora.welcome.contextmenu.import.csv.download-template=Do you want to import or download the template?
|
||||
termora.welcome.contextmenu.import.csv.download-template-done=Download the template successfully
|
||||
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=Download the template successfully, Do you want to open the folder?
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=Create a new host
|
||||
|
||||
@@ -132,6 +132,11 @@ termora.welcome.contextmenu.new.folder.name=新建文件夹
|
||||
termora.welcome.contextmenu.property=属性
|
||||
termora.welcome.contextmenu.show-more-info=显示更多信息
|
||||
|
||||
termora.welcome.contextmenu.download=下载
|
||||
termora.welcome.contextmenu.import.csv.download-template=您要导入还是下载模板?
|
||||
termora.welcome.contextmenu.import.csv.download-template-done=下载成功
|
||||
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下载成功, 是否需要打开所在文件夹?
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=新建主机
|
||||
termora.new-host.general=属性
|
||||
|
||||
@@ -130,6 +130,10 @@ termora.welcome.contextmenu.new.host=主機
|
||||
termora.welcome.contextmenu.new.folder.name=新建資料夾
|
||||
termora.welcome.contextmenu.property=屬性
|
||||
termora.welcome.contextmenu.show-more-info=顯示更多信息
|
||||
termora.welcome.contextmenu.download=下載
|
||||
termora.welcome.contextmenu.import.csv.download-template=您要匯入還是下載範本?
|
||||
termora.welcome.contextmenu.import.csv.download-template-done=下載成功
|
||||
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下載成功, 是否需要開啟所在資料夾?
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=新主機
|
||||
|
||||
Reference in New Issue
Block a user