mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
Compare commits
20 Commits
2.0.0-beta
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92e43ee41 | ||
|
|
f6243e33da | ||
|
|
72f334d572 | ||
|
|
68cbb10dec | ||
|
|
45f5c4ee91 | ||
|
|
48c511613e | ||
|
|
c94063d459 | ||
|
|
c26aafd831 | ||
|
|
a5638329e7 | ||
|
|
8323f8eb5d | ||
|
|
35199ed094 | ||
|
|
b5d53cf416 | ||
|
|
39e26a6e3d | ||
|
|
15cb06af0f | ||
|
|
1e0bbb5a00 | ||
|
|
fb6fdbc14c | ||
|
|
96df53ce40 | ||
|
|
42f86dc3a3 | ||
|
|
32b11c6063 | ||
|
|
b2f43ba439 |
@@ -229,7 +229,8 @@ tasks.register<Copy>("copy-dependencies") {
|
|||||||
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
|
||||||
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
|
||||||
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
|
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
|
||||||
val targetDir = FileUtils.getFile(dylib, pty4j.name, if (os.isWindows) "win32" else "linux")
|
val osName = if (os.isWindows) "win32" else if (os.isMacOsX) "darwin" else "linux"
|
||||||
|
val targetDir = FileUtils.getFile(dylib, pty4j.name, osName)
|
||||||
FileUtils.forceMkdir(targetDir)
|
FileUtils.forceMkdir(targetDir)
|
||||||
val myArchName = if (arch.isArm) "aarch64" else "x86-64"
|
val myArchName = if (arch.isArm) "aarch64" else "x86-64"
|
||||||
if (os.isWindows) {
|
if (os.isWindows) {
|
||||||
@@ -548,7 +549,16 @@ fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: Str
|
|||||||
"/DMyAppVersion=${appVersion}",
|
"/DMyAppVersion=${appVersion}",
|
||||||
"/DMyOutputDir=${distributionDir.asFile.absolutePath}",
|
"/DMyOutputDir=${distributionDir.asFile.absolutePath}",
|
||||||
"/DMySetupIconFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora.ico")}",
|
"/DMySetupIconFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora.ico")}",
|
||||||
"/DMyWizardSmallImageFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora_128x128.bmp")}",
|
"/DMyWizardSmallImageFile=${
|
||||||
|
FileUtils.getFile(
|
||||||
|
projectDir,
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"resources",
|
||||||
|
"icons",
|
||||||
|
"termora_128x128.bmp"
|
||||||
|
)
|
||||||
|
}",
|
||||||
"/DMySourceDir=${FileUtils.getFile(dir, projectName).absolutePath}",
|
"/DMySourceDir=${FileUtils.getFile(dir, projectName).absolutePath}",
|
||||||
"/F${finalFilenameWithoutExtension}",
|
"/F${finalFilenameWithoutExtension}",
|
||||||
FileUtils.getFile(projectDir, "src", "main", "resources", "termora.iss")
|
FileUtils.getFile(projectDir, "src", "main", "resources", "termora.iss")
|
||||||
@@ -658,7 +668,7 @@ fun packOnLinux(distributionDir: Directory, finalFilenameWithoutExtension: Strin
|
|||||||
commandLine(
|
commandLine(
|
||||||
"wget",
|
"wget",
|
||||||
"-O", appimagetool.absolutePath,
|
"-O", appimagetool.absolutePath,
|
||||||
"https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${if (arch.isArm) "aarch64" else "x86_64"}.AppImage"
|
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${if (arch.isArm) "aarch64" else "x86_64"}.AppImage"
|
||||||
)
|
)
|
||||||
workingDir = distributionDir.asFile
|
workingDir = distributionDir.asFile
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ tinylog = "2.7.0"
|
|||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
flatlaf = "3.6.1"
|
flatlaf = "3.6.1"
|
||||||
kotlinx-serialization-json = "1.9.0"
|
kotlinx-serialization-json = "1.9.0"
|
||||||
commons-codec = "1.18.0"
|
commons-codec = "1.19.0"
|
||||||
commons-lang3 = "3.18.0"
|
commons-lang3 = "3.18.0"
|
||||||
commons-csv = "1.14.0"
|
commons-csv = "1.14.1"
|
||||||
commons-net = "3.11.1"
|
commons-net = "3.11.1"
|
||||||
commons-text = "1.13.1"
|
commons-text = "1.14.0"
|
||||||
commons-compress = "1.27.1"
|
commons-compress = "1.28.0"
|
||||||
commons-vfs2 = "2.10.0"
|
commons-vfs2 = "2.10.0"
|
||||||
swingx = "1.6.5-1"
|
swingx = "1.6.5-1"
|
||||||
jgoodies-forms = "1.9.0"
|
jgoodies-forms = "1.9.0"
|
||||||
@@ -20,7 +20,7 @@ oshi = "6.8.1"
|
|||||||
versioncompare = "1.4.1"
|
versioncompare = "1.4.1"
|
||||||
jna = "5.17.0"
|
jna = "5.17.0"
|
||||||
jSystemThemeDetector = "3.9.1"
|
jSystemThemeDetector = "3.9.1"
|
||||||
commons-io = "2.19.0"
|
commons-io = "2.20.0"
|
||||||
jbr-api = "17.1.10.1"
|
jbr-api = "17.1.10.1"
|
||||||
hutool = "5.8.39"
|
hutool = "5.8.39"
|
||||||
jsch = "2.27.2"
|
jsch = "2.27.2"
|
||||||
@@ -43,7 +43,7 @@ restart4j = "0.0.1"
|
|||||||
eddsa = "0.3.0"
|
eddsa = "0.3.0"
|
||||||
exposed = "1.0.0-beta-4"
|
exposed = "1.0.0-beta-4"
|
||||||
h2 = "2.3.232"
|
h2 = "2.3.232"
|
||||||
sqlite = "3.50.2.0"
|
sqlite = "3.50.3.0"
|
||||||
jug = "5.1.0"
|
jug = "5.1.0"
|
||||||
semver4j = "6.0.0"
|
semver4j = "6.0.0"
|
||||||
jsvg = "2.0.0"
|
jsvg = "2.0.0"
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
}
|
}
|
||||||
|
|
||||||
project.version = "0.0.3"
|
project.version = "0.0.4"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
implementation("com.qcloud:cos_api:5.6.247")
|
implementation("com.qcloud:cos_api:5.6.249")
|
||||||
compileOnly(project(":"))
|
compileOnly(project(":"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
}
|
}
|
||||||
|
|
||||||
project.version = "0.0.7"
|
project.version = "0.0.8"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
compileOnly(project(":"))
|
compileOnly(project(":"))
|
||||||
implementation("com.maxmind.geoip2:geoip2:4.3.1")
|
implementation("com.maxmind.geoip2:geoip2:4.3.1")
|
||||||
// https://github.com/hstyi/geolite2
|
// https://github.com/hstyi/geolite2
|
||||||
implementation("com.github.hstyi:geolite2:v1.0-202507070058")
|
implementation("com.github.hstyi:geolite2:v1.0-202507280101")
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(from = "$rootDir/plugins/common.gradle.kts")
|
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
project.version = "0.0.3"
|
project.version = "0.0.4"
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -127,12 +127,19 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createSouthPanel(): JComponent? {
|
protected open fun createSouthPanel(): JComponent? {
|
||||||
|
val westSourcePanel = createWestSourcePanel()
|
||||||
val box = Box.createHorizontalBox()
|
val box = Box.createHorizontalBox()
|
||||||
|
|
||||||
|
if (westSourcePanel != null) {
|
||||||
|
box.add(westSourcePanel)
|
||||||
|
} else {
|
||||||
|
box.add(Box.createHorizontalGlue())
|
||||||
|
}
|
||||||
|
|
||||||
box.border = BorderFactory.createCompoundBorder(
|
box.border = BorderFactory.createCompoundBorder(
|
||||||
BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor),
|
BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor),
|
||||||
BorderFactory.createEmptyBorder(8, 12, 8, 12)
|
BorderFactory.createEmptyBorder(8, 12, 8, 12)
|
||||||
)
|
)
|
||||||
box.add(Box.createHorizontalGlue())
|
|
||||||
|
|
||||||
val actions = createActions()
|
val actions = createActions()
|
||||||
for (i in actions.size - 1 downTo 0) {
|
for (i in actions.size - 1 downTo 0) {
|
||||||
@@ -145,6 +152,10 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
|||||||
return box
|
return box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun createWestSourcePanel(): JComponent? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun createActions(): List<AbstractAction> {
|
protected open fun createActions(): List<AbstractAction> {
|
||||||
return listOf(createOkAction(), createCancelAction())
|
return listOf(createOkAction(), createCancelAction())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package app.termora
|
|||||||
|
|
||||||
import app.termora.actions.StateAction
|
import app.termora.actions.StateAction
|
||||||
import app.termora.findeverywhere.FindEverywhereAction
|
import app.termora.findeverywhere.FindEverywhereAction
|
||||||
import app.termora.plugin.internal.update.AppUpdateAction
|
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||||
import com.formdev.flatlaf.extras.components.FlatToolBar
|
import com.formdev.flatlaf.extras.components.FlatToolBar
|
||||||
@@ -82,10 +81,11 @@ internal class MyTermoraToolbar(private val windowScope: WindowScope, private va
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
add(Box.createHorizontalGlue())
|
if (SystemInfo.isLinux || SystemInfo.isWindows) {
|
||||||
|
add(Box.createHorizontalStrut(24))
|
||||||
|
}
|
||||||
|
|
||||||
// update
|
add(Box.createHorizontalGlue())
|
||||||
add(redirectUpdateAction(disposable))
|
|
||||||
|
|
||||||
for (action in model.getActions()) {
|
for (action in model.getActions()) {
|
||||||
if (action.visible.not()) continue
|
if (action.visible.not()) continue
|
||||||
@@ -122,34 +122,6 @@ internal class MyTermoraToolbar(private val windowScope: WindowScope, private va
|
|||||||
toolbar.add(spacing)
|
toolbar.add(spacing)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun redirectUpdateAction(disposable: Disposable): AbstractButton {
|
|
||||||
val action = AppUpdateAction.getInstance()
|
|
||||||
val button = JButton(action.smallIcon)
|
|
||||||
button.toolTipText = (action.getValue(Action.SHORT_DESCRIPTION) as? String)
|
|
||||||
?: action.getValue(Action.NAME) as? String
|
|
||||||
button.isVisible = action.isEnabled
|
|
||||||
button.addActionListener(object : AbstractAction() {
|
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
|
||||||
action.actionPerformed(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val listener = object : PropertyChangeListener, Disposable {
|
|
||||||
override fun propertyChange(evt: PropertyChangeEvent) {
|
|
||||||
button.isVisible = action.isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
action.removePropertyChangeListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
action.addPropertyChangeListener(listener)
|
|
||||||
Disposer.register(disposable, listener)
|
|
||||||
|
|
||||||
return button
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun redirectAction(action: Action, disposable: Disposable): AbstractButton {
|
private fun redirectAction(action: Action, disposable: Disposable): AbstractButton {
|
||||||
val button = if (action is StateAction) JToggleButton() else JButton()
|
val button = if (action is StateAction) JToggleButton() else JButton()
|
||||||
button.toolTipText = (action.getValue(Action.SHORT_DESCRIPTION) as? String)
|
button.toolTipText = (action.getValue(Action.SHORT_DESCRIPTION) as? String)
|
||||||
|
|||||||
@@ -650,6 +650,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private val browseEditCommandBtn = JButton(Icons.folder)
|
private val browseEditCommandBtn = JButton(Icons.folder)
|
||||||
private val pinTabComboBox = YesOrNoComboBox()
|
private val pinTabComboBox = YesOrNoComboBox()
|
||||||
private val preserveModificationTimeComboBox = YesOrNoComboBox()
|
private val preserveModificationTimeComboBox = YesOrNoComboBox()
|
||||||
|
private val doubleClickComboBox = OutlineComboBox<String>()
|
||||||
private val sftp get() = database.sftp
|
private val sftp get() = database.sftp
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -699,6 +700,13 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
doubleClickComboBox.addItemListener(object : ItemListener {
|
||||||
|
override fun itemStateChanged(e: ItemEvent) {
|
||||||
|
if (e.stateChange != ItemEvent.SELECTED) return
|
||||||
|
sftp.dbClickBehavior = doubleClickComboBox.selectedItem as String
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
preserveModificationTimeComboBox.addItemListener {
|
preserveModificationTimeComboBox.addItemListener {
|
||||||
if (it.stateChange == ItemEvent.SELECTED) {
|
if (it.stateChange == ItemEvent.SELECTED) {
|
||||||
sftp.preserveModificationTime = preserveModificationTimeComboBox.selectedItem as Boolean
|
sftp.preserveModificationTime = preserveModificationTimeComboBox.selectedItem as Boolean
|
||||||
@@ -780,6 +788,26 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
sftpCommandField.text = sftp.sftpCommand
|
sftpCommandField.text = sftp.sftpCommand
|
||||||
pinTabComboBox.selectedItem = sftp.pinTab
|
pinTabComboBox.selectedItem = sftp.pinTab
|
||||||
preserveModificationTimeComboBox.selectedItem = sftp.preserveModificationTime
|
preserveModificationTimeComboBox.selectedItem = sftp.preserveModificationTime
|
||||||
|
|
||||||
|
doubleClickComboBox.renderer = object : DefaultListCellRenderer() {
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<*>?,
|
||||||
|
value: Any?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component? {
|
||||||
|
var text = value?.toString()
|
||||||
|
if (value == "Edit") text = I18n.getString("termora.keymgr.edit")
|
||||||
|
if (value == "Transfer") text = getTitle()
|
||||||
|
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleClickComboBox.addItem("Transfer")
|
||||||
|
doubleClickComboBox.addItem("Edit")
|
||||||
|
|
||||||
|
doubleClickComboBox.selectedItem = sftp.dbClickBehavior
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(isSelected: Boolean): Icon {
|
override fun getIcon(isSelected: Boolean): Icon {
|
||||||
@@ -813,6 +841,8 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
builder.add(editCommandField).xy(3, rows).apply { rows += 2 }
|
builder.add(editCommandField).xy(3, rows).apply { rows += 2 }
|
||||||
builder.add("${I18n.getString("termora.tabbed.contextmenu.sftp-command")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.tabbed.contextmenu.sftp-command")}:").xy(1, rows)
|
||||||
builder.add(sftpCommandField).xy(3, rows).apply { rows += 2 }
|
builder.add(sftpCommandField).xy(3, rows).apply { rows += 2 }
|
||||||
|
builder.add("${I18n.getString("termora.settings.sftp.db-click-behavior")}:").xy(1, rows)
|
||||||
|
builder.add(doubleClickComboBox).xy(3, rows).apply { rows += 2 }
|
||||||
builder.add("${I18n.getString("termora.settings.sftp.default-directory")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.settings.sftp.default-directory")}:").xy(1, rows)
|
||||||
builder.add(defaultDirectoryField).xy(3, rows).apply { rows += 2 }
|
builder.add(defaultDirectoryField).xy(3, rows).apply { rows += 2 }
|
||||||
builder.add(box).xyw(1, rows, 3).apply { rows += 2 }
|
builder.add(box).xyw(1, rows, 3).apply { rows += 2 }
|
||||||
|
|||||||
@@ -763,6 +763,12 @@ class DatabaseManager private constructor() : Disposable {
|
|||||||
*/
|
*/
|
||||||
var editCommand by StringPropertyDelegate(StringUtils.EMPTY)
|
var editCommand by StringPropertyDelegate(StringUtils.EMPTY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双击行为
|
||||||
|
*
|
||||||
|
* Transfer、Edit
|
||||||
|
*/
|
||||||
|
var dbClickBehavior by StringPropertyDelegate("Transfer")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sftp command
|
* sftp command
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package app.termora.highlight
|
package app.termora.highlight
|
||||||
|
|
||||||
import app.termora.DialogWrapper
|
import app.termora.DialogWrapper
|
||||||
|
import app.termora.Disposable
|
||||||
|
import app.termora.Disposer
|
||||||
import app.termora.TerminalFactory
|
import app.termora.TerminalFactory
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
@@ -15,8 +17,9 @@ class ChooseColorTemplateDialog(owner: Window, title: String) : DialogWrapper(ow
|
|||||||
var colorIndex = -1
|
var colorIndex = -1
|
||||||
var defaultColor: Color = Color.white
|
var defaultColor: Color = Color.white
|
||||||
|
|
||||||
|
var ok = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
|
|
||||||
isModal = true
|
isModal = true
|
||||||
super.setTitle(title)
|
super.setTitle(title)
|
||||||
controlsVisible = false
|
controlsVisible = false
|
||||||
@@ -30,11 +33,12 @@ class ChooseColorTemplateDialog(owner: Window, title: String) : DialogWrapper(ow
|
|||||||
|
|
||||||
override fun createCenterPanel(): JComponent {
|
override fun createCenterPanel(): JComponent {
|
||||||
val panel = JPanel(GridLayout(2, 8, 4, 4))
|
val panel = JPanel(GridLayout(2, 8, 4, 4))
|
||||||
val colorPalette = TerminalFactory.getInstance()
|
val terminal = TerminalFactory.getInstance().createTerminal()
|
||||||
.createTerminal().getTerminalModel().getColorPalette()
|
val colorPalette = terminal.getTerminalModel().getColorPalette()
|
||||||
for (i in 1..16) {
|
for (i in 1..16) {
|
||||||
val c = JPanel()
|
val c = JPanel()
|
||||||
c.preferredSize = Dimension(24, 24)
|
c.preferredSize = Dimension(24, 24)
|
||||||
|
c.minimumSize = c.preferredSize
|
||||||
c.background = Color(colorPalette.getXTerm256Color(i))
|
c.background = Color(colorPalette.getXTerm256Color(i))
|
||||||
c.addMouseListener(object : MouseAdapter() {
|
c.addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
@@ -67,6 +71,12 @@ class ChooseColorTemplateDialog(owner: Window, title: String) : DialogWrapper(ow
|
|||||||
cPanel.add(panel, BorderLayout.CENTER)
|
cPanel.add(panel, BorderLayout.CENTER)
|
||||||
cPanel.add(customBtn, BorderLayout.SOUTH)
|
cPanel.add(customBtn, BorderLayout.SOUTH)
|
||||||
cPanel.border = BorderFactory.createEmptyBorder(if (SystemInfo.isLinux) 6 else 0, 12, 12, 12)
|
cPanel.border = BorderFactory.createEmptyBorder(if (SystemInfo.isLinux) 6 else 0, 12, 12, 12)
|
||||||
|
|
||||||
|
Disposer.register(disposable, object : Disposable {
|
||||||
|
override fun dispose() {
|
||||||
|
terminal.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
return cPanel
|
return cPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,4 +84,9 @@ class ChooseColorTemplateDialog(owner: Window, title: String) : DialogWrapper(ow
|
|||||||
override fun createSouthPanel(): JComponent? {
|
override fun createSouthPanel(): JComponent? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun doOKAction() {
|
||||||
|
ok = true
|
||||||
|
super.doOKAction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,8 @@ package app.termora.highlight
|
|||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
|
|
||||||
class ColorPanel : JPanel {
|
class ColorPanel : JPanel() {
|
||||||
var color: Color = Color.WHITE
|
var color: Color? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
background = value
|
background = value
|
||||||
val old = field
|
val old = field
|
||||||
@@ -13,7 +13,4 @@ class ColorPanel : JPanel {
|
|||||||
}
|
}
|
||||||
var colorIndex = -1
|
var colorIndex = -1
|
||||||
|
|
||||||
constructor(color: Color) : super() {
|
|
||||||
this.color = color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -285,29 +285,27 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
|
|||||||
dialog.keywordTextField.text = keywordHighlight.keyword
|
dialog.keywordTextField.text = keywordHighlight.keyword
|
||||||
dialog.descriptionTextField.text = keywordHighlight.description
|
dialog.descriptionTextField.text = keywordHighlight.description
|
||||||
|
|
||||||
if (keywordHighlight.textColor <= 16) {
|
if (keywordHighlight.textColor in 0..16) {
|
||||||
if (keywordHighlight.textColor == 0) {
|
if (keywordHighlight.textColor == 0) {
|
||||||
dialog.textColor.color = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
|
dialog.textColor.background = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
|
||||||
|
dialog.textColor.colorIndex = -1
|
||||||
} else {
|
} else {
|
||||||
dialog.textColor.color = Color(colorPalette.getXTerm256Color(keywordHighlight.textColor))
|
dialog.textColor.color = Color(colorPalette.getXTerm256Color(keywordHighlight.textColor))
|
||||||
|
dialog.textColor.colorIndex = keywordHighlight.textColor
|
||||||
}
|
}
|
||||||
dialog.textColor.colorIndex = keywordHighlight.textColor
|
|
||||||
} else {
|
} else {
|
||||||
dialog.textColor.color = Color(keywordHighlight.textColor)
|
dialog.textColor.color = Color(keywordHighlight.textColor)
|
||||||
dialog.textColor.colorIndex = -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywordHighlight.backgroundColor <= 16) {
|
if (keywordHighlight.backgroundColor in 0..16) {
|
||||||
if (keywordHighlight.backgroundColor == 0) {
|
if (keywordHighlight.backgroundColor == 0) {
|
||||||
dialog.backgroundColor.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
dialog.backgroundColor.background = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
||||||
|
dialog.backgroundColor.colorIndex = -1
|
||||||
} else {
|
} else {
|
||||||
dialog.backgroundColor.color =
|
dialog.backgroundColor.color =
|
||||||
Color(colorPalette.getXTerm256Color(keywordHighlight.backgroundColor))
|
Color(colorPalette.getXTerm256Color(keywordHighlight.backgroundColor))
|
||||||
|
dialog.backgroundColor.colorIndex = keywordHighlight.backgroundColor
|
||||||
}
|
}
|
||||||
dialog.backgroundColor.colorIndex = keywordHighlight.backgroundColor
|
|
||||||
} else {
|
|
||||||
dialog.backgroundColor.color = Color(keywordHighlight.backgroundColor)
|
|
||||||
dialog.backgroundColor.colorIndex = -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.boldCheckBox.isSelected = keywordHighlight.bold
|
dialog.boldCheckBox.isSelected = keywordHighlight.bold
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.awt.event.MouseEvent
|
|||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class NewKeywordHighlightDialog(
|
class NewKeywordHighlightDialog(
|
||||||
owner: Window,
|
owner: Window,
|
||||||
@@ -95,7 +96,7 @@ class NewKeywordHighlightDialog(
|
|||||||
|
|
||||||
init()
|
init()
|
||||||
pack()
|
pack()
|
||||||
size = Dimension(UIManager.getInt("Dialog.width") - 200, height)
|
size = Dimension(UIManager.getInt("Dialog.width") - 200, max(height, preferredSize.height))
|
||||||
setLocationRelativeTo(null)
|
setLocationRelativeTo(null)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -121,13 +122,15 @@ class NewKeywordHighlightDialog(
|
|||||||
lineThroughCheckBox.addActionListener { repaintKeywordHighlightView() }
|
lineThroughCheckBox.addActionListener { repaintKeywordHighlightView() }
|
||||||
|
|
||||||
textColorRevert.addActionListener {
|
textColorRevert.addActionListener {
|
||||||
textColor.color = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
|
textColor.color = null
|
||||||
textColor.colorIndex = 0
|
textColor.background = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
|
||||||
|
textColor.colorIndex = -1
|
||||||
repaintKeywordHighlightView()
|
repaintKeywordHighlightView()
|
||||||
}
|
}
|
||||||
backgroundColorRevert.addActionListener {
|
backgroundColorRevert.addActionListener {
|
||||||
backgroundColor.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
backgroundColor.color = null
|
||||||
backgroundColor.colorIndex = 0
|
backgroundColor.background = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
||||||
|
backgroundColor.colorIndex = -1
|
||||||
repaintKeywordHighlightView()
|
repaintKeywordHighlightView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +148,22 @@ class NewKeywordHighlightDialog(
|
|||||||
keywordHighlightView.italic = italicCheckBox.isSelected
|
keywordHighlightView.italic = italicCheckBox.isSelected
|
||||||
keywordHighlightView.underline = underlineCheckBox.isSelected
|
keywordHighlightView.underline = underlineCheckBox.isSelected
|
||||||
keywordHighlightView.lineThrough = lineThroughCheckBox.isSelected
|
keywordHighlightView.lineThrough = lineThroughCheckBox.isSelected
|
||||||
keywordHighlightView.textColor = textColor.color
|
|
||||||
keywordHighlightView.backgroundColor = backgroundColor.color
|
if (textColor.color == null && textColor.colorIndex == -1) {
|
||||||
|
keywordHighlightView.textColor = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
|
||||||
|
} else if (textColor.color != null) {
|
||||||
|
keywordHighlightView.textColor = textColor.color
|
||||||
|
} else {
|
||||||
|
keywordHighlightView.textColor = Color(colorPalette.getXTerm256Color(textColor.colorIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backgroundColor.color == null && backgroundColor.colorIndex == -1) {
|
||||||
|
keywordHighlightView.backgroundColor = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
||||||
|
} else if (backgroundColor.color != null) {
|
||||||
|
keywordHighlightView.backgroundColor = backgroundColor.color
|
||||||
|
} else {
|
||||||
|
keywordHighlightView.backgroundColor = Color(colorPalette.getXTerm256Color(backgroundColor.colorIndex))
|
||||||
|
}
|
||||||
keywordHighlightView.repaint()
|
keywordHighlightView.repaint()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +209,8 @@ class NewKeywordHighlightDialog(
|
|||||||
val owner = this
|
val owner = this
|
||||||
val arc = UIManager.getInt("Component.arc")
|
val arc = UIManager.getInt("Component.arc")
|
||||||
val lineBorder = FlatLineBorder(Insets(1, 1, 1, 1), DynamicColor.BorderColor, 1f, arc)
|
val lineBorder = FlatLineBorder(Insets(1, 1, 1, 1), DynamicColor.BorderColor, 1f, arc)
|
||||||
val colorPanel = ColorPanel(color)
|
val colorPanel = ColorPanel()
|
||||||
|
colorPanel.background = color
|
||||||
colorPanel.preferredSize = keywordTextField.preferredSize
|
colorPanel.preferredSize = keywordTextField.preferredSize
|
||||||
colorPanel.border = lineBorder
|
colorPanel.border = lineBorder
|
||||||
colorPanel.addMouseListener(object : MouseAdapter() {
|
colorPanel.addMouseListener(object : MouseAdapter() {
|
||||||
@@ -200,10 +218,19 @@ class NewKeywordHighlightDialog(
|
|||||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||||
val dialog = ChooseColorTemplateDialog(owner, title)
|
val dialog = ChooseColorTemplateDialog(owner, title)
|
||||||
dialog.setLocationRelativeTo(owner)
|
dialog.setLocationRelativeTo(owner)
|
||||||
dialog.defaultColor = colorPanel.color
|
dialog.defaultColor = colorPanel.color ?: Color.orange
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
colorPanel.color = dialog.color ?: return
|
if (dialog.ok.not()) return
|
||||||
colorPanel.colorIndex = dialog.colorIndex
|
|
||||||
|
colorPanel.colorIndex = -1
|
||||||
|
colorPanel.color = null
|
||||||
|
if (dialog.colorIndex in 1..16) {
|
||||||
|
colorPanel.colorIndex = dialog.colorIndex
|
||||||
|
colorPanel.background = Color(colorPalette.getXTerm256Color(dialog.colorIndex))
|
||||||
|
} else {
|
||||||
|
colorPanel.color = dialog.color
|
||||||
|
}
|
||||||
|
repaintKeywordHighlightView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -218,13 +245,22 @@ class NewKeywordHighlightDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val newTextColor = if (textColor.color != null) textColor.color?.toRGB() ?: 0
|
||||||
|
else if (textColor.colorIndex == -1) 0
|
||||||
|
else textColor.colorIndex
|
||||||
|
|
||||||
|
|
||||||
|
val newBackgroundColor = if (backgroundColor.color != null) backgroundColor.color?.toRGB() ?: 0
|
||||||
|
else if (backgroundColor.colorIndex == -1) 0
|
||||||
|
else backgroundColor.colorIndex
|
||||||
|
|
||||||
keywordHighlight = KeywordHighlight(
|
keywordHighlight = KeywordHighlight(
|
||||||
keyword = keywordTextField.text,
|
keyword = keywordTextField.text,
|
||||||
description = descriptionTextField.text,
|
description = descriptionTextField.text,
|
||||||
matchCase = matchCaseBtn.isSelected,
|
matchCase = matchCaseBtn.isSelected,
|
||||||
regex = regexBtn.isSelected,
|
regex = regexBtn.isSelected,
|
||||||
textColor = if (textColor.colorIndex != -1) textColor.colorIndex else textColor.color.toRGB(),
|
textColor = newTextColor,
|
||||||
backgroundColor = if (backgroundColor.colorIndex != -1) backgroundColor.colorIndex else backgroundColor.color.toRGB(),
|
backgroundColor = newBackgroundColor,
|
||||||
bold = boldCheckBox.isSelected,
|
bold = boldCheckBox.isSelected,
|
||||||
italic = italicCheckBox.isSelected,
|
italic = italicCheckBox.isSelected,
|
||||||
lineThrough = lineThroughCheckBox.isSelected,
|
lineThrough = lineThroughCheckBox.isSelected,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import app.termora.plugin.internal.rdp.RDPInternalPlugin
|
|||||||
import app.termora.plugin.internal.sftppty.SFTPPtyInternalPlugin
|
import app.termora.plugin.internal.sftppty.SFTPPtyInternalPlugin
|
||||||
import app.termora.plugin.internal.ssh.SSHInternalPlugin
|
import app.termora.plugin.internal.ssh.SSHInternalPlugin
|
||||||
import app.termora.plugin.internal.telnet.TelnetInternalPlugin
|
import app.termora.plugin.internal.telnet.TelnetInternalPlugin
|
||||||
import app.termora.plugin.internal.update.UpdatePlugin
|
import app.termora.plugin.internal.updater.UpdaterPlugin
|
||||||
import app.termora.plugin.internal.wsl.WSLInternalPlugin
|
import app.termora.plugin.internal.wsl.WSLInternalPlugin
|
||||||
import app.termora.swingCoroutineScope
|
import app.termora.swingCoroutineScope
|
||||||
import app.termora.terminal.panel.vw.FloatingToolbarPlugin
|
import app.termora.terminal.panel.vw.FloatingToolbarPlugin
|
||||||
@@ -111,7 +111,7 @@ internal class PluginManager private constructor() {
|
|||||||
// badge plugin
|
// badge plugin
|
||||||
plugins.add(PluginDescriptor(BadgePlugin(), origin = PluginOrigin.Internal, version = version))
|
plugins.add(PluginDescriptor(BadgePlugin(), origin = PluginOrigin.Internal, version = version))
|
||||||
// update plugin
|
// update plugin
|
||||||
plugins.add(PluginDescriptor(UpdatePlugin(), origin = PluginOrigin.Internal, version = version))
|
plugins.add(PluginDescriptor(UpdaterPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||||
// frame plugin
|
// frame plugin
|
||||||
plugins.add(PluginDescriptor(FramePlugin(), origin = PluginOrigin.Internal, version = version))
|
plugins.add(PluginDescriptor(FramePlugin(), origin = PluginOrigin.Internal, version = version))
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ open class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
|||||||
var showCharsetComboBox: Boolean = false
|
var showCharsetComboBox: Boolean = false
|
||||||
var showStartupCommandTextField: Boolean = false
|
var showStartupCommandTextField: Boolean = false
|
||||||
var showHeartbeatIntervalTextField: Boolean = false
|
var showHeartbeatIntervalTextField: Boolean = false
|
||||||
|
var showTimeoutTextField: Boolean = false
|
||||||
var showEnvironmentTextArea: Boolean = false
|
var showEnvironmentTextArea: Boolean = false
|
||||||
var showLoginScripts: Boolean = false
|
var showLoginScripts: Boolean = false
|
||||||
var showBackspaceComboBox: Boolean = false
|
var showBackspaceComboBox: Boolean = false
|
||||||
@@ -33,7 +34,8 @@ open class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
|||||||
|
|
||||||
val charsetComboBox = JComboBox<String>()
|
val charsetComboBox = JComboBox<String>()
|
||||||
val startupCommandTextField = OutlineTextField()
|
val startupCommandTextField = OutlineTextField()
|
||||||
val heartbeatIntervalTextField = IntSpinner(30, minimum = 3, maximum = Int.MAX_VALUE)
|
val heartbeatIntervalTextField = IntSpinner(60, minimum = 3, maximum = Int.MAX_VALUE)
|
||||||
|
val timeoutTextField = IntSpinner(60, minimum = 10, maximum = Int.MAX_VALUE)
|
||||||
val environmentTextArea = FixedLengthTextArea(2048)
|
val environmentTextArea = FixedLengthTextArea(2048)
|
||||||
val loginScripts = mutableListOf<LoginScript>()
|
val loginScripts = mutableListOf<LoginScript>()
|
||||||
val backspaceComboBox = JComboBox<Backspace>()
|
val backspaceComboBox = JComboBox<Backspace>()
|
||||||
@@ -173,7 +175,7 @@ open class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
|||||||
private fun getCenterComponent(): JComponent {
|
private fun getCenterComponent(): JComponent {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $FORM_MARGIN, default:grow",
|
"left:pref, $FORM_MARGIN, default:grow",
|
||||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
val accountOwner = this.accountOwner
|
val accountOwner = this.accountOwner
|
||||||
@@ -210,6 +212,11 @@ open class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
|||||||
.add(characterAtATimeTextField).xy(3, rows).apply { rows += step }
|
.add(characterAtATimeTextField).xy(3, rows).apply { rows += step }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showTimeoutTextField) {
|
||||||
|
builder.add("${I18n.getString("termora.new-host.terminal.timeout")}:").xy(1, rows)
|
||||||
|
.add(timeoutTextField).xy(3, rows).apply { rows += step }
|
||||||
|
}
|
||||||
|
|
||||||
if (showHeartbeatIntervalTextField) {
|
if (showHeartbeatIntervalTextField) {
|
||||||
builder.add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows)
|
||||||
.add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step }
|
.add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step }
|
||||||
@@ -220,14 +227,12 @@ open class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
|||||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (showEnvironmentTextArea) {
|
if (showEnvironmentTextArea) {
|
||||||
builder.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||||
.apply { rows += step }
|
.apply { rows += step }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,24 +82,42 @@ internal class RDPProtocolProvider private constructor() : GenericProtocolProvid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val file = FileUtils.getFile(Application.getTemporaryDir(), randomUUID() + ".rdp")
|
|
||||||
file.outputStream().use { IOUtils.write(sb.toString(), it, Charsets.UTF_8) }
|
|
||||||
|
|
||||||
if (host.authentication.type == AuthenticationType.Password) {
|
if (host.authentication.type == AuthenticationType.Password) {
|
||||||
val systemClipboard = windowScope.window.toolkit.systemClipboard
|
|
||||||
val password = host.authentication.password
|
val password = host.authentication.password
|
||||||
systemClipboard.setContents(StringSelection(password), null)
|
var ep = StringUtils.EMPTY
|
||||||
// clear password
|
|
||||||
swingCoroutineScope.launch(Dispatchers.IO) {
|
if (SystemInfo.isWindows) {
|
||||||
delay(30.seconds)
|
val cmd = "ConvertTo-SecureString '${password}' -AsPlainText -Force | ConvertFrom-SecureString"
|
||||||
if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
|
val process = ProcessBuilder("powershell.exe", "-Command", cmd).start()
|
||||||
if (systemClipboard.getData(DataFlavor.stringFlavor) == password) {
|
if (process.waitFor() == 0) {
|
||||||
systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null)
|
ep = String(process.inputStream.readAllBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ep.isNotBlank()) {
|
||||||
|
sb.append("password 51:b:").append(ep).appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果获取加密密码失败,那么依然要走剪切板
|
||||||
|
if (ep.isBlank() || SystemInfo.isMacOS) {
|
||||||
|
val systemClipboard = windowScope.window.toolkit.systemClipboard
|
||||||
|
systemClipboard.setContents(StringSelection(password), null)
|
||||||
|
// clear password
|
||||||
|
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||||
|
delay(30.seconds)
|
||||||
|
if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
|
||||||
|
if (systemClipboard.getData(DataFlavor.stringFlavor) == password) {
|
||||||
|
systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val file = FileUtils.getFile(Application.getTemporaryDir(), randomUUID() + ".rdp")
|
||||||
|
file.outputStream().use { IOUtils.write(sb.toString(), it, Charsets.UTF_8) }
|
||||||
|
|
||||||
if (SystemInfo.isMacOS) {
|
if (SystemInfo.isMacOS) {
|
||||||
ProcessBuilder("open", file.absolutePath).start()
|
ProcessBuilder("open", file.absolutePath).start()
|
||||||
} else if (SystemInfo.isWindows) {
|
} else if (SystemInfo.isWindows) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
showEnvironmentTextArea = true
|
showEnvironmentTextArea = true
|
||||||
showStartupCommandTextField = true
|
showStartupCommandTextField = true
|
||||||
showHeartbeatIntervalTextField = true
|
showHeartbeatIntervalTextField = true
|
||||||
|
showTimeoutTextField = true
|
||||||
showHighlightSet = true
|
showHighlightSet = true
|
||||||
accountOwner = this@SSHHostOptionsPane.accountOwner
|
accountOwner = this@SSHHostOptionsPane.accountOwner
|
||||||
init()
|
init()
|
||||||
@@ -113,6 +114,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
?: AltKeyModifier.EightBit.name),
|
?: AltKeyModifier.EightBit.name),
|
||||||
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
|
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
|
||||||
?: "-1"),
|
?: "-1"),
|
||||||
|
"timeout" to (terminalOption.timeoutTextField.value ?: 60).toString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,6 +165,10 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
.getOrNull() ?: AltKeyModifier.EightBit
|
.getOrNull() ?: AltKeyModifier.EightBit
|
||||||
|
|
||||||
|
|
||||||
|
val timeout = host.options.extras["timeout"] ?: "60"
|
||||||
|
terminalOption.timeoutTextField.value = timeout.toIntOrNull() ?: 60
|
||||||
|
|
||||||
|
|
||||||
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
|
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
|
||||||
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
|
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
|
||||||
val item = terminalOption.highlightSetComboBox.getItemAt(i)
|
val item = terminalOption.highlightSetComboBox.getItemAt(i)
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ object SshClients {
|
|||||||
|
|
||||||
val HOST_KEY = AttributeRepository.AttributeKey<Host>()
|
val HOST_KEY = AttributeRepository.AttributeKey<Host>()
|
||||||
|
|
||||||
private val timeout = Duration.ofSeconds(30)
|
|
||||||
private val hostManager get() = HostManager.Companion.getInstance()
|
private val hostManager get() = HostManager.Companion.getInstance()
|
||||||
private val log by lazy { LoggerFactory.getLogger(SshClients::class.java) }
|
private val log by lazy { LoggerFactory.getLogger(SshClients::class.java) }
|
||||||
|
|
||||||
@@ -101,6 +100,7 @@ object SshClients {
|
|||||||
session: ClientSession,
|
session: ClientSession,
|
||||||
): ChannelShell {
|
): ChannelShell {
|
||||||
|
|
||||||
|
val timeout = Duration.ofSeconds(host.options.extras["timeout"]?.toLongOrNull() ?: 60)
|
||||||
|
|
||||||
val configuration = PtyChannelConfiguration()
|
val configuration = PtyChannelConfiguration()
|
||||||
configuration.ptyColumns = size.cols
|
configuration.ptyColumns = size.cols
|
||||||
@@ -136,6 +136,7 @@ object SshClients {
|
|||||||
command: String
|
command: String
|
||||||
): Pair<Int, String> {
|
): Pair<Int, String> {
|
||||||
|
|
||||||
|
val timeout = Duration.ofSeconds(60)
|
||||||
val baos = ByteArrayOutputStream()
|
val baos = ByteArrayOutputStream()
|
||||||
val channel = session.createExecChannel(command)
|
val channel = session.createExecChannel(command)
|
||||||
channel.out = baos
|
channel.out = baos
|
||||||
@@ -248,6 +249,7 @@ object SshClients {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val timeout = Duration.ofSeconds(host.options.extras["timeout"]?.toLongOrNull() ?: 60)
|
||||||
val session = client.connect(entry).verify(timeout).session
|
val session = client.connect(entry).verify(timeout).session
|
||||||
if (host.authentication.type == AuthenticationType.Password) {
|
if (host.authentication.type == AuthenticationType.Password) {
|
||||||
if (StringUtils.isNotBlank(host.authentication.password))
|
if (StringUtils.isNotBlank(host.authentication.password))
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
package app.termora.plugin.internal.update
|
|
||||||
|
|
||||||
import app.termora.*
|
|
||||||
import app.termora.actions.AnAction
|
|
||||||
import app.termora.actions.AnActionEvent
|
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
|
||||||
import com.sun.jna.platform.win32.Advapi32
|
|
||||||
import com.sun.jna.platform.win32.WinError
|
|
||||||
import com.sun.jna.platform.win32.WinNT
|
|
||||||
import com.sun.jna.platform.win32.WinReg
|
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import org.jdesktop.swingx.JXEditorPane
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.awt.Dimension
|
|
||||||
import java.awt.KeyboardFocusManager
|
|
||||||
import java.net.URI
|
|
||||||
import javax.swing.BorderFactory
|
|
||||||
import javax.swing.JOptionPane
|
|
||||||
import javax.swing.JScrollPane
|
|
||||||
import javax.swing.UIManager
|
|
||||||
import javax.swing.event.HyperlinkEvent
|
|
||||||
|
|
||||||
internal class AppUpdateAction private constructor() : AnAction(StringUtils.EMPTY, Icons.ideUpdate) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(AppUpdateAction::class.java)
|
|
||||||
|
|
||||||
fun getInstance(): AppUpdateAction {
|
|
||||||
return ApplicationScope.forApplicationScope().getOrCreate(AppUpdateAction::class) { AppUpdateAction() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val updaterManager get() = UpdaterManager.getInstance()
|
|
||||||
|
|
||||||
init {
|
|
||||||
isEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun actionPerformed(evt: AnActionEvent) {
|
|
||||||
showUpdateDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun showUpdateDialog() {
|
|
||||||
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow
|
|
||||||
val lastVersion = updaterManager.lastVersion
|
|
||||||
val editorPane = JXEditorPane()
|
|
||||||
editorPane.contentType = "text/html"
|
|
||||||
editorPane.text = lastVersion.htmlBody
|
|
||||||
editorPane.isEditable = false
|
|
||||||
editorPane.addHyperlinkListener {
|
|
||||||
if (it.eventType == HyperlinkEvent.EventType.ACTIVATED) {
|
|
||||||
Application.browse(it.url.toURI())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editorPane.background = DynamicColor("window")
|
|
||||||
val scrollPane = JScrollPane(editorPane)
|
|
||||||
scrollPane.border = BorderFactory.createEmptyBorder()
|
|
||||||
scrollPane.preferredSize = Dimension(
|
|
||||||
UIManager.getInt("Dialog.width") - 100,
|
|
||||||
UIManager.getInt("Dialog.height") - 100
|
|
||||||
)
|
|
||||||
|
|
||||||
val option = OptionPane.showConfirmDialog(
|
|
||||||
owner,
|
|
||||||
scrollPane,
|
|
||||||
title = I18n.getString("termora.update.title"),
|
|
||||||
messageType = JOptionPane.PLAIN_MESSAGE,
|
|
||||||
optionType = JOptionPane.OK_CANCEL_OPTION,
|
|
||||||
options = arrayOf(
|
|
||||||
I18n.getString("termora.update.update"),
|
|
||||||
I18n.getString("termora.cancel")
|
|
||||||
),
|
|
||||||
initialValue = I18n.getString("termora.update.update")
|
|
||||||
)
|
|
||||||
if (option == JOptionPane.CANCEL_OPTION) {
|
|
||||||
return
|
|
||||||
} else if (option == JOptionPane.OK_OPTION) {
|
|
||||||
updateSelf(lastVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSelf(latestVersion: UpdaterManager.LatestVersion) {
|
|
||||||
val pkg = Updater.getInstance().getLatestPkg()
|
|
||||||
if (SystemInfo.isLinux || pkg == null) {
|
|
||||||
Application.browse(URI.create("https://github.com/TermoraDev/termora/releases/tag/${latestVersion.version}"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val file = pkg.file
|
|
||||||
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner
|
|
||||||
val commands = if (SystemInfo.isMacOS) listOf("open", "-n", file.absolutePath)
|
|
||||||
// 如果安装过,那么直接静默安装和自动启动
|
|
||||||
else if (isAppInstalled()) listOf(
|
|
||||||
file.absolutePath,
|
|
||||||
"/SILENT",
|
|
||||||
"/AUTOSTART",
|
|
||||||
"/NORESTART",
|
|
||||||
"/FORCECLOSEAPPLICATIONS"
|
|
||||||
)
|
|
||||||
// 没有安装过 则打开安装向导
|
|
||||||
else listOf(file.absolutePath)
|
|
||||||
|
|
||||||
if (log.isInfoEnabled) {
|
|
||||||
log.info("restart {}", commands.joinToString(StringUtils.SPACE))
|
|
||||||
}
|
|
||||||
|
|
||||||
TermoraRestarter.getInstance().scheduleRestart(owner, true, commands)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAppInstalled(): Boolean {
|
|
||||||
val keyPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${Application.getName()}_is1"
|
|
||||||
val phkKey = WinReg.HKEYByReference()
|
|
||||||
|
|
||||||
// 尝试打开注册表键
|
|
||||||
val result = Advapi32.INSTANCE.RegOpenKeyEx(
|
|
||||||
WinReg.HKEY_LOCAL_MACHINE,
|
|
||||||
keyPath,
|
|
||||||
0,
|
|
||||||
WinNT.KEY_READ,
|
|
||||||
phkKey
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result == WinError.ERROR_SUCCESS) {
|
|
||||||
// 键存在,关闭句柄
|
|
||||||
Advapi32.INSTANCE.RegCloseKey(phkKey.getValue())
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
// 键不存在或无权限
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package app.termora.plugin.internal.update
|
|
||||||
|
|
||||||
import app.termora.ApplicationRunnerExtension
|
|
||||||
|
|
||||||
internal class MyApplicationRunnerExtension private constructor() : ApplicationRunnerExtension {
|
|
||||||
companion object {
|
|
||||||
val instance = MyApplicationRunnerExtension()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun ready() {
|
|
||||||
Updater.getInstance().scheduleUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
package app.termora.plugin.internal.update
|
|
||||||
|
|
||||||
import app.termora.*
|
|
||||||
import app.termora.Application.httpClient
|
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import okhttp3.Request
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.apache.commons.io.IOUtils
|
|
||||||
import org.semver4j.Semver
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.File
|
|
||||||
import java.net.ProxySelector
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
import kotlin.time.Duration.Companion.hours
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
internal class Updater private constructor() : Disposable {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(Updater::class.java)
|
|
||||||
fun getInstance(): Updater {
|
|
||||||
return ApplicationScope.forApplicationScope().getOrCreate(Updater::class) { Updater() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val updaterManager get() = UpdaterManager.getInstance()
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
||||||
private var isRemindMeNextTime = false
|
|
||||||
private val disabledUpdater get() = Application.getLayout() == AppLayout.Appx
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安装包位置
|
|
||||||
*/
|
|
||||||
private var pkg: LatestPkg? = null
|
|
||||||
|
|
||||||
fun scheduleUpdate() {
|
|
||||||
|
|
||||||
if (disabledUpdater) {
|
|
||||||
if (coroutineScope.isActive) {
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
|
||||||
// 启动 3 分钟后才是检查
|
|
||||||
if (Application.isUnknownVersion().not()) {
|
|
||||||
delay(3.seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
while (coroutineScope.isActive) {
|
|
||||||
// 下次提醒我
|
|
||||||
if (isRemindMeNextTime) break
|
|
||||||
|
|
||||||
try {
|
|
||||||
checkUpdate()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (log.isWarnEnabled) {
|
|
||||||
log.warn(e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 之后每 3 小时检查一次
|
|
||||||
delay(3.hours.inWholeMilliseconds)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkUpdate() {
|
|
||||||
|
|
||||||
// Windows 应用商店
|
|
||||||
if (disabledUpdater) return
|
|
||||||
|
|
||||||
val latestVersion = updaterManager.fetchLatestVersion()
|
|
||||||
if (latestVersion.isSelf) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 之所以放到后面检查是不是开发版本,是需要发起一次检测请求,以方便调试
|
|
||||||
if (Application.isUnknownVersion()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val newVersion = Semver.parse(latestVersion.version) ?: return
|
|
||||||
val version = Semver.parse(Application.getVersion()) ?: return
|
|
||||||
if (newVersion <= version) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
downloadLatestPkg(latestVersion)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (log.isErrorEnabled) {
|
|
||||||
log.error(e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun downloadLatestPkg(latestVersion: UpdaterManager.LatestVersion) {
|
|
||||||
if (SystemInfo.isLinux) return
|
|
||||||
|
|
||||||
setLatestPkg(null)
|
|
||||||
|
|
||||||
val arch = if (SystemInfo.isAARCH64) "aarch64" else "x86-64"
|
|
||||||
val osName = if (SystemInfo.isWindows) "windows" else "osx"
|
|
||||||
val suffix = if (SystemInfo.isWindows) "exe" else "dmg"
|
|
||||||
val filename = "termora-${latestVersion.version}-${osName}-${arch}.${suffix}"
|
|
||||||
val asset = latestVersion.assets.find { it.name == filename } ?: return
|
|
||||||
|
|
||||||
val response = httpClient
|
|
||||||
.newBuilder()
|
|
||||||
.callTimeout(15, TimeUnit.MINUTES)
|
|
||||||
.readTimeout(15, TimeUnit.MINUTES)
|
|
||||||
.proxySelector(ProxySelector.getDefault())
|
|
||||||
.build()
|
|
||||||
.newCall(Request.Builder().url(asset.downloadUrl).build())
|
|
||||||
.execute()
|
|
||||||
if (response.isSuccessful.not()) {
|
|
||||||
if (log.isErrorEnabled) {
|
|
||||||
log.warn("Failed to download latest version ${latestVersion.version}, response code ${response.code}")
|
|
||||||
}
|
|
||||||
IOUtils.closeQuietly(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = response.body
|
|
||||||
val input = body.byteStream()
|
|
||||||
val file = FileUtils.getFile(Application.getTemporaryDir(), "${UUID.randomUUID()}-${filename}")
|
|
||||||
val output = file.outputStream()
|
|
||||||
|
|
||||||
val downloaded = runCatching { IOUtils.copy(input, output) }.isSuccess
|
|
||||||
IOUtils.closeQuietly(input, output, body, response)
|
|
||||||
|
|
||||||
if (!downloaded) {
|
|
||||||
if (log.isErrorEnabled) {
|
|
||||||
log.error("Failed to download latest version to $filename")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isInfoEnabled) {
|
|
||||||
log.info("Successfully downloaded latest version to $file")
|
|
||||||
}
|
|
||||||
|
|
||||||
setLatestPkg(LatestPkg(latestVersion.version, file))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setLatestPkg(pkg: LatestPkg?) {
|
|
||||||
this.pkg = pkg
|
|
||||||
SwingUtilities.invokeLater { AppUpdateAction.getInstance().isEnabled = pkg != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLatestPkg(): LatestPkg? {
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
data class LatestPkg(val version: String, val file: File)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package app.termora.plugin.internal.updater
|
||||||
|
|
||||||
|
import app.termora.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.semver4j.Semver
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.awt.KeyboardFocusManager
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
internal class MyApplicationRunnerExtension private constructor() : ApplicationRunnerExtension {
|
||||||
|
companion object {
|
||||||
|
val instance = MyApplicationRunnerExtension()
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(MyApplicationRunnerExtension::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val disabledUpdater get() = Application.getLayout() == AppLayout.Appx
|
||||||
|
private val updaterManager get() = UpdaterManager.getInstance()
|
||||||
|
|
||||||
|
|
||||||
|
override fun ready() {
|
||||||
|
swingCoroutineScope.launch {
|
||||||
|
try {
|
||||||
|
delay(3.seconds)
|
||||||
|
scheduleUpdate()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun scheduleUpdate() {
|
||||||
|
if (disabledUpdater) return
|
||||||
|
|
||||||
|
val latestVersion = updaterManager.fetchLatestVersion()
|
||||||
|
if (latestVersion.isSelf) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val newVersion = Semver.parse(latestVersion.version) ?: return
|
||||||
|
val version = Semver.parse(Application.getVersion()) ?: return
|
||||||
|
if (newVersion <= version) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow
|
||||||
|
?: TermoraFrameManager.getInstance().getWindows().firstOrNull()
|
||||||
|
if (owner == null) return
|
||||||
|
|
||||||
|
val dialog = UpdaterDialog(owner, latestVersion)
|
||||||
|
dialog.isModal = true
|
||||||
|
dialog.isVisible = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
package app.termora.plugin.internal.updater
|
||||||
|
|
||||||
|
import app.termora.*
|
||||||
|
import app.termora.Application.httpClient
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
|
import com.sun.jna.platform.win32.Advapi32
|
||||||
|
import com.sun.jna.platform.win32.WinError
|
||||||
|
import com.sun.jna.platform.win32.WinNT
|
||||||
|
import com.sun.jna.platform.win32.WinReg
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.swing.Swing
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.apache.commons.lang3.Strings
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
|
import org.apache.commons.net.io.CopyStreamEvent
|
||||||
|
import org.apache.commons.net.io.CopyStreamListener
|
||||||
|
import org.apache.commons.net.io.Util
|
||||||
|
import org.jdesktop.swingx.JXEditorPane
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.Window
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.event.HyperlinkEvent
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
internal class UpdaterDialog(owner: Window, private val latestVersion: UpdaterManager.LatestVersion) :
|
||||||
|
DialogWrapper(owner) {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(UpdaterDialog::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class State {
|
||||||
|
Ready,
|
||||||
|
Downloading,
|
||||||
|
Downloaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
private val progressBar = JProgressBar()
|
||||||
|
private val okAction = OkAction(I18n.getString("termora.update.update"))
|
||||||
|
private val okButton = JButton(okAction)
|
||||||
|
private var state = State.Ready
|
||||||
|
private val westSourcePanel = Box.createHorizontalBox()
|
||||||
|
private val glue = Box.createHorizontalGlue()
|
||||||
|
private val layout = Application.getLayout()
|
||||||
|
private val dialog get() = this
|
||||||
|
|
||||||
|
private val executorService = Executors.newVirtualThreadPerTaskExecutor()
|
||||||
|
private val coroutineDispatcher = executorService.asCoroutineDispatcher()
|
||||||
|
private val coroutineScope = CoroutineScope(coroutineDispatcher)
|
||||||
|
|
||||||
|
init {
|
||||||
|
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
|
||||||
|
isModal = true
|
||||||
|
controlsVisible = false
|
||||||
|
isResizable = false
|
||||||
|
escapeDispose = false
|
||||||
|
title = I18n.getString("termora.update.title")
|
||||||
|
setLocationRelativeTo(owner)
|
||||||
|
|
||||||
|
init()
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
progressBar.isIndeterminate = true
|
||||||
|
progressBar.isStringPainted = true
|
||||||
|
|
||||||
|
westSourcePanel.add(glue)
|
||||||
|
westSourcePanel.add(progressBar)
|
||||||
|
westSourcePanel.add(Box.createHorizontalStrut(20))
|
||||||
|
progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
Disposer.register(disposable, object : Disposable {
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
coroutineDispatcher.close()
|
||||||
|
executorService.shutdownNow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent {
|
||||||
|
val editorPane = JXEditorPane()
|
||||||
|
editorPane.contentType = "text/html"
|
||||||
|
editorPane.text = latestVersion.htmlBody
|
||||||
|
editorPane.isEditable = false
|
||||||
|
editorPane.addHyperlinkListener {
|
||||||
|
if (it.eventType == HyperlinkEvent.EventType.ACTIVATED) {
|
||||||
|
Application.browse(it.url.toURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editorPane.background = DynamicColor("window")
|
||||||
|
val scrollPane = JScrollPane(editorPane)
|
||||||
|
scrollPane.border = BorderFactory.createEmptyBorder()
|
||||||
|
return scrollPane
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createWestSourcePanel(): JComponent {
|
||||||
|
return westSourcePanel
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun createActions(): List<AbstractAction> {
|
||||||
|
return listOf(okAction, createCancelAction())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createJButtonForAction(action: Action): JButton {
|
||||||
|
if (action == okAction) {
|
||||||
|
rootPane.defaultButton = okButton
|
||||||
|
return okButton
|
||||||
|
}
|
||||||
|
return super.createJButtonForAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("CascadeIf")
|
||||||
|
override fun doOKAction() {
|
||||||
|
if (state == State.Ready) {
|
||||||
|
okButton.text = "${okButton.text}..."
|
||||||
|
okButton.isEnabled = false
|
||||||
|
progressBar.isVisible = true
|
||||||
|
glue.isVisible = false
|
||||||
|
state = State.Downloading
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
downloadPkg()
|
||||||
|
} catch (_: LatestReleaseException) {
|
||||||
|
Application.browse(URI.create("https://github.com/TermoraDev/termora/releases/latest"))
|
||||||
|
SwingUtilities.invokeLater { doCancelAction() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) log.error(e.message, e)
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
|
||||||
|
state = State.Ready
|
||||||
|
okButton.isEnabled = true
|
||||||
|
okButton.text = okAction.name
|
||||||
|
progressBar.isVisible = false
|
||||||
|
glue.isVisible = true
|
||||||
|
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
dialog,
|
||||||
|
StringUtils.defaultIfBlank(e.message, ExceptionUtils.getRootCauseMessage(e)).toString(),
|
||||||
|
messageType = JOptionPane.ERROR_MESSAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
progressBar.isVisible = false
|
||||||
|
glue.isVisible = true
|
||||||
|
okButton.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if (state == State.Downloading) {
|
||||||
|
return
|
||||||
|
} else if (state == State.Downloaded) {
|
||||||
|
super.doOKAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun downloadPkg() {
|
||||||
|
val arch = if (SystemInfo.isAARCH64) "aarch64" else "x86-64"
|
||||||
|
val osName = if (SystemInfo.isWindows) "windows" else if (SystemInfo.isLinux) "linux" else "osx"
|
||||||
|
val suffix = when (layout) {
|
||||||
|
AppLayout.Zip -> "zip"
|
||||||
|
AppLayout.Exe -> "exe"
|
||||||
|
|
||||||
|
AppLayout.App -> "dmg"
|
||||||
|
|
||||||
|
AppLayout.TarGz -> "tar.gz"
|
||||||
|
AppLayout.AppImage -> "AppImage"
|
||||||
|
AppLayout.Deb -> "deb"
|
||||||
|
else -> throw LatestReleaseException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val filename = "termora-${latestVersion.version}-${osName}-${arch}.${suffix}"
|
||||||
|
val asset = latestVersion.assets.find { it.name == filename } ?: throw LatestReleaseException()
|
||||||
|
|
||||||
|
var url = asset.downloadUrl
|
||||||
|
|
||||||
|
if (I18n.isChinaMainland()) {
|
||||||
|
url = Strings.CI.replace(
|
||||||
|
url,
|
||||||
|
"https://github.com/TermoraDev/termora/releases/download/",
|
||||||
|
"https://dl.termora.cn/termora/"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = httpClient.newCall(Request.Builder().url(url).get().build())
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.isSuccessful.not()) {
|
||||||
|
response.closeQuietly()
|
||||||
|
throw IllegalStateException("Failed to download asset $filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
progressBar.isIndeterminate = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val listener = object : CopyStreamListener {
|
||||||
|
override fun bytesTransferred(event: CopyStreamEvent?) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bytesTransferred(
|
||||||
|
totalBytesTransferred: Long,
|
||||||
|
bytesTransferred: Int,
|
||||||
|
streamSize: Long
|
||||||
|
) {
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
val progress = 1.0 * totalBytesTransferred / asset.size * 100
|
||||||
|
progressBar.value = floor(progress).toInt()
|
||||||
|
progressBar.string = formatBytes(totalBytesTransferred) + " / " + formatBytes(asset.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = FileUtils.getFile(Application.getTemporaryDir(), "${UUID.randomUUID()}-${filename}")
|
||||||
|
|
||||||
|
response.use {
|
||||||
|
response.body.byteStream().use { input ->
|
||||||
|
file.outputStream().use { output ->
|
||||||
|
Util.copyStream(
|
||||||
|
input, output, Util.DEFAULT_COPY_BUFFER_SIZE,
|
||||||
|
asset.size, listener
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
state = State.Downloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
val commands = mutableListOf<String>()
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
commands.addAll(listOf("open", "-n", file.absolutePath))
|
||||||
|
} else if (layout == AppLayout.Zip) {
|
||||||
|
commands.addAll(listOf("explorer", "/select," + file.absolutePath))
|
||||||
|
} else if (layout == AppLayout.Exe) {
|
||||||
|
// 如果安装过,那么直接静默安装和自动启动
|
||||||
|
if (isAppInstalled()) {
|
||||||
|
commands.addAll(
|
||||||
|
listOf(
|
||||||
|
file.absolutePath,
|
||||||
|
"/SILENT",
|
||||||
|
"/AUTOSTART",
|
||||||
|
"/NORESTART",
|
||||||
|
"/FORCECLOSEAPPLICATIONS"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
commands.addAll(listOf(file.absolutePath))
|
||||||
|
}
|
||||||
|
} else if (SystemInfo.isLinux) {
|
||||||
|
commands.addAll(listOf("xdg-open", file.parentFile.absolutePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("commands: {}", commands.joinToString(StringUtils.SPACE))
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
super.doOKAction()
|
||||||
|
TermoraRestarter.getInstance().scheduleRestart(owner, true, commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LatestReleaseException() : RuntimeException()
|
||||||
|
|
||||||
|
private fun isAppInstalled(): Boolean {
|
||||||
|
try {
|
||||||
|
val keyPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${Application.getName()}_is1"
|
||||||
|
val phkKey = WinReg.HKEYByReference()
|
||||||
|
|
||||||
|
// 尝试打开注册表键
|
||||||
|
val result = Advapi32.INSTANCE.RegOpenKeyEx(
|
||||||
|
WinReg.HKEY_LOCAL_MACHINE,
|
||||||
|
keyPath,
|
||||||
|
0,
|
||||||
|
WinNT.KEY_READ,
|
||||||
|
phkKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result == WinError.ERROR_SUCCESS) {
|
||||||
|
// 键存在,关闭句柄
|
||||||
|
Advapi32.INSTANCE.RegCloseKey(phkKey.getValue())
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// 键不存在或无权限
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addNotify() {
|
||||||
|
super.addNotify()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
package app.termora.plugin.internal.update
|
package app.termora.plugin.internal.updater
|
||||||
|
|
||||||
import app.termora.ApplicationRunnerExtension
|
import app.termora.ApplicationRunnerExtension
|
||||||
import app.termora.plugin.Extension
|
import app.termora.plugin.Extension
|
||||||
import app.termora.plugin.InternalPlugin
|
import app.termora.plugin.InternalPlugin
|
||||||
|
|
||||||
internal class UpdatePlugin : InternalPlugin() {
|
internal class UpdaterPlugin : InternalPlugin() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
support.addExtension(ApplicationRunnerExtension::class.java) { MyApplicationRunnerExtension.instance }
|
support.addExtension(ApplicationRunnerExtension::class.java) { MyApplicationRunnerExtension.instance }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
return "Update"
|
return "Updater"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||||
return support.getExtensions(clazz)
|
return support.getExtensions(clazz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -430,6 +430,7 @@ internal class TransportPanel(
|
|||||||
})
|
})
|
||||||
|
|
||||||
table.addMouseListener(object : MouseAdapter() {
|
table.addMouseListener(object : MouseAdapter() {
|
||||||
|
private val sftp get() = DatabaseManager.getInstance().sftp
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
if (SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) {
|
if (SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) {
|
||||||
var row = table.selectedRow
|
var row = table.selectedRow
|
||||||
@@ -438,10 +439,18 @@ internal class TransportPanel(
|
|||||||
val attributes = model.getAttributes(row)
|
val attributes = model.getAttributes(row)
|
||||||
if (attributes.isDirectory) {
|
if (attributes.isDirectory) {
|
||||||
enterSelectionFolder()
|
enterSelectionFolder()
|
||||||
|
} else if (sftp.dbClickBehavior == "Edit") {
|
||||||
|
val path = model.getPath(row)
|
||||||
|
val target = Application.createSubTemporaryDir().resolve(path.name)
|
||||||
|
val transferId = internalTransferManager.addHighTransfer(path, target)
|
||||||
|
editTransferListener.addListenTransfer(transferId)
|
||||||
} else {
|
} else {
|
||||||
val paths = listOf(model.getPath(row) to attributes)
|
val paths = listOf(model.getPath(row) to attributes)
|
||||||
if (loader.isOpened() && internalTransferManager.canTransfer(paths.map { it.first })) {
|
if (loader.isOpened() && internalTransferManager.canTransfer(paths.map { it.first })) {
|
||||||
internalTransferManager.addTransfer(paths, InternalTransferManager.TransferMode.Transfer)
|
internalTransferManager.addTransfer(
|
||||||
|
paths,
|
||||||
|
InternalTransferManager.TransferMode.Transfer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (SwingUtilities.isRightMouseButton(e)) {
|
} else if (SwingUtilities.isRightMouseButton(e)) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(NewHostTree::class.java)
|
private val log = LoggerFactory.getLogger(NewHostTree::class.java)
|
||||||
private val CSV_HEADERS = arrayOf("Folders", "Label", "Hostname", "Port", "Username", "Protocol")
|
private val CSV_HEADERS = arrayOf("Folders", "Label", "Protocol", "Hostname", "Port", "Username", "Password")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// 基本信息
|
// 基本信息
|
||||||
@@ -577,26 +577,29 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
"Projects/Dev",
|
"Projects/Dev",
|
||||||
"Web Server",
|
"Web Server",
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
"192.168.1.1",
|
"192.168.1.1",
|
||||||
"22",
|
"22",
|
||||||
"root",
|
"root",
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
"Projects/Prod",
|
"Projects/Prod",
|
||||||
"Web Server",
|
"Web Server",
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
"serverhost.com",
|
"serverhost.com",
|
||||||
"2222",
|
"2222",
|
||||||
"root",
|
"root",
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
StringUtils.EMPTY,
|
StringUtils.EMPTY,
|
||||||
"Web Server",
|
"Web Server",
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
"serverhost.com",
|
"serverhost.com",
|
||||||
"2222",
|
"2222",
|
||||||
"user",
|
"user",
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OptionPane.openFileInFolder(
|
OptionPane.openFileInFolder(
|
||||||
@@ -683,10 +686,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
groups.joinToString("/"),
|
groups.joinToString("/"),
|
||||||
label,
|
label,
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
target,
|
target,
|
||||||
port,
|
port,
|
||||||
StringUtils.EMPTY,
|
StringUtils.EMPTY,
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,10 +707,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
StringUtils.EMPTY,
|
StringUtils.EMPTY,
|
||||||
StringUtils.defaultString(entry.host),
|
StringUtils.defaultString(entry.host),
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
StringUtils.defaultString(entry.hostName),
|
StringUtils.defaultString(entry.hostName),
|
||||||
if (entry.port == 0) 22 else entry.port,
|
if (entry.port == 0) 22 else entry.port,
|
||||||
StringUtils.defaultString(entry.username),
|
StringUtils.defaultString(entry.username),
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -746,10 +751,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
folders.joinToString("/"),
|
folders.joinToString("/"),
|
||||||
label,
|
label,
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
hostname,
|
hostname,
|
||||||
port.toString(),
|
port.toString(),
|
||||||
username,
|
username,
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -776,10 +782,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
StringUtils.EMPTY,
|
StringUtils.EMPTY,
|
||||||
label,
|
label,
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
hostname,
|
hostname,
|
||||||
port.toString(),
|
port.toString(),
|
||||||
username,
|
username,
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -819,7 +826,15 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
if (segments.first() != "#109#0") continue
|
if (segments.first() != "#109#0") continue
|
||||||
val hostname = segments.getOrNull(1) ?: StringUtils.EMPTY
|
val hostname = segments.getOrNull(1) ?: StringUtils.EMPTY
|
||||||
val port = segments.getOrNull(2) ?: 22
|
val port = segments.getOrNull(2) ?: 22
|
||||||
printer.printRecord(folders, key, hostname, port, StringUtils.EMPTY, SSHProtocolProvider.PROTOCOL)
|
printer.printRecord(
|
||||||
|
folders,
|
||||||
|
key,
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
StringUtils.EMPTY,
|
||||||
|
StringUtils.EMPTY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,7 +863,15 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
val label = file.nameWithoutExtension
|
val label = file.nameWithoutExtension
|
||||||
val port = ini.get("CONNECTION", "Port")?.toIntOrNull() ?: 22
|
val port = ini.get("CONNECTION", "Port")?.toIntOrNull() ?: 22
|
||||||
val username = ini.get("CONNECTION:AUTHENTICATION", "UserName") ?: StringUtils.EMPTY
|
val username = ini.get("CONNECTION:AUTHENTICATION", "UserName") ?: StringUtils.EMPTY
|
||||||
printer.printRecord(folders, label, hostname, port, username, SSHProtocolProvider.PROTOCOL)
|
printer.printRecord(
|
||||||
|
folders,
|
||||||
|
label,
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
StringUtils.EMPTY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,10 +908,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
folders,
|
folders,
|
||||||
StringUtils.defaultIfBlank(label, host),
|
StringUtils.defaultIfBlank(label, host),
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (log.isErrorEnabled) {
|
if (log.isErrorEnabled) {
|
||||||
@@ -940,10 +964,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
printer.printRecord(
|
printer.printRecord(
|
||||||
folderNames.joinToString("/"),
|
folderNames.joinToString("/"),
|
||||||
StringUtils.defaultIfBlank(title, hostname),
|
StringUtils.defaultIfBlank(title, hostname),
|
||||||
|
SSHProtocolProvider.PROTOCOL,
|
||||||
hostname,
|
hostname,
|
||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
SSHProtocolProvider.PROTOCOL
|
StringUtils.EMPTY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,6 +1000,7 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
val hostname = map["Hostname"] ?: StringUtils.EMPTY
|
val hostname = map["Hostname"] ?: StringUtils.EMPTY
|
||||||
val port = map["Port"]?.toIntOrNull() ?: 22
|
val port = map["Port"]?.toIntOrNull() ?: 22
|
||||||
val username = map["Username"] ?: StringUtils.EMPTY
|
val username = map["Username"] ?: StringUtils.EMPTY
|
||||||
|
val password = map["Password"] ?: StringUtils.EMPTY
|
||||||
val protocol = map["Protocol"] ?: "SSH"
|
val protocol = map["Protocol"] ?: "SSH"
|
||||||
// 仅支持 SSH、RDP 协议
|
// 仅支持 SSH、RDP 协议
|
||||||
if (StringUtils.equalsAnyIgnoreCase(protocol, "SSH", "RDP").not()) continue
|
if (StringUtils.equalsAnyIgnoreCase(protocol, "SSH", "RDP").not()) continue
|
||||||
@@ -1017,6 +1043,15 @@ class NewHostTree : SimpleTree(), Disposable {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (password.isNotBlank()) {
|
||||||
|
n.host = n.host.copy(
|
||||||
|
authentication = Authentication.No.copy(
|
||||||
|
type = AuthenticationType.Password,
|
||||||
|
password = password,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
nodes.add(n)
|
nodes.add(n)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ termora.settings.keymap.already-exists=The shortcut [{0}] is already in use by [
|
|||||||
|
|
||||||
|
|
||||||
termora.settings.sftp.edit-command=Edit Command
|
termora.settings.sftp.edit-command=Edit Command
|
||||||
|
termora.settings.sftp.db-click-behavior=Double-click
|
||||||
termora.settings.sftp.fixed-tab=Fixed tab
|
termora.settings.sftp.fixed-tab=Fixed tab
|
||||||
termora.settings.sftp.default-directory=Default Directory
|
termora.settings.sftp.default-directory=Default Directory
|
||||||
termora.settings.sftp.preserve-time=Preserve original file modification time
|
termora.settings.sftp.preserve-time=Preserve original file modification time
|
||||||
@@ -182,6 +183,7 @@ termora.new-host.terminal.encoding=Encoding
|
|||||||
termora.new-host.terminal.backspace=Backspace
|
termora.new-host.terminal.backspace=Backspace
|
||||||
termora.new-host.terminal.character-mode=Character-at-a-time
|
termora.new-host.terminal.character-mode=Character-at-a-time
|
||||||
termora.new-host.terminal.heartbeat-interval=Heartbeat Interval
|
termora.new-host.terminal.heartbeat-interval=Heartbeat Interval
|
||||||
|
termora.new-host.terminal.timeout=Timeout
|
||||||
termora.new-host.terminal.startup-commands=Startup Command
|
termora.new-host.terminal.startup-commands=Startup Command
|
||||||
termora.new-host.terminal.alt-modifier=Alt modifier
|
termora.new-host.terminal.alt-modifier=Alt modifier
|
||||||
termora.new-host.terminal.alt-modifier.eight-bit=8-bit characters
|
termora.new-host.terminal.alt-modifier.eight-bit=8-bit characters
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ termora.settings.keymap.already-exists=Комбинация [{0}] уже исп
|
|||||||
|
|
||||||
|
|
||||||
termora.settings.sftp.edit-command=Редактировать команду
|
termora.settings.sftp.edit-command=Редактировать команду
|
||||||
|
termora.settings.sftp.db-click-behavior=двойной щелчок
|
||||||
termora.settings.sftp.fixed-tab=Отображать вкладку
|
termora.settings.sftp.fixed-tab=Отображать вкладку
|
||||||
termora.settings.sftp.default-directory=Директория по умолчанию
|
termora.settings.sftp.default-directory=Директория по умолчанию
|
||||||
termora.settings.sftp.preserve-time=Сохранять исходное время модификации файла
|
termora.settings.sftp.preserve-time=Сохранять исходное время модификации файла
|
||||||
@@ -162,6 +163,7 @@ termora.new-host.proxy=Прокси
|
|||||||
termora.new-host.terminal=${termora.settings.terminal}
|
termora.new-host.terminal=${termora.settings.terminal}
|
||||||
termora.new-host.terminal.encoding=Кодировка
|
termora.new-host.terminal.encoding=Кодировка
|
||||||
termora.new-host.terminal.heartbeat-interval=Интервал опроса
|
termora.new-host.terminal.heartbeat-interval=Интервал опроса
|
||||||
|
termora.new-host.terminal.timeout=Тайм-аут
|
||||||
termora.new-host.terminal.startup-commands=Команда запуска
|
termora.new-host.terminal.startup-commands=Команда запуска
|
||||||
termora.new-host.terminal.env=переменные
|
termora.new-host.terminal.env=переменные
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ termora.settings.keymap.already-exists=快捷键 [{0}] 已经被 [{1}] 占用
|
|||||||
|
|
||||||
|
|
||||||
termora.settings.sftp.edit-command=编辑命令
|
termora.settings.sftp.edit-command=编辑命令
|
||||||
|
termora.settings.sftp.db-click-behavior=双击行为
|
||||||
termora.settings.sftp.fixed-tab=固定标签
|
termora.settings.sftp.fixed-tab=固定标签
|
||||||
termora.settings.sftp.default-directory=默认目录
|
termora.settings.sftp.default-directory=默认目录
|
||||||
termora.settings.sftp.preserve-time=保留原始文件修改时间
|
termora.settings.sftp.preserve-time=保留原始文件修改时间
|
||||||
@@ -175,6 +176,7 @@ termora.new-host.terminal.encoding=编码
|
|||||||
termora.new-host.terminal.backspace=退格键
|
termora.new-host.terminal.backspace=退格键
|
||||||
termora.new-host.terminal.character-mode=单字符模式
|
termora.new-host.terminal.character-mode=单字符模式
|
||||||
termora.new-host.terminal.heartbeat-interval=心跳间隔
|
termora.new-host.terminal.heartbeat-interval=心跳间隔
|
||||||
|
termora.new-host.terminal.timeout=超时时间
|
||||||
termora.new-host.terminal.startup-commands=启动命令
|
termora.new-host.terminal.startup-commands=启动命令
|
||||||
termora.new-host.terminal.alt-modifier=Alt 键修饰
|
termora.new-host.terminal.alt-modifier=Alt 键修饰
|
||||||
termora.new-host.terminal.alt-modifier.eight-bit=8 位字符
|
termora.new-host.terminal.alt-modifier.eight-bit=8 位字符
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ termora.settings.keymap.action=操作
|
|||||||
termora.settings.keymap.already-exists=快捷鍵 [{0}] 已經被 [{1}] 占用
|
termora.settings.keymap.already-exists=快捷鍵 [{0}] 已經被 [{1}] 占用
|
||||||
|
|
||||||
termora.settings.sftp.edit-command=編輯命令
|
termora.settings.sftp.edit-command=編輯命令
|
||||||
|
termora.settings.sftp.db-click-behavior=按兩下行為
|
||||||
termora.settings.sftp.fixed-tab=固定標籤
|
termora.settings.sftp.fixed-tab=固定標籤
|
||||||
termora.settings.sftp.default-directory=預設目錄
|
termora.settings.sftp.default-directory=預設目錄
|
||||||
termora.settings.sftp.preserve-time=保留原始文件修改時間
|
termora.settings.sftp.preserve-time=保留原始文件修改時間
|
||||||
@@ -178,6 +179,7 @@ termora.new-host.terminal.alt-modifier=Alt 鍵修飾
|
|||||||
termora.new-host.terminal.alt-modifier.eight-bit=8 位元字符
|
termora.new-host.terminal.alt-modifier.eight-bit=8 位元字符
|
||||||
termora.new-host.terminal.alt-modifier.by-esc=ESC 鍵作為前綴
|
termora.new-host.terminal.alt-modifier.by-esc=ESC 鍵作為前綴
|
||||||
termora.new-host.terminal.heartbeat-interval=心跳間隔
|
termora.new-host.terminal.heartbeat-interval=心跳間隔
|
||||||
|
termora.new-host.terminal.timeout=超時時間
|
||||||
termora.new-host.terminal.env=環境
|
termora.new-host.terminal.env=環境
|
||||||
termora.new-host.terminal.login-scripts=登入腳本
|
termora.new-host.terminal.login-scripts=登入腳本
|
||||||
termora.new-host.terminal.expect=預期
|
termora.new-host.terminal.expect=預期
|
||||||
|
|||||||
Reference in New Issue
Block a user