feat: supports importing hosts from SSH config (#359)

This commit is contained in:
hstyi
2025-03-14 00:03:02 +08:00
committed by GitHub
parent 98ac2928b4
commit b283a3ea38
2 changed files with 55 additions and 8 deletions

View File

@@ -14,6 +14,7 @@ import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.filefilter.FileFilterUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import org.apache.sshd.client.config.hosts.HostConfigEntry
import org.ini4j.Ini
import org.ini4j.Reg
import org.jdesktop.swingx.action.ActionManager
@@ -185,8 +186,10 @@ class NewHostTree : SimpleTree() {
val finalShellMenu = importMenu.add("FinalShell")
val windTermMenu = importMenu.add("WindTerm")
val secureCRTMenu = importMenu.add("SecureCRT")
val sshMenu = importMenu.add(".ssh/config")
val mobaXtermMenu = importMenu.add("MobaXterm")
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 openWithSFTP = openWith.add("SFTP")
@@ -218,6 +221,7 @@ class NewHostTree : SimpleTree() {
secureCRTMenu.addActionListener { importHosts(lastNode, ImportType.SecureCRT) }
electermMenu.addActionListener { importHosts(lastNode, ImportType.electerm) }
mobaXtermMenu.addActionListener { importHosts(lastNode, ImportType.MobaXterm) }
sshMenu.addActionListener { importHosts(lastNode, ImportType.SSH) }
finalShellMenu.addActionListener { importHosts(lastNode, ImportType.FinalShell) }
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
@@ -428,6 +432,7 @@ class NewHostTree : SimpleTree() {
when (type) {
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.SecureCRT -> chooser.fileFilter = FileNameExtensionFilter("SecureCRT (*.xml)", "xml")
ImportType.electerm -> chooser.fileFilter = FileNameExtensionFilter("electerm (*.json)", "json")
@@ -493,19 +498,23 @@ class NewHostTree : SimpleTree() {
}
// 选择文件
val code = chooser.showOpenDialog(owner)
if (code != JFileChooser.APPROVE_OPTION) {
return
if (type != ImportType.SSH) {
val code = chooser.showOpenDialog(owner)
if (code != JFileChooser.APPROVE_OPTION) {
return
}
}
val file = chooser.selectedFile
properties.putString(
"NewHostTree.ImportHosts.defaultDir",
(if (FileUtils.isDirectory(file)) file else file.parentFile).absolutePath
)
if (file != null && file.parentFile != null) {
properties.putString(
"NewHostTree.ImportHosts.defaultDir",
(if (FileUtils.isDirectory(file)) file else file.parentFile).absolutePath
)
}
val nodes = when (type) {
ImportType.SSH -> parseFromSSH(folder)
ImportType.WindTerm -> parseFromWindTerm(folder, file)
ImportType.SecureCRT -> parseFromSecureCRT(folder, file)
ImportType.MobaXterm -> parseFromMobaXterm(folder, file)
@@ -537,6 +546,9 @@ class NewHostTree : SimpleTree() {
// 重新加载
model.reload(folder)
// expand root
expandPath(TreePath(model.getPathToRoot(folder)))
}
private fun parseFromWindTerm(folder: HostTreeNode, file: File): List<HostTreeNode> {
@@ -562,6 +574,26 @@ class NewHostTree : SimpleTree() {
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> {
val xPath = XPathFactory.newInstance().newXPath()
val db = DocumentBuilderFactory.newInstance().newDocumentBuilder()
@@ -861,6 +893,7 @@ class NewHostTree : SimpleTree() {
PuTTY,
SecureCRT,
MobaXterm,
SSH,
FinalShell,
electerm,
}

View 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)
}
}
}