1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-11-27 10:42:35 +03:00

permanent port forwards - fixes #3479, fixes #2395

This commit is contained in:
Eugene Pankov 2021-05-02 15:08:22 +02:00
parent f87efcf5bd
commit 44040ba54b
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
9 changed files with 154 additions and 93 deletions

View File

@ -44,6 +44,7 @@ export interface SSHConnection {
warnOnClose?: boolean
algorithms?: Record<string, string[]>
proxyCommand?: string
forwardedPorts?: ForwardedPortConfig[]
}
export enum PortForwardType {
@ -52,7 +53,15 @@ export enum PortForwardType {
Dynamic = 'Dynamic',
}
export class ForwardedPort {
export interface ForwardedPortConfig {
type: PortForwardType
host: string
port: number
targetAddress: string
targetPort: number
}
export class ForwardedPort implements ForwardedPortConfig {
type: PortForwardType
host = '127.0.0.1'
port: number

View File

@ -100,6 +100,15 @@
button.btn.btn-secondary((click)='selectPrivateKey()')
i.fas.fa-folder-open
li(ngbNavItem)
a(ngbNavLink) Ports
ng-template(ngbNavContent)
ssh-port-forwarding-config(
[model]='connection.forwardedPorts',
(forwardAdded)='onForwardAdded($event)',
(forwardRemoved)='onForwardRemoved($event)'
)
li(ngbNavItem)
a(ngbNavLink) Advanced
ng-template(ngbNavContent)

View File

@ -6,7 +6,7 @@ import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { ElectronService, HostAppService, ConfigService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, LoginScript, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
import { PromptModalComponent } from './promptModal.component'
import { ALGORITHMS } from 'ssh2-streams/lib/constants'
@ -173,4 +173,13 @@ export class EditConnectionModalComponent {
}
this.connection.scripts.push({ expect: '', send: '' })
}
onForwardAdded (fw: ForwardedPortConfig) {
this.connection.forwardedPorts = this.connection.forwardedPorts ?? []
this.connection.forwardedPorts.push(fw)
}
onForwardRemoved (fw: ForwardedPortConfig) {
this.connection.forwardedPorts = this.connection.forwardedPorts?.filter(x => x !== fw)
}
}

View File

@ -0,0 +1,61 @@
.list-group-light.mb-3
.list-group-item.d-flex.align-items-center(*ngFor='let fw of model')
strong(*ngIf='fw.type === PortForwardType.Local') Local
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
strong(*ngIf='fw.type === PortForwardType.Dynamic') Dynamic
.ml-3 {{fw.host}}:{{fw.port}}
.ml-2 &rarr;
.ml-2(*ngIf='fw.type !== PortForwardType.Dynamic') {{fw.targetAddress}}:{{fw.targetPort}}
.ml-2(*ngIf='fw.type === PortForwardType.Dynamic') SOCKS proxy
button.btn.btn-link.ml-auto((click)='remove(fw)')
i.fas.fa-trash-alt.mr-2
span Remove
.input-group.mb-2(*ngIf='newForward.type === PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.port')
.input-group.mb-2(*ngIf='newForward.type !== PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.port')
.input-group-append
.input-group-text &rarr;
input.form-control(type='text', [(ngModel)]='newForward.targetAddress')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.targetPort')
.d-flex
.btn-group.mr-auto(
[(ngModel)]='newForward.type',
ngbRadioGroup
)
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Local'
)
| Local
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Remote'
)
| Remote
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Dynamic'
)
| Dynamic
button.btn.btn-primary((click)='addForward()')
i.fas.fa-check.mr-2
span Forward port

View File

@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { ForwardedPortConfig, PortForwardType } from '../api'
/** @hidden */
@Component({
selector: 'ssh-port-forwarding-config',
template: require('./sshPortForwardingConfig.component.pug'),
})
export class SSHPortForwardingConfigComponent {
@Input() model: ForwardedPortConfig[]
@Output() forwardAdded = new EventEmitter<ForwardedPortConfig>()
@Output() forwardRemoved = new EventEmitter<ForwardedPortConfig>()
newForward: ForwardedPortConfig
PortForwardType = PortForwardType
constructor (
) {
this.reset()
}
reset () {
this.newForward = {
type: PortForwardType.Local,
host: '127.0.0.1',
port: 8000,
targetAddress: '127.0.0.1',
targetPort: 80,
}
}
async addForward () {
try {
this.forwardAdded.emit(this.newForward)
this.reset()
} catch (e) {
console.error(e)
}
}
remove (fw: ForwardedPortConfig) {
this.forwardRemoved.emit(fw)
}
}

View File

