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
|
Apache License 2.0
|
||||||
https://github.com/apache/commons-csv/blob/master/LICENSE.txt
|
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
|
eddsa 0.3.0
|
||||||
Creative Commons Zero v1.0 Universal
|
Creative Commons Zero v1.0 Universal
|
||||||
https://github.com/str4d/ed25519-java/blob/master/LICENSE.txt
|
https://github.com/str4d/ed25519-java/blob/master/LICENSE.txt
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ dependencies {
|
|||||||
implementation(libs.colorpicker)
|
implementation(libs.colorpicker)
|
||||||
implementation(libs.mixpanel)
|
implementation(libs.mixpanel)
|
||||||
implementation(libs.jSerialComm)
|
implementation(libs.jSerialComm)
|
||||||
|
implementation(libs.ini4j)
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ delight-rhino-sandbox = "0.0.17"
|
|||||||
testcontainers = "1.20.4"
|
testcontainers = "1.20.4"
|
||||||
mixpanel = "1.5.3"
|
mixpanel = "1.5.3"
|
||||||
jSerialComm = "2.11.0"
|
jSerialComm = "2.11.0"
|
||||||
|
ini4j = "0.5.5-2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
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-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" }
|
commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commons-compress" }
|
||||||
pty4j = { group = "org.jetbrains.pty4j", name = "pty4j", version.ref = "pty4j" }
|
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 = { group = "com.formdev", name = "flatlaf", version.ref = "flatlaf" }
|
||||||
flatlaf-extras = { group = "com.formdev", name = "flatlaf-extras", 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" }
|
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.CSVParser
|
||||||
import org.apache.commons.csv.CSVPrinter
|
import org.apache.commons.csv.CSVPrinter
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.io.FilenameUtils
|
||||||
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.ini4j.Ini
|
||||||
import org.jdesktop.swingx.JXTree
|
import org.jdesktop.swingx.JXTree
|
||||||
import org.jdesktop.swingx.action.ActionManager
|
import org.jdesktop.swingx.action.ActionManager
|
||||||
import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer
|
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 newHost = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.host"))
|
||||||
val importMenu = JMenu(I18n.getString("termora.welcome.contextmenu.import"))
|
val importMenu = JMenu(I18n.getString("termora.welcome.contextmenu.import"))
|
||||||
val csvMenu = importMenu.add("CSV")
|
val csvMenu = importMenu.add("CSV")
|
||||||
|
val XshellMenu = importMenu.add("Xshell")
|
||||||
val windTermMenu = importMenu.add("WindTerm")
|
val windTermMenu = importMenu.add("WindTerm")
|
||||||
|
|
||||||
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
|
||||||
@@ -385,9 +388,8 @@ class NewHostTree : JXTree() {
|
|||||||
popupMenu.add(showMoreInfo)
|
popupMenu.add(showMoreInfo)
|
||||||
val property = popupMenu.add(I18n.getString("termora.welcome.contextmenu.property"))
|
val property = popupMenu.add(I18n.getString("termora.welcome.contextmenu.property"))
|
||||||
|
|
||||||
csvMenu.addActionListener {
|
XshellMenu.addActionListener { importHosts(lastNode, ImportType.Xshell) }
|
||||||
importHosts(lastNode, ImportType.CSV)
|
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
|
||||||
}
|
|
||||||
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
|
||||||
open.addActionListener { openHosts(it, false) }
|
open.addActionListener { openHosts(it, false) }
|
||||||
openInNewWindow.addActionListener { openHosts(it, true) }
|
openInNewWindow.addActionListener { openHosts(it, true) }
|
||||||
@@ -633,10 +635,14 @@ class NewHostTree : JXTree() {
|
|||||||
chooser.isAcceptAllFileFilterUsed = false
|
chooser.isAcceptAllFileFilterUsed = false
|
||||||
chooser.isMultiSelectionEnabled = false
|
chooser.isMultiSelectionEnabled = false
|
||||||
|
|
||||||
if (type == ImportType.WindTerm) {
|
when (type) {
|
||||||
chooser.fileFilter = FileNameExtensionFilter("WindTerm(*.sessions)", "sessions")
|
ImportType.WindTerm -> chooser.fileFilter = FileNameExtensionFilter("WindTerm (*.sessions)", "sessions")
|
||||||
} else if (type == ImportType.CSV) {
|
ImportType.CSV -> chooser.fileFilter = FileNameExtensionFilter("CSV (*.csv)", "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)
|
val dir = properties.getString("NewHostTree.ImportHosts.defaultDir", StringUtils.EMPTY)
|
||||||
@@ -698,6 +704,7 @@ class NewHostTree : JXTree() {
|
|||||||
|
|
||||||
val nodes = when (type) {
|
val nodes = when (type) {
|
||||||
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
ImportType.WindTerm -> parseFromWindTerm(folder, file)
|
||||||
|
ImportType.Xshell -> parseFromXshell(folder, file)
|
||||||
ImportType.CSV -> file.bufferedReader().use { parseFromCSV(folder, it) }
|
ImportType.CSV -> file.bufferedReader().use { parseFromCSV(folder, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,6 +752,34 @@ class NewHostTree : JXTree() {
|
|||||||
return parseFromCSV(folder, StringReader(sw.toString()))
|
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> {
|
private fun parseFromCSV(folderNode: HostTreeNode, sr: Reader): List<HostTreeNode> {
|
||||||
val records = CSVParser.builder()
|
val records = CSVParser.builder()
|
||||||
.setFormat(CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).setSkipHeaderRecord(true).get())
|
.setFormat(CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).setSkipHeaderRecord(true).get())
|
||||||
@@ -823,6 +858,7 @@ class NewHostTree : JXTree() {
|
|||||||
private enum class ImportType {
|
private enum class ImportType {
|
||||||
WindTerm,
|
WindTerm,
|
||||||
CSV,
|
CSV,
|
||||||
|
Xshell,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
|
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=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=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.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
|
# New Host
|
||||||
termora.new-host.title=Create a 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=您要导入还是下载模板?
|
||||||
termora.welcome.contextmenu.import.csv.download-template-done=下载成功
|
termora.welcome.contextmenu.import.csv.download-template-done=下载成功
|
||||||
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下载成功, 是否需要打开所在文件夹?
|
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下载成功, 是否需要打开所在文件夹?
|
||||||
|
termora.welcome.contextmenu.import.xshell-folder-empty=该文件夹不包含 *.xsh 文件,请选择正确的 Xshell 会话目录
|
||||||
|
|
||||||
# New Host
|
# New Host
|
||||||
termora.new-host.title=新建主机
|
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=您要匯入還是下載範本?
|
||||||
termora.welcome.contextmenu.import.csv.download-template-done=下載成功
|
termora.welcome.contextmenu.import.csv.download-template-done=下載成功
|
||||||
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下載成功, 是否需要開啟所在資料夾?
|
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下載成功, 是否需要開啟所在資料夾?
|
||||||
|
termora.welcome.contextmenu.import.xshell-folder-empty=該資料夾不包含 *.xsh 文件,請選擇正確的 Xshell 會話目錄
|
||||||
|
|
||||||
# New Host
|
# New Host
|
||||||
termora.new-host.title=新主機
|
termora.new-host.title=新主機
|
||||||
|
|||||||
Reference in New Issue
Block a user