mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support telnet
This commit is contained in:
@@ -78,6 +78,7 @@ object Icons {
|
||||
val import by lazy { DynamicIcon("icons/import.svg", "icons/import_dark.svg") }
|
||||
val export by lazy { DynamicIcon("icons/export.svg", "icons/export_dark.svg") }
|
||||
val terminal by lazy { DynamicIcon("icons/terminal.svg", "icons/terminal_dark.svg") }
|
||||
val telnet by lazy { DynamicIcon("icons/telnet.svg", "icons/telnet_dark.svg") }
|
||||
val ssh by lazy { DynamicIcon("icons/ssh.svg", "icons/ssh_dark.svg") }
|
||||
val ftp by lazy { DynamicIcon("icons/ftp.svg", "icons/ftp_dark.svg") }
|
||||
val minio by lazy { DynamicIcon("icons/minio.svg", "icons/minio_dark.svg") }
|
||||
|
||||
@@ -11,6 +11,7 @@ import app.termora.plugin.internal.rdp.RDPInternalPlugin
|
||||
import app.termora.plugin.internal.serial.SerialInternalPlugin
|
||||
import app.termora.plugin.internal.sftppty.SFTPPtyInternalPlugin
|
||||
import app.termora.plugin.internal.ssh.SSHInternalPlugin
|
||||
import app.termora.plugin.internal.telnet.TelnetInternalPlugin
|
||||
import app.termora.plugin.internal.wsl.WSLInternalPlugin
|
||||
import app.termora.swingCoroutineScope
|
||||
import app.termora.terminal.panel.vw.FloatingToolbarPlugin
|
||||
@@ -111,12 +112,14 @@ internal class PluginManager private constructor() {
|
||||
|
||||
// ssh plugin
|
||||
plugins.add(PluginDescriptor(SSHInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// serial plugin
|
||||
plugins.add(PluginDescriptor(SerialInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// local plugin
|
||||
plugins.add(PluginDescriptor(LocalInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// rdp plugin
|
||||
plugins.add(PluginDescriptor(RDPInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// telnet plugin
|
||||
plugins.add(PluginDescriptor(TelnetInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// serial plugin
|
||||
plugins.add(PluginDescriptor(SerialInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// wsl plugin
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
plugins.add(PluginDescriptor(WSLInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
|
||||
@@ -10,7 +10,10 @@ import java.awt.Component
|
||||
import java.awt.event.ItemEvent
|
||||
import javax.swing.*
|
||||
|
||||
class BasicProxyOption(private val proxyTypes: List<ProxyType> = listOf(ProxyType.HTTP, ProxyType.SOCKS5)) :
|
||||
class BasicProxyOption(
|
||||
private val proxyTypes: List<ProxyType> = listOf(ProxyType.HTTP, ProxyType.SOCKS5),
|
||||
private val authenticationTypes: List<AuthenticationType> = listOf(AuthenticationType.Password),
|
||||
) :
|
||||
JPanel(BorderLayout()), Option {
|
||||
private val formMargin = "7dlu"
|
||||
|
||||
@@ -67,7 +70,9 @@ class BasicProxyOption(private val proxyTypes: List<ProxyType> = listOf(ProxyTyp
|
||||
}
|
||||
|
||||
proxyAuthenticationTypeComboBox.addItem(AuthenticationType.No)
|
||||
proxyAuthenticationTypeComboBox.addItem(AuthenticationType.Password)
|
||||
for (type in authenticationTypes) {
|
||||
proxyAuthenticationTypeComboBox.addItem(type)
|
||||
}
|
||||
|
||||
proxyUsernameTextField.text = "root"
|
||||
|
||||
|
||||
@@ -18,4 +18,8 @@ internal class LocalProtocolHostPanelExtension private constructor() : ProtocolH
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return LocalProtocolHostPanel()
|
||||
}
|
||||
|
||||
override fun ordered(): Long {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,8 @@ internal class RDPProtocolHostPanelExtension private constructor() : ProtocolHos
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return RDPProtocolHostPanel()
|
||||
}
|
||||
|
||||
override fun ordered(): Long {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,7 @@ internal class SerialProtocolHostPanelExtension private constructor() : Protocol
|
||||
return SerialProtocolHostPanel()
|
||||
}
|
||||
|
||||
override fun ordered(): Long {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,8 @@ internal class SSHProtocolHostPanelExtension private constructor() : ProtocolHos
|
||||
return SSHProtocolHostPanel(accountOwner)
|
||||
}
|
||||
|
||||
override fun ordered(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.keymgr.KeyManager
|
||||
import app.termora.plugin.internal.BasicProxyOption
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.extras.components.FlatComboBox
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.Window
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
@Suppress("CascadeIf")
|
||||
open class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
|
||||
protected val generalOption = GeneralOption()
|
||||
|
||||
// telnet 不支持代理密码
|
||||
protected val proxyOption = BasicProxyOption(authenticationTypes = listOf())
|
||||
protected val terminalOption = TerminalOption()
|
||||
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
|
||||
init {
|
||||
addOption(generalOption)
|
||||
addOption(proxyOption)
|
||||
addOption(terminalOption)
|
||||
}
|
||||
|
||||
|
||||
open fun getHost(): Host {
|
||||
val name = generalOption.nameTextField.text
|
||||
val protocol = TelnetProtocolProvider.PROTOCOL
|
||||
val host = generalOption.hostTextField.text
|
||||
val port = (generalOption.portTextField.value ?: 22) as Int
|
||||
var authentication = Authentication.No
|
||||
var proxy = Proxy.Companion.No
|
||||
val authenticationType = generalOption.authenticationTypeComboBox.selectedItem as AuthenticationType
|
||||
|
||||
if (authenticationType == AuthenticationType.Password) {
|
||||
authentication = authentication.copy(
|
||||
type = authenticationType,
|
||||
password = String(generalOption.passwordTextField.password)
|
||||
)
|
||||
}
|
||||
|
||||
if (proxyOption.proxyTypeComboBox.selectedItem != ProxyType.No) {
|
||||
proxy = proxy.copy(
|
||||
type = proxyOption.proxyTypeComboBox.selectedItem as ProxyType,
|
||||
host = proxyOption.proxyHostTextField.text,
|
||||
username = proxyOption.proxyUsernameTextField.text,
|
||||
password = String(proxyOption.proxyPasswordTextField.password),
|
||||
port = proxyOption.proxyPortTextField.value as Int,
|
||||
authenticationType = proxyOption.proxyAuthenticationTypeComboBox.selectedItem as AuthenticationType,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
val serialComm = SerialComm()
|
||||
|
||||
val options = Options.Companion.Default.copy(
|
||||
encoding = terminalOption.charsetComboBox.selectedItem as String,
|
||||
env = terminalOption.environmentTextArea.text,
|
||||
startupCommand = terminalOption.startupCommandTextField.text,
|
||||
serialComm = serialComm,
|
||||
)
|
||||
|
||||
return Host(
|
||||
name = name,
|
||||
protocol = protocol,
|
||||
host = host,
|
||||
port = port,
|
||||
username = generalOption.usernameTextField.text,
|
||||
authentication = authentication,
|
||||
proxy = proxy,
|
||||
sort = System.currentTimeMillis(),
|
||||
remark = generalOption.remarkTextArea.text,
|
||||
options = options,
|
||||
)
|
||||
}
|
||||
|
||||
fun setHost(host: Host) {
|
||||
generalOption.portTextField.value = host.port
|
||||
generalOption.nameTextField.text = host.name
|
||||
generalOption.usernameTextField.text = host.username
|
||||
generalOption.hostTextField.text = host.host
|
||||
generalOption.remarkTextArea.text = host.remark
|
||||
generalOption.authenticationTypeComboBox.selectedItem = host.authentication.type
|
||||
if (host.authentication.type == AuthenticationType.Password) {
|
||||
generalOption.passwordTextField.text = host.authentication.password
|
||||
}
|
||||
proxyOption.proxyTypeComboBox.selectedItem = host.proxy.type
|
||||
proxyOption.proxyHostTextField.text = host.proxy.host
|
||||
proxyOption.proxyPasswordTextField.text = host.proxy.password
|
||||
proxyOption.proxyUsernameTextField.text = host.proxy.username
|
||||
proxyOption.proxyPortTextField.value = host.proxy.port
|
||||
proxyOption.proxyAuthenticationTypeComboBox.selectedItem = host.proxy.authenticationType
|
||||
|
||||
terminalOption.charsetComboBox.selectedItem = host.options.encoding
|
||||
terminalOption.environmentTextArea.text = host.options.env
|
||||
terminalOption.startupCommandTextField.text = host.options.startupCommand
|
||||
|
||||
}
|
||||
|
||||
fun validateFields(): Boolean {
|
||||
val host = getHost()
|
||||
|
||||
// general
|
||||
if (validateField(generalOption.nameTextField)
|
||||
|| validateField(generalOption.hostTextField)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (StringUtils.equalsIgnoreCase(host.protocol, TelnetProtocolProvider.PROTOCOL)) {
|
||||
if (validateField(generalOption.usernameTextField)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (host.authentication.type == AuthenticationType.Password) {
|
||||
if (validateField(generalOption.passwordTextField)) {
|
||||
return false
|
||||
}
|
||||
} else if (host.authentication.type == AuthenticationType.PublicKey) {
|
||||
if (validateField(generalOption.publicKeyComboBox)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// proxy
|
||||
if (host.proxy.type != ProxyType.No) {
|
||||
if (validateField(proxyOption.proxyHostTextField)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (host.proxy.authenticationType != AuthenticationType.No) {
|
||||
if (validateField(proxyOption.proxyUsernameTextField)
|
||||
|| validateField(proxyOption.proxyPasswordTextField)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 true 表示有错误
|
||||
*/
|
||||
private fun validateField(textField: JTextField): Boolean {
|
||||
if (textField.isEnabled && textField.text.isBlank()) {
|
||||
setOutlineError(textField)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setOutlineError(textField: JTextField) {
|
||||
selectOptionJComponent(textField)
|
||||
textField.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
||||
textField.requestFocusInWindow()
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 true 表示有错误
|
||||
*/
|
||||
private fun validateField(comboBox: JComboBox<*>): Boolean {
|
||||
val selectedItem = comboBox.selectedItem
|
||||
if (comboBox.isEnabled && (selectedItem == null || (selectedItem is String && selectedItem.isBlank()))) {
|
||||
selectOptionJComponent(comboBox)
|
||||
comboBox.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
||||
comboBox.requestFocusInWindow()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected inner class GeneralOption : JPanel(BorderLayout()), Option {
|
||||
val portTextField = PortSpinner()
|
||||
val nameTextField = OutlineTextField(128)
|
||||
val usernameTextField = OutlineTextField(128)
|
||||
val hostTextField = OutlineTextField(255)
|
||||
val passwordTextField = OutlinePasswordField(255)
|
||||
val publicKeyComboBox = OutlineComboBox<String>()
|
||||
val remarkTextArea = FixedLengthTextArea(512)
|
||||
val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
|
||||
publicKeyComboBox.isEditable = false
|
||||
|
||||
publicKeyComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<*>?,
|
||||
value: Any?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): Component {
|
||||
var text = StringUtils.EMPTY
|
||||
if (value is String) {
|
||||
text = KeyManager.getInstance().getOhKeyPair(value)?.name ?: text
|
||||
}
|
||||
return super.getListCellRendererComponent(
|
||||
list,
|
||||
text,
|
||||
index,
|
||||
isSelected,
|
||||
cellHasFocus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
authenticationTypeComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<*>?,
|
||||
value: Any?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): Component {
|
||||
var text = value?.toString() ?: ""
|
||||
when (value) {
|
||||
AuthenticationType.Password -> {
|
||||
text = "Password"
|
||||
}
|
||||
|
||||
AuthenticationType.PublicKey -> {
|
||||
text = "Public Key"
|
||||
}
|
||||
|
||||
AuthenticationType.KeyboardInteractive -> {
|
||||
text = "Keyboard Interactive"
|
||||
}
|
||||
}
|
||||
return super.getListCellRendererComponent(
|
||||
list,
|
||||
text,
|
||||
index,
|
||||
isSelected,
|
||||
cellHasFocus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
authenticationTypeComboBox.addItem(AuthenticationType.No)
|
||||
authenticationTypeComboBox.addItem(AuthenticationType.Password)
|
||||
|
||||
authenticationTypeComboBox.selectedItem = AuthenticationType.Password
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
addComponentListener(object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent) {
|
||||
SwingUtilities.invokeLater { nameTextField.requestFocusInWindow() }
|
||||
removeComponentListener(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.settings
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.general")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, pref, $FORM_MARGIN, default",
|
||||
"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"
|
||||
)
|
||||
remarkTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
remarkTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
remarkTextArea.rows = 8
|
||||
remarkTextArea.lineWrap = true
|
||||
remarkTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.general.name")}:").xy(1, rows)
|
||||
.add(nameTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.host")}:").xy(1, rows)
|
||||
.add(hostTextField).xy(3, rows)
|
||||
.add("${I18n.getString("termora.new-host.general.port")}:").xy(5, rows)
|
||||
.add(portTextField).xy(7, rows).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.username")}:").xy(1, rows)
|
||||
.add(usernameTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, rows)
|
||||
.add(authenticationTypeComboBox).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.password")}:").xy(1, rows)
|
||||
.add(passwordTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.remark")}:").xy(1, rows)
|
||||
.add(JScrollPane(remarkTextArea).apply { border = FlatTextBorder() })
|
||||
.xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.InternalPlugin
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProviderExtension
|
||||
|
||||
internal class TelnetInternalPlugin : InternalPlugin() {
|
||||
init {
|
||||
support.addExtension(ProtocolProviderExtension::class.java) { TelnetProtocolProviderExtension.instance }
|
||||
support.addExtension(ProtocolHostPanelExtension::class.java) { TelnetProtocolHostPanelExtension.instance }
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "Telnet Protocol"
|
||||
}
|
||||
|
||||
|
||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||
return support.getExtensions(clazz)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.Disposer
|
||||
import app.termora.Host
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import java.awt.BorderLayout
|
||||
|
||||
class TelnetProtocolHostPanel(accountOwner: AccountOwner) : ProtocolHostPanel() {
|
||||
|
||||
private val pane = TelnetHostOptionsPane(accountOwner)
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
|
||||
private fun initView() {
|
||||
add(pane, BorderLayout.CENTER)
|
||||
Disposer.register(this, pane)
|
||||
}
|
||||
|
||||
private fun initEvents() {}
|
||||
|
||||
|
||||
override fun getHost(): Host {
|
||||
return pane.getHost()
|
||||
}
|
||||
|
||||
override fun setHost(host: Host) {
|
||||
pane.setHost(host)
|
||||
}
|
||||
|
||||
override fun validateFields(): Boolean {
|
||||
return pane.validateFields()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
|
||||
internal class TelnetProtocolHostPanelExtension private constructor() : ProtocolHostPanelExtension {
|
||||
companion object {
|
||||
val instance = TelnetProtocolHostPanelExtension()
|
||||
|
||||
}
|
||||
|
||||
override fun getProtocolProvider(): ProtocolProvider {
|
||||
return TelnetProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return TelnetProtocolHostPanel(accountOwner)
|
||||
}
|
||||
|
||||
override fun ordered(): Long {
|
||||
return 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.actions.DataProvider
|
||||
import app.termora.protocol.GenericProtocolProvider
|
||||
import app.termora.protocol.ProtocolTester
|
||||
|
||||
internal class TelnetProtocolProvider private constructor() : GenericProtocolProvider, ProtocolTester {
|
||||
companion object {
|
||||
val instance by lazy { TelnetProtocolProvider() }
|
||||
const val PROTOCOL = "Telnet"
|
||||
}
|
||||
|
||||
override fun getProtocol(): String {
|
||||
return PROTOCOL
|
||||
}
|
||||
|
||||
override fun createTerminalTab(dataProvider: DataProvider, windowScope: WindowScope, host: Host): TerminalTab {
|
||||
return TelnetTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
override fun getIcon(width: Int, height: Int): DynamicIcon {
|
||||
return Icons.telnet
|
||||
}
|
||||
|
||||
override fun ordered() = Int.MIN_VALUE
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
import app.termora.protocol.ProtocolProviderExtension
|
||||
|
||||
internal class TelnetProtocolProviderExtension private constructor() : ProtocolProviderExtension {
|
||||
companion object {
|
||||
val instance = TelnetProtocolProviderExtension()
|
||||
}
|
||||
|
||||
override fun getProtocolProvider(): ProtocolProvider {
|
||||
return TelnetProtocolProvider.instance
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.terminal.StreamPtyConnector
|
||||
import org.apache.commons.net.telnet.TelnetClient
|
||||
import org.apache.commons.net.telnet.TelnetOption
|
||||
import org.apache.commons.net.telnet.WindowSizeOptionHandler
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class TelnetStreamPtyConnector(
|
||||
private val telnet: TelnetClient,
|
||||
private val charset: Charset
|
||||
) :
|
||||
StreamPtyConnector(telnet.inputStream, telnet.outputStream) {
|
||||
private val reader = InputStreamReader(telnet.inputStream, getCharset())
|
||||
|
||||
override fun read(buffer: CharArray): Int {
|
||||
return reader.read(buffer)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteArray, offset: Int, len: Int) {
|
||||
output.write(buffer, offset, len)
|
||||
output.flush()
|
||||
}
|
||||
|
||||
override fun resize(rows: Int, cols: Int) {
|
||||
telnet.deleteOptionHandler(TelnetOption.WINDOW_SIZE)
|
||||
telnet.addOptionHandler(WindowSizeOptionHandler(cols, rows, true, false, true, false))
|
||||
}
|
||||
|
||||
override fun waitFor(): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
telnet.disconnect()
|
||||
}
|
||||
|
||||
override fun getCharset(): Charset {
|
||||
return charset
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.terminal.PtyConnector
|
||||
import org.apache.commons.net.telnet.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class TelnetTerminalTab(
|
||||
windowScope: WindowScope, host: Host,
|
||||
) : PtyHostTerminalTab(windowScope, host) {
|
||||
override suspend fun openPtyConnector(): PtyConnector {
|
||||
val winSize = terminalPanel.winSize()
|
||||
val telnet = TelnetClient()
|
||||
telnet.charset = Charset.forName(host.options.encoding)
|
||||
telnet.connectTimeout = 60 * 1000
|
||||
|
||||
if (host.proxy.type == ProxyType.HTTP) {
|
||||
telnet.proxy = Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress(host.proxy.host, host.proxy.port)
|
||||
)
|
||||
} else if (host.proxy.type == ProxyType.SOCKS5) {
|
||||
telnet.proxy = Proxy(
|
||||
Proxy.Type.SOCKS,
|
||||
InetSocketAddress(host.proxy.host, host.proxy.port)
|
||||
)
|
||||
}
|
||||
|
||||
val termtype = host.options.envs()["TERM"] ?: "xterm-256color"
|
||||
val ttopt = TerminalTypeOptionHandler(termtype, false, false, true, false)
|
||||
val echoopt = EchoOptionHandler(false, true, false, true)
|
||||
val gaopt = SuppressGAOptionHandler(true, true, true, true)
|
||||
val wsopt = WindowSizeOptionHandler(winSize.cols, winSize.rows, true, false, true, false)
|
||||
|
||||
telnet.addOptionHandler(ttopt)
|
||||
telnet.addOptionHandler(echoopt)
|
||||
telnet.addOptionHandler(gaopt)
|
||||
telnet.addOptionHandler(wsopt)
|
||||
|
||||
telnet.connect(host.host, host.port)
|
||||
telnet.keepAlive = true
|
||||
|
||||
return ptyConnectorFactory.decorate(TelnetStreamPtyConnector(telnet, telnet.charset))
|
||||
}
|
||||
|
||||
|
||||
override fun loginScriptsPtyConnector(host: Host, ptyConnector: PtyConnector): PtyConnector {
|
||||
if (host.authentication.type != AuthenticationType.Password) {
|
||||
return ptyConnector
|
||||
}
|
||||
|
||||
val scripts = mutableListOf<LoginScript>()
|
||||
scripts.add(
|
||||
LoginScript(
|
||||
expect = "login:",
|
||||
send = host.username,
|
||||
regex = false,
|
||||
matchCase = false
|
||||
)
|
||||
)
|
||||
|
||||
scripts.add(
|
||||
LoginScript(
|
||||
expect = "password:",
|
||||
send = host.authentication.password,
|
||||
regex = false,
|
||||
matchCase = false
|
||||
)
|
||||
)
|
||||
|
||||
return super.loginScriptsPtyConnector(
|
||||
host.copy(options = host.options.copy(loginScripts = scripts)),
|
||||
ptyConnector
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,6 @@ interface ProtocolHostPanelExtension : Extension {
|
||||
val extensions
|
||||
get() = ExtensionManager.getInstance()
|
||||
.getExtensions(ProtocolHostPanelExtension::class.java)
|
||||
.sortedBy { it.getProtocolProvider().ordered() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
1
src/main/resources/icons/telnet.svg
Normal file
1
src/main/resources/icons/telnet.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1751694328543" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1612" width="16" height="16"><path d="M889.5623525 160.37019636v579.50117625H134.4376475V160.31372511h755.124705z m-755.124705-56.47058813a56.47058813 56.47058813 0 0 0-56.47058906 56.47058813v579.50117625a56.47058813 56.47058813 0 0 0 56.47058906 56.47058812h755.124705a56.47058813 56.47058813 0 0 0 56.47058906-56.47058812V160.31372511a56.47058813 56.47058813 0 0 0-56.47058906-56.47058813H134.4376475zM733.53411781 915.26901917H290.46588219v-56.47058813h443.06823562v56.47058813z" p-id="1613" fill="#6C707E"></path><path d="M672.65882375 589.60313698v-278.96470593h43.87764656v241.46823562h118.08v37.49647031h-161.95764656zM445.70352969 589.60313698v-278.96470593h164.66823469v37.10117718H489.6376475v77.59058813h102.21176438V462.43137261h-102.21176438v89.67529406h124.85647031v37.49647031H445.70352969zM270.7576475 589.60313698V347.73960823H189.38352969v-37.10117719h207.81176437v37.10117719H315.36941187v241.86352875h-44.66823562z" p-id="1614" fill="#6C707E"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/main/resources/icons/telnet_dark.svg
Normal file
1
src/main/resources/icons/telnet_dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1751694328543" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1612" width="16" height="16"><path d="M889.5623525 160.37019636v579.50117625H134.4376475V160.31372511h755.124705z m-755.124705-56.47058813a56.47058813 56.47058813 0 0 0-56.47058906 56.47058813v579.50117625a56.47058813 56.47058813 0 0 0 56.47058906 56.47058812h755.124705a56.47058813 56.47058813 0 0 0 56.47058906-56.47058812V160.31372511a56.47058813 56.47058813 0 0 0-56.47058906-56.47058813H134.4376475zM733.53411781 915.26901917H290.46588219v-56.47058813h443.06823562v56.47058813z" p-id="1613" fill="#CED0D6"></path><path d="M672.65882375 589.60313698v-278.96470593h43.87764656v241.46823562h118.08v37.49647031h-161.95764656zM445.70352969 589.60313698v-278.96470593h164.66823469v37.10117718H489.6376475v77.59058813h102.21176438V462.43137261h-102.21176438v89.67529406h124.85647031v37.49647031H445.70352969zM270.7576475 589.60313698V347.73960823H189.38352969v-37.10117719h207.81176437v37.10117719H315.36941187v241.86352875h-44.66823562z" p-id="1614" fill="#CED0D6"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user