mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: supports importing hosts from Xshell (#292)
This commit is contained in:
@@ -42,6 +42,10 @@ commons-csv 1.13.0
|
||||
Apache License 2.0
|
||||
https://github.com/apache/commons-csv/blob/master/LICENSE.txt
|
||||
|
||||
ini4j 0.5.5-2
|
||||
Apache License 2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
||||
eddsa 0.3.0
|
||||
Creative Commons Zero v1.0 Universal
|
||||
https://github.com/str4d/ed25519-java/blob/master/LICENSE.txt
|
||||
|
||||
@@ -108,6 +108,7 @@ dependencies {
|
||||
implementation(libs.colorpicker)
|
||||
implementation(libs.mixpanel)
|
||||
implementation(libs.jSerialComm)
|
||||
implementation(libs.ini4j)
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -43,6 +43,7 @@ delight-rhino-sandbox = "0.0.17"
|
||||
testcontainers = "1.20.4"
|
||||
mixpanel = "1.5.3"
|
||||
jSerialComm = "2.11.0"
|
||||
ini4j = "0.5.5-2"
|
||||
|
||||
[libraries]
|
||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||
@@ -58,6 +59,7 @@ commons-csv = { group = "org.apache.commons", name = "commons-csv", version.ref
|
||||
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" }
|
||||
ini4j = { module = "org.jetbrains.intellij.deps:ini4j", version.ref = "ini4j" }
|
||||
flatlaf = { group = "com.formdev", name = "flatlaf", version.ref = "flatlaf" }
|
||||
flatlaf-extras = { group = "com.formdev", name = "flatlaf-extras", version.ref = "flatlaf" }
|
||||
trove4j = { group = "org.jetbrains.intellij.deps", name = "trove4j", version.ref = "trove4j" }
|
||||
|
||||
@@ -15,8 +15,10 @@ 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.io.FilenameUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||
import org.ini4j.Ini
|
||||
import org.jdesktop.swingx.JXTree
|
||||
import org.jdesktop.swingx.action.ActionManager
|
||||
import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer
|
||||
@@ -357,6 +359,7 @@ class NewHostTree : JXTree() {
|
||||
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 XshellMenu = importMenu.add("Xshell")
|
||||
val windTermMenu = importMenu.add("WindTerm")
|
||||
|
||||
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
||||
@@ -385,9 +388,8 @@ class NewHostTree : JXTree() {
|
||||
popupMenu.add(showMoreInfo)
|
||||
val property = popupMenu.add(I18n.getString("termora.welcome.contextmenu.property"))
|
||||
|
||||
csvMenu.addActionListener {
|
||||
importHosts(lastNode, ImportType.CSV)
|
||||
}
|
||||
XshellMenu.addActionListener { importHosts(lastNode, ImportType.Xshell) }
|
||||
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
|
||||
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
||||
open.addActionListener { openHosts(it, false) }
|
||||
openInNewWindow.addActionListener { openHosts(it, true) }
|
||||
@@ -633,10 +635,14 @@ class NewHostTree : JXTree() {
|
||||
chooser.isAcceptAllFileFilterUsed = false
|
||||
chooser.isMultiSelectionEnabled = false
|
||||
|
||||
if (type == ImportType.WindTerm) {
|
||||
chooser.fileFilter = FileNameExtensionFilter("WindTerm(*.sessions)", "sessions")
|
||||
} else if (type == ImportType.CSV) {
|
||||
chooser.fileFilter = FileNameExtensionFilter("CSV(*.csv)", "csv")
|
||||
when (type) {
|
||||
ImportType.WindTerm -> chooser.fileFilter = FileNameExtensionFilter("WindTerm (*.sessions)", "sessions")
|
||||
ImportType.CSV -> chooser.fileFilter = FileNameExtensionFilter("CSV (*.csv)", "csv")
|
||||
ImportType.Xshell -> {
|
||||
chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
|
||||
chooser.dialogTitle = "Xshell Sessions"
|
||||
chooser.isAcceptAllFileFilterUsed = true
|
||||
}
|
||||
}
|
||||
|
||||
val dir = properties.getString("NewHostTree.ImportHosts.defaultDir", StringUtils.EMPTY)
|
||||
@@ -698,6 +704,7 @@ class NewHostTree : JXTree() {
|
||||
|
||||
val nodes = when (type) {
|
||||
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
||||
ImportType.Xshell -> parseFromXshell(folder, file)
|
||||
ImportType.CSV -> file.bufferedReader().use { parseFromCSV(folder, it) }
|
||||
}
|
||||
|
||||
@@ -745,6 +752,34 @@ class NewHostTree : JXTree() {
|
||||
return parseFromCSV(folder, StringReader(sw.toString()))
|
||||
}
|
||||
|
||||
private fun parseFromXshell(folder: HostTreeNode, dir: File): List<HostTreeNode> {
|
||||
val files = FileUtils.listFiles(dir, arrayOf("xsh"), true)
|
||||
if (files.isEmpty()) {
|
||||
OptionPane.showMessageDialog(
|
||||
owner,
|
||||
I18n.getString("termora.welcome.contextmenu.import.xshell-folder-empty")
|
||||
)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val sw = StringWriter()
|
||||
CSVPrinter(sw, CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).get()).use { printer ->
|
||||
for (file in files) {
|
||||
val ini = Ini(file)
|
||||
val protocol = ini.get("CONNECTION", "Protocol") ?: "SSH"
|
||||
if (!StringUtils.equalsIgnoreCase("SSH", protocol)) continue
|
||||
val folders = FilenameUtils.separatorsToUnix(file.parentFile.relativeTo(dir).toString())
|
||||
val hostname = ini.get("CONNECTION", "Host") ?: StringUtils.EMPTY
|
||||
val label = file.nameWithoutExtension
|
||||
val port = ini.get("CONNECTION", "Port")?.toIntOrNull() ?: 22
|
||||
val username = ini.get("CONNECTION:AUTHENTICATION", "UserName") ?: StringUtils.EMPTY
|
||||
printer.printRecord(folders, label, hostname, port, username, "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())
|
||||
@@ -823,6 +858,7 @@ class NewHostTree : JXTree() {
|
||||
private enum class ImportType {
|
||||
WindTerm,
|
||||
CSV,
|
||||
Xshell,
|
||||
}
|
||||
|
||||
private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
|
||||
|
||||
@@ -148,6 +148,7 @@ 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?
|
||||
termora.welcome.contextmenu.import.xshell-folder-empty=The folder does not contain any *.xsh files, Please select the correct Xshell Sessions directory
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=Create a new host
|
||||
|
||||
@@ -136,6 +136,7 @@ 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=下载成功, 是否需要打开所在文件夹?
|
||||
termora.welcome.contextmenu.import.xshell-folder-empty=该文件夹不包含 *.xsh 文件,请选择正确的 Xshell 会话目录
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=新建主机
|
||||
|
||||
@@ -134,6 +134,7 @@ 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=下載成功, 是否需要開啟所在資料夾?
|
||||
termora.welcome.contextmenu.import.xshell-folder-empty=該資料夾不包含 *.xsh 文件,請選擇正確的 Xshell 會話目錄
|
||||
|
||||
# New Host
|
||||
termora.new-host.title=新主機
|
||||
|
||||
Reference in New Issue
Block a user