From 44040ba54b880d42c0e3d03e3ecb5f466cbf51af Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sun, 2 May 2021 15:08:22 +0200 Subject: [PATCH] permanent port forwards - fixes #3479, fixes #2395 --- terminus-ssh/src/api.ts | 11 +++- .../editConnectionModal.component.pug | 9 +++ .../editConnectionModal.component.ts | 11 +++- .../sshPortForwardingConfig.component.pug | 61 +++++++++++++++++ .../sshPortForwardingConfig.component.ts | 44 +++++++++++++ .../sshPortForwardingModal.component.pug | 66 ++----------------- .../sshPortForwardingModal.component.ts | 36 ++-------- terminus-ssh/src/index.ts | 2 + terminus-ssh/src/services/ssh.service.ts | 7 +- 9 files changed, 154 insertions(+), 93 deletions(-) create mode 100644 terminus-ssh/src/components/sshPortForwardingConfig.component.pug create mode 100644 terminus-ssh/src/components/sshPortForwardingConfig.component.ts diff --git a/terminus-ssh/src/api.ts b/terminus-ssh/src/api.ts index 85eb50f8..842c3184 100644 --- a/terminus-ssh/src/api.ts +++ b/terminus-ssh/src/api.ts @@ -44,6 +44,7 @@ export interface SSHConnection { warnOnClose?: boolean algorithms?: Record 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 diff --git a/terminus-ssh/src/components/editConnectionModal.component.pug b/terminus-ssh/src/components/editConnectionModal.component.pug index 57c732cd..05fa5fad 100644 --- a/terminus-ssh/src/components/editConnectionModal.component.pug +++ b/terminus-ssh/src/components/editConnectionModal.component.pug @@ -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) diff --git a/terminus-ssh/src/components/editConnectionModal.component.ts b/terminus-ssh/src/components/editConnectionModal.component.ts index 3f3f304b..601b0307 100644 --- a/terminus-ssh/src/components/editConnectionModal.component.ts +++ b/terminus-ssh/src/components/editConnectionModal.component.ts @@ -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) + } } diff --git a/terminus-ssh/src/components/sshPortForwardingConfig.component.pug b/terminus-ssh/src/components/sshPortForwardingConfig.component.pug new file mode 100644 index 00000000..d6758d5b --- /dev/null +++ b/terminus-ssh/src/components/sshPortForwardingConfig.component.pug @@ -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 → + .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 → + 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 diff --git a/terminus-ssh/src/components/sshPortForwardingConfig.component.ts b/terminus-ssh/src/components/sshPortForwardingConfig.component.ts new file mode 100644 index 00000000..0115c07b --- /dev/null +++ b/terminus-ssh/src/components/sshPortForwardingConfig.component.ts @@ -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() + @Output() forwardRemoved = new EventEmitter() + 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) + } +} diff --git a/terminus-ssh/src/components/sshPortForwardingModal.component.pug b/terminus-ssh/src/components/sshPortForwardingModal.component.pug index 270ece9d..c1f6b979 100644 --- a/terminus-ssh/src/components/sshPortForwardingModal.component.pug +++ b/terminus-ssh/src/components/sshPortForwardingModal.component.pug @@ -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 → - .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 → - 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)' + ) diff --git a/terminus-ssh/src/components/sshPortForwardingModal.component.ts b/terminus-ssh/src/components/sshPortForwardingModal.component.ts index e44fc853..5ed631f6 100644 --- a/terminus-ssh/src/components/sshPortForwardingModal.component.ts +++ b/terminus-ssh/src/components/sshPortForwardingModal.component.ts @@ -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) } } diff --git a/terminus-ssh/src/index.ts b/terminus-ssh/src/index.ts index f05b6dd4..3d30623f 100644 --- a/terminus-ssh/src/index.ts +++ b/terminus-ssh/src/index.ts @@ -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, ], diff --git a/terminus-ssh/src/services/ssh.service.ts b/terminus-ssh/src/services/ssh.service.ts index 5ddd3e5b..610dba1f 100644 --- a/terminus-ssh/src/services/ssh.service.ts +++ b/terminus-ssh/src/services/ssh.service.ts @@ -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 => {