mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-24 09:06:03 +00:00
added a winscp hotkey - fixes #2938
This commit is contained in:
@@ -62,6 +62,11 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
case 'restart-ssh-session':
|
case 'restart-ssh-session':
|
||||||
this.reconnect()
|
this.reconnect()
|
||||||
break
|
break
|
||||||
|
case 'launch-winscp':
|
||||||
|
if (this.session) {
|
||||||
|
this.ssh.launchWinSCP(this.session)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -13,6 +13,10 @@ export class SSHHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'restart-ssh-session',
|
id: 'restart-ssh-session',
|
||||||
name: 'Restart current SSH session',
|
name: 'Restart current SSH session',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'launch-winscp',
|
||||||
|
name: 'Launch WinSCP for current SSH session',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
async provide (): Promise<HotkeyDescription[]> {
|
async provide (): Promise<HotkeyDescription[]> {
|
||||||
|
@@ -19,7 +19,7 @@ import { SSHConfigProvider } from './config'
|
|||||||
import { SSHSettingsTabProvider } from './settings'
|
import { SSHSettingsTabProvider } from './settings'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { SSHHotkeyProvider } from './hotkeys'
|
import { SSHHotkeyProvider } from './hotkeys'
|
||||||
import { WinSCPContextMenu } from './winSCPIntegration'
|
import { WinSCPContextMenu } from './tabContextMenu'
|
||||||
import { SSHCLIHandler } from './cli'
|
import { SSHCLIHandler } from './cli'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
|
@@ -5,7 +5,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { Client } from 'ssh2'
|
import { Client } from 'ssh2'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { Logger, LogService, AppService, SelectorOption, ConfigService, NotificationsService } from 'terminus-core'
|
import { Logger, LogService, AppService, SelectorOption, ConfigService, NotificationsService, HostAppService, Platform, PlatformService } from 'terminus-core'
|
||||||
import { SettingsTabComponent } from 'terminus-settings'
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
import { ALGORITHM_BLACKLIST, ForwardedPort, SSHConnection, SSHSession } from '../api'
|
import { ALGORITHM_BLACKLIST, ForwardedPort, SSHConnection, SSHSession } from '../api'
|
||||||
import { PromptModalComponent } from '../components/promptModal.component'
|
import { PromptModalComponent } from '../components/promptModal.component'
|
||||||
@@ -16,6 +16,7 @@ import { ChildProcess } from 'node:child_process'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SSHService {
|
export class SSHService {
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
private detectedWinSCPPath: string | null
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
@@ -26,8 +27,13 @@ export class SSHService {
|
|||||||
private notifications: NotificationsService,
|
private notifications: NotificationsService,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
|
hostApp: HostAppService,
|
||||||
|
private platform: PlatformService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('ssh')
|
this.logger = log.create('ssh')
|
||||||
|
if (hostApp.platform === Platform.Windows) {
|
||||||
|
this.detectedWinSCPPath = platform.getWinSCPPath()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createSession (connection: SSHConnection): SSHSession {
|
createSession (connection: SSHConnection): SSHSession {
|
||||||
@@ -277,6 +283,33 @@ export class SSHService {
|
|||||||
this.config.save()
|
this.config.save()
|
||||||
return this.connect(connection)
|
return this.connect(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWinSCPPath (): string|undefined {
|
||||||
|
return this.detectedWinSCPPath ?? this.config.store.ssh.winSCPPath
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWinSCPURI (connection: SSHConnection): Promise<string> {
|
||||||
|
let uri = `scp://${connection.user}`
|
||||||
|
const password = await this.passwordStorage.loadPassword(connection)
|
||||||
|
if (password) {
|
||||||
|
uri += ':' + encodeURIComponent(password)
|
||||||
|
}
|
||||||
|
uri += `@${connection.host}:${connection.port}/`
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
async launchWinSCP (session: SSHSession): Promise<void> {
|
||||||
|
const path = this.getWinSCPPath()
|
||||||
|
if (!path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const args = [await this.getWinSCPURI(session.connection)]
|
||||||
|
if (session.activePrivateKey) {
|
||||||
|
args.push('/privatekey')
|
||||||
|
args.push(session.activePrivateKey)
|
||||||
|
}
|
||||||
|
this.platform.exec(path, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProxyCommandStream extends Duplex {
|
export class ProxyCommandStream extends Duplex {
|
||||||
|
35
terminus-ssh/src/tabContextMenu.ts
Normal file
35
terminus-ssh/src/tabContextMenu.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform, MenuItemOptions } from 'terminus-core'
|
||||||
|
import { SSHTabComponent } from './components/sshTab.component'
|
||||||
|
import { SSHService } from './services/ssh.service'
|
||||||
|
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class WinSCPContextMenu extends TabContextMenuItemProvider {
|
||||||
|
weight = 10
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
private ssh: SSHService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
||||||
|
if (!(tab instanceof SSHTabComponent) || !tab.connection) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (this.hostApp.platform !== Platform.Windows || tabHeader || !this.ssh.getWinSCPPath()) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Launch WinSCP',
|
||||||
|
click: (): void => {
|
||||||
|
this.ssh.launchWinSCP(tab.session!)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@@ -1,75 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core'
|
|
||||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform, PlatformService, MenuItemOptions } from 'terminus-core'
|
|
||||||
import { SSHTabComponent } from './components/sshTab.component'
|
|
||||||
import { PasswordStorageService } from './services/passwordStorage.service'
|
|
||||||
import { SSHConnection, SSHSession } from './api'
|
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
|
||||||
@Injectable()
|
|
||||||
export class WinSCPContextMenu extends TabContextMenuItemProvider {
|
|
||||||
weight = 10
|
|
||||||
private detectedPath: string | null
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private hostApp: HostAppService,
|
|
||||||
private config: ConfigService,
|
|
||||||
private platform: PlatformService,
|
|
||||||
private passwordStorage: PasswordStorageService,
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
if (hostApp.platform !== Platform.Windows) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.detectedPath = platform.getWinSCPPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
|
||||||
if (this.hostApp.platform !== Platform.Windows || tabHeader) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (!this.getPath()) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (!(tab instanceof SSHTabComponent) || !tab.connection) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: 'Launch WinSCP',
|
|
||||||
click: (): void => {
|
|
||||||
this.launchWinSCP(tab.session!)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
getPath (): string|undefined {
|
|
||||||
return this.detectedPath ?? this.config.store.ssh.winSCPPath
|
|
||||||
}
|
|
||||||
|
|
||||||
async getURI (connection: SSHConnection): Promise<string> {
|
|
||||||
let uri = `scp://${connection.user}`
|
|
||||||
const password = await this.passwordStorage.loadPassword(connection)
|
|
||||||
if (password) {
|
|
||||||
uri += ':' + encodeURIComponent(password)
|
|
||||||
}
|
|
||||||
uri += `@${connection.host}:${connection.port}/`
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchWinSCP (session: SSHSession): Promise<void> {
|
|
||||||
const path = this.getPath()
|
|
||||||
if (!path) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const args = [await this.getURI(session.connection)]
|
|
||||||
if (session.activePrivateKey) {
|
|
||||||
args.push('/privatekey')
|
|
||||||
args.push(session.activePrivateKey)
|
|
||||||
}
|
|
||||||
this.platform.exec(path, args)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user