mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22:58 +08:00
feat: supports importing hosts from SSH config (#359)
This commit is contained in:
@@ -14,6 +14,7 @@ import org.apache.commons.io.FilenameUtils
|
|||||||
import org.apache.commons.io.filefilter.FileFilterUtils
|
import org.apache.commons.io.filefilter.FileFilterUtils
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
|
import org.apache.sshd.client.config.hosts.HostConfigEntry
|
||||||
import org.ini4j.Ini
|
import org.ini4j.Ini
|
||||||
import org.ini4j.Reg
|
import org.ini4j.Reg
|
||||||
import org.jdesktop.swingx.action.ActionManager
|
import org.jdesktop.swingx.action.ActionManager
|
||||||
@@ -185,8 +186,10 @@ class NewHostTree : SimpleTree() {
|
|||||||
val finalShellMenu = importMenu.add("FinalShell")
|
val finalShellMenu = importMenu.add("FinalShell")
|
||||||
val windTermMenu = importMenu.add("WindTerm")
|
val windTermMenu = importMenu.add("WindTerm")
|
||||||
val secureCRTMenu = importMenu.add("SecureCRT")
|
val secureCRTMenu = importMenu.add("SecureCRT")
|
||||||
|
val sshMenu = importMenu.add(".ssh/config")
|
||||||
val mobaXtermMenu = importMenu.add("MobaXterm")
|
val mobaXtermMenu = importMenu.add("MobaXterm")
|
||||||
|
|
||||||
|
|
||||||
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
||||||
val openWith = popupMenu.add(JMenu(I18n.getString("termora.welcome.contextmenu.connect-with"))) as JMenu
|
val openWith = popupMenu.add(JMenu(I18n.getString("termora.welcome.contextmenu.connect-with"))) as JMenu
|
||||||
val openWithSFTP = openWith.add("SFTP")
|
val openWithSFTP = openWith.add("SFTP")
|
||||||
@@ -218,6 +221,7 @@ class NewHostTree : SimpleTree() {
|
|||||||
secureCRTMenu.addActionListener { importHosts(lastNode, ImportType.SecureCRT) }
|
secureCRTMenu.addActionListener { importHosts(lastNode, ImportType.SecureCRT) }
|
||||||
electermMenu.addActionListener { importHosts(lastNode, ImportType.electerm) }
|
electermMenu.addActionListener { importHosts(lastNode, ImportType.electerm) }
|
||||||
mobaXtermMenu.addActionListener { importHosts(lastNode, ImportType.MobaXterm) }
|
mobaXtermMenu.addActionListener { importHosts(lastNode, ImportType.MobaXterm) }
|
||||||
|
sshMenu.addActionListener { importHosts(lastNode, ImportType.SSH) }
|
||||||
finalShellMenu.addActionListener { importHosts(lastNode, ImportType.FinalShell) }
|
finalShellMenu.addActionListener { importHosts(lastNode, ImportType.FinalShell) }
|
||||||
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
|
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
|
||||||
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
||||||
@@ -428,6 +432,7 @@ class NewHostTree : SimpleTree() {
|
|||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
ImportType.WindTerm -> chooser.fileFilter = FileNameExtensionFilter("WindTerm (*.sessions)", "sessions")
|
ImportType.WindTerm -> chooser.fileFilter = FileNameExtensionFilter("WindTerm (*.sessions)", "sessions")
|
||||||
|
ImportType.SSH -> chooser.fileFilter = FileNameExtensionFilter("SSH (config)", "config")
|
||||||
ImportType.CSV -> chooser.fileFilter = FileNameExtensionFilter("CSV (*.csv)", "csv")
|
ImportType.CSV -> chooser.fileFilter = FileNameExtensionFilter("CSV (*.csv)", "csv")
|
||||||
ImportType.SecureCRT -> chooser.fileFilter = FileNameExtensionFilter("SecureCRT (*.xml)", "xml")
|
ImportType.SecureCRT -> chooser.fileFilter = FileNameExtensionFilter("SecureCRT (*.xml)", "xml")
|
||||||
ImportType.electerm -> chooser.fileFilter = FileNameExtensionFilter("electerm (*.json)", "json")
|
ImportType.electerm -> chooser.fileFilter = FileNameExtensionFilter("electerm (*.json)", "json")
|
||||||
@@ -493,19 +498,23 @@ class NewHostTree : SimpleTree() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 选择文件
|
// 选择文件
|
||||||
|
if (type != ImportType.SSH) {
|
||||||
val code = chooser.showOpenDialog(owner)
|
val code = chooser.showOpenDialog(owner)
|
||||||
|
|
||||||
if (code != JFileChooser.APPROVE_OPTION) {
|
if (code != JFileChooser.APPROVE_OPTION) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val file = chooser.selectedFile
|
val file = chooser.selectedFile
|
||||||
|
if (file != null && file.parentFile != null) {
|
||||||
properties.putString(
|
properties.putString(
|
||||||
"NewHostTree.ImportHosts.defaultDir",
|
"NewHostTree.ImportHosts.defaultDir",
|
||||||
(if (FileUtils.isDirectory(file)) file else file.parentFile).absolutePath
|
(if (FileUtils.isDirectory(file)) file else file.parentFile).absolutePath
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val nodes = when (type) {
|
val nodes = when (type) {
|
||||||
|
ImportType.SSH -> parseFromSSH(folder)
|
||||||
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
||||||
ImportType.SecureCRT -> parseFromSecureCRT(folder, file)
|
ImportType.SecureCRT -> parseFromSecureCRT(folder, file)
|
||||||
ImportType.MobaXterm -> parseFromMobaXterm(folder, file)
|
ImportType.MobaXterm -> parseFromMobaXterm(folder, file)
|
||||||
@@ -537,6 +546,9 @@ class NewHostTree : SimpleTree() {
|
|||||||
|
|
||||||
// 重新加载
|
// 重新加载
|
||||||
model.reload(folder)
|
model.reload(folder)
|
||||||
|
|
||||||
|
// expand root
|
||||||
|
expandPath(TreePath(model.getPathToRoot(folder)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFromWindTerm(folder: HostTreeNode, file: File): List<HostTreeNode> {
|
private fun parseFromWindTerm(folder: HostTreeNode, file: File): List<HostTreeNode> {
|
||||||
@@ -562,6 +574,26 @@ class NewHostTree : SimpleTree() {
|
|||||||
return parseFromCSV(folder, StringReader(sw.toString()))
|
return parseFromCSV(folder, StringReader(sw.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseFromSSH(folder: HostTreeNode): List<HostTreeNode> {
|
||||||
|
val entries = HostConfigEntry.readHostConfigEntries(HostConfigEntry.getDefaultHostConfigFile())
|
||||||
|
|
||||||
|
val sw = StringWriter()
|
||||||
|
CSVPrinter(sw, CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).get()).use { printer ->
|
||||||
|
for (entry in entries) {
|
||||||
|
printer.printRecord(
|
||||||
|
StringUtils.EMPTY,
|
||||||
|
StringUtils.defaultString(entry.host),
|
||||||
|
StringUtils.defaultString(entry.hostName),
|
||||||
|
if (entry.port == 0) 22 else entry.port,
|
||||||
|
StringUtils.defaultString(entry.username),
|
||||||
|
"SSH"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFromCSV(folder, StringReader(sw.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseFromSecureCRT(folder: HostTreeNode, file: File): List<HostTreeNode> {
|
private fun parseFromSecureCRT(folder: HostTreeNode, file: File): List<HostTreeNode> {
|
||||||
val xPath = XPathFactory.newInstance().newXPath()
|
val xPath = XPathFactory.newInstance().newXPath()
|
||||||
val db = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
val db = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
@@ -861,6 +893,7 @@ class NewHostTree : SimpleTree() {
|
|||||||
PuTTY,
|
PuTTY,
|
||||||
SecureCRT,
|
SecureCRT,
|
||||||
MobaXterm,
|
MobaXterm,
|
||||||
|
SSH,
|
||||||
FinalShell,
|
FinalShell,
|
||||||
electerm,
|
electerm,
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/test/kotlin/app/termora/HostConfigEntryTest.kt
Normal file
14
src/test/kotlin/app/termora/HostConfigEntryTest.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import org.apache.sshd.client.config.hosts.HostConfigEntry
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class HostConfigEntryTest {
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
val entries = HostConfigEntry.readHostConfigEntries(HostConfigEntry.getDefaultHostConfigFile())
|
||||||
|
for (entry in entries) {
|
||||||
|
println(entry.host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user