Browse Source

permanent port forwards - fixes #3479, fixes #2395

Eugene Pankov 4 years ago
parent
commit
44040ba54b

+ 10 - 1
terminus-ssh/src/api.ts

@@ -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

+ 9 - 0
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)

+ 10 - 1
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)
+    }
 }

+ 61 - 0
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 &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

+ 44 - 0
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<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)
+    }
+}

+ 5 - 61
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 &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)'
+    )

+ 7 - 29
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)
     }
 }

+ 2 - 0
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,
     ],

+ 6 - 1
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 => {