mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: supports importing hosts from WindTerm (#289)
This commit is contained in:
@@ -1,37 +1,50 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import kotlin.system.measureTimeMillis
|
|
||||||
|
|
||||||
|
|
||||||
class HostManager private constructor() {
|
class HostManager private constructor() {
|
||||||
companion object {
|
companion object {
|
||||||
fun getInstance(): HostManager {
|
fun getInstance(): HostManager {
|
||||||
return ApplicationScope.forApplicationScope().getOrCreate(HostManager::class) { HostManager() }
|
return ApplicationScope.forApplicationScope().getOrCreate(HostManager::class) { HostManager() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(HostManager::class.java)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val database get() = Database.getDatabase()
|
private val database get() = Database.getDatabase()
|
||||||
|
private var hosts = mutableMapOf<String, Host>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改缓存并存入数据库
|
||||||
|
*/
|
||||||
fun addHost(host: Host) {
|
fun addHost(host: Host) {
|
||||||
assertEventDispatchThread()
|
assertEventDispatchThread()
|
||||||
database.addHost(host)
|
database.addHost(host)
|
||||||
|
setHost(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第一次调用从数据库中获取,后续从缓存中获取
|
||||||
|
*/
|
||||||
fun hosts(): List<Host> {
|
fun hosts(): List<Host> {
|
||||||
val hosts: List<Host>
|
if (hosts.isEmpty()) {
|
||||||
measureTimeMillis {
|
database.getHosts().filter { !it.deleted }
|
||||||
hosts = database.getHosts()
|
.forEach { hosts[it.id] = it }
|
||||||
.filter { !it.deleted }
|
|
||||||
.sortedWith(compareBy<Host> { if (it.protocol == Protocol.Folder) 0 else 1 }.thenBy { it.sort })
|
|
||||||
}.let {
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("hosts: $it ms")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return hosts
|
return hosts.values.filter { !it.deleted }
|
||||||
|
.sortedWith(compareBy<Host> { if (it.protocol == Protocol.Folder) 0 else 1 }.thenBy { it.sort })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存中获取
|
||||||
|
*/
|
||||||
|
fun getHost(id: String): Host? {
|
||||||
|
return hosts[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅修改缓存中的
|
||||||
|
*/
|
||||||
|
fun setHost(host: Host) {
|
||||||
|
assertEventDispatchThread()
|
||||||
|
hosts[host.id] = host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,16 @@ package app.termora
|
|||||||
import javax.swing.tree.DefaultMutableTreeNode
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
|
||||||
class HostTreeNode(host: Host) : DefaultMutableTreeNode(host) {
|
class HostTreeNode(host: Host) : DefaultMutableTreeNode(host) {
|
||||||
|
companion object {
|
||||||
|
private val hostManager get() = HostManager.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
var host: Host
|
var host: Host
|
||||||
get() = userObject as Host
|
get() = hostManager.getHost((userObject as Host).id) ?: userObject as Host
|
||||||
set(value) = setUserObject(value)
|
set(value) {
|
||||||
|
setUserObject(value)
|
||||||
|
hostManager.setHost(value)
|
||||||
|
}
|
||||||
|
|
||||||
val folderCount
|
val folderCount
|
||||||
get() = children().toList().count { if (it is HostTreeNode) it.host.protocol == Protocol.Folder else false }
|
get() = children().toList().count { if (it is HostTreeNode) it.host.protocol == Protocol.Folder else false }
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.Application.ohMyJson
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.actions.OpenHostAction
|
import app.termora.actions.OpenHostAction
|
||||||
import app.termora.transport.SFTPAction
|
import app.termora.transport.SFTPAction
|
||||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||||
import com.formdev.flatlaf.icons.FlatTreeClosedIcon
|
import com.formdev.flatlaf.icons.FlatTreeClosedIcon
|
||||||
import com.formdev.flatlaf.icons.FlatTreeOpenIcon
|
import com.formdev.flatlaf.icons.FlatTreeOpenIcon
|
||||||
|
import kotlinx.serialization.json.intOrNull
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
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
|
||||||
@@ -19,6 +26,7 @@ import java.awt.event.ActionEvent
|
|||||||
import java.awt.event.ActionListener
|
import java.awt.event.ActionListener
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
@@ -26,6 +34,7 @@ import javax.swing.event.CellEditorListener
|
|||||||
import javax.swing.event.ChangeEvent
|
import javax.swing.event.ChangeEvent
|
||||||
import javax.swing.event.PopupMenuEvent
|
import javax.swing.event.PopupMenuEvent
|
||||||
import javax.swing.event.PopupMenuListener
|
import javax.swing.event.PopupMenuListener
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter
|
||||||
import javax.swing.tree.TreePath
|
import javax.swing.tree.TreePath
|
||||||
import javax.swing.tree.TreeSelectionModel
|
import javax.swing.tree.TreeSelectionModel
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -337,6 +346,8 @@ class NewHostTree : JXTree() {
|
|||||||
val newMenu = JMenu(I18n.getString("termora.welcome.contextmenu.new"))
|
val newMenu = JMenu(I18n.getString("termora.welcome.contextmenu.new"))
|
||||||
val newFolder = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.folder"))
|
val newFolder = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.folder"))
|
||||||
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 windTermMenu = importMenu.add("WindTerm")
|
||||||
|
|
||||||
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
|
||||||
@@ -352,6 +363,7 @@ class NewHostTree : JXTree() {
|
|||||||
val expandAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.expand-all"))
|
val expandAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.expand-all"))
|
||||||
val colspanAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.collapse-all"))
|
val colspanAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.collapse-all"))
|
||||||
popupMenu.addSeparator()
|
popupMenu.addSeparator()
|
||||||
|
popupMenu.add(importMenu)
|
||||||
popupMenu.add(newMenu)
|
popupMenu.add(newMenu)
|
||||||
popupMenu.addSeparator()
|
popupMenu.addSeparator()
|
||||||
val showMoreInfo = JCheckBoxMenuItem(I18n.getString("termora.welcome.contextmenu.show-more-info"))
|
val showMoreInfo = JCheckBoxMenuItem(I18n.getString("termora.welcome.contextmenu.show-more-info"))
|
||||||
@@ -363,6 +375,7 @@ 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"))
|
||||||
|
|
||||||
|
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) }
|
||||||
openWithSFTP.addActionListener { openWithSFTP(it) }
|
openWithSFTP.addActionListener { openWithSFTP(it) }
|
||||||
@@ -396,6 +409,15 @@ class NewHostTree : JXTree() {
|
|||||||
for (c in nodes) {
|
for (c in nodes) {
|
||||||
hostManager.addHost(c.host.copy(deleted = true, updateDate = System.currentTimeMillis()))
|
hostManager.addHost(c.host.copy(deleted = true, updateDate = System.currentTimeMillis()))
|
||||||
model.removeNodeFromParent(c)
|
model.removeNodeFromParent(c)
|
||||||
|
// 将所有子孙也删除
|
||||||
|
for (child in c.getAllChildren()) {
|
||||||
|
hostManager.addHost(
|
||||||
|
child.host.copy(
|
||||||
|
deleted = true,
|
||||||
|
updateDate = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,8 +448,7 @@ class NewHostTree : JXTree() {
|
|||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
val host = (dialog.host ?: return).copy(parentId = lastHost.id)
|
val host = (dialog.host ?: return).copy(parentId = lastHost.id)
|
||||||
hostManager.addHost(host)
|
hostManager.addHost(host)
|
||||||
val c = HostTreeNode(host)
|
val newNode = HostTreeNode(host)
|
||||||
val newNode = copyNode(c, lastHost.id)
|
|
||||||
model.insertNodeInto(newNode, lastNode, lastNode.childCount)
|
model.insertNodeInto(newNode, lastNode, lastNode.childCount)
|
||||||
selectionPath = TreePath(model.getPathToRoot(newNode))
|
selectionPath = TreePath(model.getPathToRoot(newNode))
|
||||||
}
|
}
|
||||||
@@ -465,14 +486,13 @@ class NewHostTree : JXTree() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newFolder.isEnabled = lastHost.protocol == Protocol.Folder
|
newMenu.isEnabled = lastHost.protocol == Protocol.Folder
|
||||||
newHost.isEnabled = newFolder.isEnabled
|
|
||||||
remove.isEnabled = getSelectionHostTreeNodes().none { it == model.root }
|
remove.isEnabled = getSelectionHostTreeNodes().none { it == model.root }
|
||||||
copy.isEnabled = remove.isEnabled
|
copy.isEnabled = remove.isEnabled
|
||||||
rename.isEnabled = remove.isEnabled
|
rename.isEnabled = remove.isEnabled
|
||||||
property.isEnabled = lastHost.protocol != Protocol.Folder
|
property.isEnabled = lastHost.protocol != Protocol.Folder
|
||||||
refresh.isEnabled = lastHost.protocol == Protocol.Folder
|
refresh.isEnabled = lastHost.protocol == Protocol.Folder
|
||||||
|
importMenu.isEnabled = lastHost.protocol == Protocol.Folder
|
||||||
|
|
||||||
// 如果选中了 SSH 服务器,那么才启用
|
// 如果选中了 SSH 服务器,那么才启用
|
||||||
openWithSFTP.isEnabled = getSelectionHostTreeNodes(true).map { it.host }.any { it.protocol == Protocol.SSH }
|
openWithSFTP.isEnabled = getSelectionHostTreeNodes(true).map { it.host }.any { it.protocol == Protocol.SSH }
|
||||||
@@ -564,7 +584,6 @@ class NewHostTree : JXTree() {
|
|||||||
nodes.forEach { openHostAction.actionPerformed(OpenHostActionEvent(source, it, evt)) }
|
nodes.forEach { openHostAction.actionPerformed(OpenHostActionEvent(source, it, evt)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun openWithSFTP(evt: EventObject) {
|
private fun openWithSFTP(evt: EventObject) {
|
||||||
val nodes = getSelectionHostTreeNodes(true).map { it.host }.filter { it.protocol == Protocol.SSH }
|
val nodes = getSelectionHostTreeNodes(true).map { it.host }.filter { it.protocol == Protocol.SSH }
|
||||||
if (nodes.isEmpty()) return
|
if (nodes.isEmpty()) return
|
||||||
@@ -584,6 +603,121 @@ class NewHostTree : JXTree() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun importHosts(folder: HostTreeNode, type: ImportType) {
|
||||||
|
val chooser = JFileChooser()
|
||||||
|
chooser.fileSelectionMode = JFileChooser.FILES_ONLY
|
||||||
|
chooser.isAcceptAllFileFilterUsed = false
|
||||||
|
chooser.isMultiSelectionEnabled = false
|
||||||
|
|
||||||
|
if (type == ImportType.WindTerm) {
|
||||||
|
chooser.fileFilter = FileNameExtensionFilter("WindTerm(*.sessions)", "sessions")
|
||||||
|
}
|
||||||
|
|
||||||
|
val dir = properties.getString("NewHostTree.ImportHosts.defaultDir", StringUtils.EMPTY)
|
||||||
|
if (dir.isNotBlank()) {
|
||||||
|
val file = FileUtils.getFile(dir)
|
||||||
|
if (file.exists()) {
|
||||||
|
chooser.currentDirectory = file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val code = chooser.showOpenDialog(owner)
|
||||||
|
properties.putString("NewHostTree.ImportHosts.defaultDir", chooser.currentDirectory.absolutePath)
|
||||||
|
|
||||||
|
if (code != JFileChooser.APPROVE_OPTION) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = chooser.selectedFile
|
||||||
|
|
||||||
|
val nodes = if (type == ImportType.WindTerm) {
|
||||||
|
parseFromWindTerm(file)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (node in nodes) {
|
||||||
|
node.host = node.host.copy(parentId = folder.host.id)
|
||||||
|
model.insertNodeInto(
|
||||||
|
node,
|
||||||
|
folder,
|
||||||
|
if (node.host.protocol == Protocol.Folder) folder.folderCount else folder.childCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (node in nodes) {
|
||||||
|
hostManager.addHost(node.host)
|
||||||
|
node.getAllChildren().forEach { hostManager.addHost(it.host) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFromWindTerm(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(">")
|
||||||
|
|
||||||
|
var p: HostTreeNode? = null
|
||||||
|
if (group.isNotBlank()) {
|
||||||
|
for (j in groups.indices) {
|
||||||
|
val folders = if (j == 0 || p == null) nodes
|
||||||
|
else p.children().toList().filterIsInstance<HostTreeNode>()
|
||||||
|
val n = HostTreeNode(
|
||||||
|
Host(
|
||||||
|
name = groups[j], protocol = Protocol.Folder,
|
||||||
|
parentId = p?.host?.id ?: StringUtils.EMPTY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val cp = folders.find { it.host.protocol == Protocol.Folder && it.host.name == groups[j] }
|
||||||
|
if (cp != null) {
|
||||||
|
p = cp
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (p == null) {
|
||||||
|
p = n
|
||||||
|
nodes.add(n)
|
||||||
|
} else {
|
||||||
|
p.add(n)
|
||||||
|
p = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val n = HostTreeNode(
|
||||||
|
Host(
|
||||||
|
name = StringUtils.defaultIfBlank(label, target),
|
||||||
|
host = target,
|
||||||
|
port = port,
|
||||||
|
protocol = Protocol.SSH,
|
||||||
|
parentId = p?.host?.id ?: StringUtils.EMPTY,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (p == null) {
|
||||||
|
nodes.add(n)
|
||||||
|
} else {
|
||||||
|
p.add(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private enum class ImportType {
|
||||||
|
WindTerm
|
||||||
|
}
|
||||||
|
|
||||||
private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
|
private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import java.awt.event.ItemEvent
|
|||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
class RequestAuthenticationDialog(owner: Window, host: Host) : DialogWrapper(owner) {
|
||||||
|
|
||||||
private val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
|
private val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
|
||||||
private val rememberCheckBox = JCheckBox("Remember")
|
private val rememberCheckBox = JCheckBox("Remember")
|
||||||
private val passwordPanel = JPanel(BorderLayout())
|
private val passwordPanel = JPanel(BorderLayout())
|
||||||
private val passwordPasswordField = OutlinePasswordField()
|
private val passwordPasswordField = OutlinePasswordField()
|
||||||
|
private val usernameTextField = OutlineTextField()
|
||||||
private val publicKeyComboBox = OutlineComboBox<OhKeyPair>()
|
private val publicKeyComboBox = OutlineComboBox<OhKeyPair>()
|
||||||
private val keyManager get() = KeyManager.getInstance()
|
private val keyManager get() = KeyManager.getInstance()
|
||||||
private var authentication = Authentication.No
|
private var authentication = Authentication.No
|
||||||
@@ -64,6 +65,8 @@ class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usernameTextField.text = host.username
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent {
|
override fun createCenterPanel(): JComponent {
|
||||||
@@ -72,7 +75,7 @@ class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
val formMargin = "7dlu"
|
val formMargin = "7dlu"
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $formMargin, default:grow",
|
"left:pref, $formMargin, default:grow",
|
||||||
"pref, $formMargin, pref"
|
"pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
switchPasswordComponent()
|
switchPasswordComponent()
|
||||||
@@ -81,8 +84,10 @@ class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
.layout(layout)
|
.layout(layout)
|
||||||
.add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, 1)
|
.add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, 1)
|
||||||
.add(authenticationTypeComboBox).xy(3, 1)
|
.add(authenticationTypeComboBox).xy(3, 1)
|
||||||
.add("${I18n.getString("termora.new-host.general.password")}:").xy(1, 3)
|
.add("${I18n.getString("termora.new-host.general.username")}:").xy(1, 3)
|
||||||
.add(passwordPanel).xy(3, 3)
|
.add(usernameTextField).xy(3, 3)
|
||||||
|
.add("${I18n.getString("termora.new-host.general.password")}:").xy(1, 5)
|
||||||
|
.add(passwordPanel).xy(3, 5)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +139,13 @@ class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
|
|
||||||
fun getAuthentication(): Authentication {
|
fun getAuthentication(): Authentication {
|
||||||
isModal = true
|
isModal = true
|
||||||
SwingUtilities.invokeLater { passwordPasswordField.requestFocusInWindow() }
|
SwingUtilities.invokeLater {
|
||||||
|
if (usernameTextField.text.isBlank()) {
|
||||||
|
usernameTextField.requestFocusInWindow()
|
||||||
|
} else {
|
||||||
|
passwordPasswordField.requestFocusInWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
isVisible = true
|
isVisible = true
|
||||||
return authentication
|
return authentication
|
||||||
}
|
}
|
||||||
@@ -143,4 +154,8 @@ class RequestAuthenticationDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
return rememberCheckBox.isSelected
|
return rememberCheckBox.isSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUsername(): String {
|
||||||
|
return usernameTextField.text
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,7 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
private val tab = this
|
||||||
|
|
||||||
private var sshClient: SshClient? = null
|
private var sshClient: SshClient? = null
|
||||||
private var sshSession: ClientSession? = null
|
private var sshSession: ClientSession? = null
|
||||||
@@ -97,12 +98,20 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
|||||||
|
|
||||||
if (host.authentication.type == AuthenticationType.No) {
|
if (host.authentication.type == AuthenticationType.No) {
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
val dialog = RequestAuthenticationDialog(owner)
|
val dialog = RequestAuthenticationDialog(owner, host)
|
||||||
val authentication = dialog.getAuthentication()
|
val authentication = dialog.getAuthentication()
|
||||||
host = host.copy(authentication = authentication)
|
host = host.copy(
|
||||||
|
authentication = authentication,
|
||||||
|
username = dialog.getUsername(),
|
||||||
|
)
|
||||||
// save
|
// save
|
||||||
if (dialog.isRemembered()) {
|
if (dialog.isRemembered()) {
|
||||||
HostManager.getInstance().addHost(this@SSHTerminalTab.host.copy(authentication = authentication))
|
HostManager.getInstance().addHost(
|
||||||
|
tab.host.copy(
|
||||||
|
authentication = authentication,
|
||||||
|
username = dialog.getUsername(),
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +166,7 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
|||||||
if (Database.getDatabase().terminal.autoCloseTabWhenDisconnected) {
|
if (Database.getDatabase().terminal.autoCloseTabWhenDisconnected) {
|
||||||
terminalTabbedManager?.let { manager ->
|
terminalTabbedManager?.let { manager ->
|
||||||
SwingUtilities.invokeLater {
|
SwingUtilities.invokeLater {
|
||||||
manager.closeTerminalTab(this@SSHTerminalTab, true)
|
manager.closeTerminalTab(tab, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,13 +119,20 @@ class SftpFileSystemPanel(
|
|||||||
client.serverKeyVerifier = DialogServerKeyVerifier(owner)
|
client.serverKeyVerifier = DialogServerKeyVerifier(owner)
|
||||||
// 弹出授权框
|
// 弹出授权框
|
||||||
if (host.authentication.type == AuthenticationType.No) {
|
if (host.authentication.type == AuthenticationType.No) {
|
||||||
val dialog = RequestAuthenticationDialog(owner)
|
val dialog = RequestAuthenticationDialog(owner, host)
|
||||||
val authentication = dialog.getAuthentication()
|
val authentication = dialog.getAuthentication()
|
||||||
host = host.copy(authentication = authentication)
|
host = host.copy(
|
||||||
|
authentication = authentication,
|
||||||
|
username = dialog.getUsername(),
|
||||||
|
)
|
||||||
// save
|
// save
|
||||||
if (dialog.isRemembered()) {
|
if (dialog.isRemembered()) {
|
||||||
HostManager.getInstance()
|
HostManager.getInstance().addHost(
|
||||||
.addHost(host.copy(authentication = authentication))
|
host.copy(
|
||||||
|
authentication = authentication,
|
||||||
|
username = dialog.getUsername(),
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ termora.welcome.contextmenu.rename=Rename
|
|||||||
termora.welcome.contextmenu.expand-all=Expand all
|
termora.welcome.contextmenu.expand-all=Expand all
|
||||||
termora.welcome.contextmenu.collapse-all=Collapse all
|
termora.welcome.contextmenu.collapse-all=Collapse all
|
||||||
termora.welcome.contextmenu.new=New
|
termora.welcome.contextmenu.new=New
|
||||||
|
termora.welcome.contextmenu.import=${termora.keymgr.import}
|
||||||
termora.welcome.contextmenu.new.folder=${termora.folder}
|
termora.welcome.contextmenu.new.folder=${termora.folder}
|
||||||
termora.welcome.contextmenu.new.host=Host
|
termora.welcome.contextmenu.new.host=Host
|
||||||
termora.welcome.contextmenu.new.folder.name=New Folder
|
termora.welcome.contextmenu.new.folder.name=New Folder
|
||||||
|
|||||||
Reference in New Issue
Block a user