@ -2,64 +2,8 @@
h5.m-0 Port forwarding
.modal-body.pt-0
.list-group-light.mb-3
.list-group-item.d-flex.align-items-center(*ngFor='let fw of session.forwardedPorts')
strong(*ngIf='fw.type === PortForwardType.Local') Local
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
strong(*ngIf='fw.type === PortForwardType.Dynamic') Dynamic
.ml-3 {{fw.host}}:{{fw.port}}
.ml-2 &rarr;
.ml-2(*ngIf='fw.type !== PortForwardType.Dynamic') {{fw.targetAddress}}:{{fw.targetPort}}
.ml-2(*ngIf='fw.type === PortForwardType.Dynamic') SOCKS proxy
button.btn.btn-link.ml-auto((click)='remove(fw)')
i.fas.fa-trash-alt.mr-2
span Remove
.input-group.mb-2(*ngIf='newForward.type === PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.port')
.input-group.mb-2(*ngIf='newForward.type !== PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.port')
.input-group-append
.input-group-text &rarr;
input.form-control(type='text', [(ngModel)]='newForward.targetAddress')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.targetPort')
.d-flex
.btn-group.mr-auto(
[(ngModel)]='newForward.type',
ngbRadioGroup
)
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Local'
)
| Local
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Remote'
)
| Remote
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Dynamic'
)
| Dynamic
button.btn.btn-primary((click)='addForward()')
i.fas.fa-check.mr-2
span Forward port
ssh-port-forwarding-config(
[model]='session.forwardedPorts',
(forwardAdded)='onForwardAdded($event)',
(forwardRemoved)='onForwardRemoved($event)'
)

View File

@ -1,43 +1,21 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, Input } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ForwardedPort, PortForwardType, SSHSession } from '../api'
import { ForwardedPort, ForwardedPortConfig, SSHSession } from '../api'
/** @hidden */
@Component({
template: require('./sshPortForwardingModal.component.pug'),
// styles: [require('./sshPortForwardingModal.component.scss')],
})
export class SSHPortForwardingModalComponent {
@Input() session: SSHSession
newForward = new ForwardedPort()
PortForwardType = PortForwardType
constructor (
public modalInstance: NgbActiveModal,
) {
this.reset()
onForwardAdded (fw: ForwardedPortConfig) {
const newForward = new ForwardedPort()
Object.assign(newForward, fw)
this.session.addPortForward(newForward)
}
reset () {
this.newForward = new ForwardedPort()
this.newForward.type = PortForwardType.Local
this.newForward.host = '127.0.0.1'
this.newForward.port = 8000
this.newForward.targetAddress = '127.0.0.1'
this.newForward.targetPort = 80
}
async addForward () {
try {
await this.session.addPortForward(this.newForward)
this.reset()
} catch (e) {
console.error(e)
}
}
remove (fw: ForwardedPort) {
this.session.removePortForward(fw)
onForwardRemoved (fw: ForwardedPortConfig) {
this.session.removePortForward(fw as ForwardedPort)
}
}

View File

@ -9,6 +9,7 @@ import TerminusTerminalModule from 'terminus-terminal'
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
import { SSHPortForwardingModalComponent } from './components/sshPortForwardingModal.component'
import { SSHPortForwardingConfigComponent } from './components/sshPortForwardingConfig.component'
import { PromptModalComponent } from './components/promptModal.component'
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
import { SSHTabComponent } from './components/sshTab.component'
@ -49,6 +50,7 @@ import { WinSCPContextMenu } from './winSCPIntegration'
EditConnectionModalComponent,
PromptModalComponent,
SSHPortForwardingModalComponent,
SSHPortForwardingConfigComponent,
SSHSettingsTabComponent,
SSHTabComponent,
],

View File

@ -14,7 +14,7 @@ import * as sshpk from 'sshpk'
import { Subject, Observable } from 'rxjs'
import { HostAppService, Platform, Logger, LogService, ElectronService, AppService, SelectorOption, ConfigService, NotificationsService } from 'terminus-core'
import { SettingsTabComponent } from 'terminus-settings'
import { ALGORITHM_BLACKLIST, SSHConnection, SSHSession } from '../api'
import { ALGORITHM_BLACKLIST, ForwardedPort, SSHConnection, SSHSession } from '../api'
import { PromptModalComponent } from '../components/promptModal.component'
import { PasswordStorageService } from './passwordStorage.service'
import { SSHTabComponent } from '../components/sshTab.component'
@ -164,6 +164,11 @@ export class SSHService {
if (savedPassword) {
this.passwordStorage.savePassword(session.connection, savedPassword)
}
for (const fw of session.connection.forwardedPorts ?? []) {
session.addPortForward(Object.assign(new ForwardedPort(), fw))
}
this.zone.run(resolve)
})
ssh.on('error', error => {