diff --git a/webapp/src/client/app/app.module.ts b/webapp/src/client/app/app.module.ts index ea96a76b0..33e8568a1 100644 --- a/webapp/src/client/app/app.module.ts +++ b/webapp/src/client/app/app.module.ts @@ -32,6 +32,7 @@ import { SidebarModule } from "primeng/sidebar"; import { ToastModule } from "primeng/toast"; import { TabView, TabViewModule } from "primeng/tabview"; import { AutoCompleteModule } from "primeng/autocomplete"; +import { SshKeyService } from './shared/services/ssh-key.service'; @NgModule({ @@ -67,6 +68,7 @@ import { AutoCompleteModule } from "primeng/autocomplete"; LoadingService, WebSessionService, WebClientService, + SshKeyService, TabView ], exports: [ diff --git a/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.html b/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.html index 5e2ca5b42..3e780da77 100644 --- a/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.html +++ b/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.html @@ -1,11 +1,23 @@
-
- +
+ +
+ +
+
-
- -
-
+
+ +
+ +
+ +
+ +
+ +
+ +
\ No newline at end of file diff --git a/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.ts b/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.ts index 8a484524c..20823049f 100644 --- a/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.ts +++ b/webapp/src/client/app/modules/web-client/form/form-components/ssh/ssh-form.component.ts @@ -1,23 +1,126 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {FormGroup} from "@angular/forms"; +import { + AfterViewInit, + Component, + Injectable, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; -import {BaseComponent} from "@shared/bases/base.component"; +import { BaseComponent } from '@shared/bases/base.component'; +import { SelectItem } from 'primeng/api'; +import { WebFormService } from '@gateway/shared/services/web-form.service'; +import { map, startWith, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { SshAuthMode } from '@gateway/shared/enums/web-client-auth-mode.enum'; +import { Observable, of } from 'rxjs'; +import { SshKeyService } from '@gateway/shared/services/ssh-key.service'; +interface FormInputVisibility { + showUsernameInput?: boolean; + showPasswordInput?: boolean; + showPrivateKeyInput?: boolean; +} + +@Injectable({ providedIn: 'root' }) @Component({ selector: 'ssh-form', templateUrl: 'ssh-form.component.html', - styleUrls: ['ssh-form.component.scss'] + styleUrls: ['ssh-form.component.scss'], }) -export class SshFormComponent extends BaseComponent implements OnInit { - +export class SshFormComponent + extends BaseComponent + implements OnInit, OnDestroy, AfterViewInit +{ @Input() form: FormGroup; @Input() inputFormData: any; - constructor() { + authModeOptions: SelectItem[]; + + formInputVisibility: FormInputVisibility = { + showUsernameInput: true, + showPasswordInput: true, + showPrivateKeyInput: false, + }; + + constructor( + private formService: WebFormService, + private sshKeyService: SshKeyService + ) { super(); } + ngAfterViewInit(): void { + this.formService.canConnectIfAlsoTrue(() => { + if (!this.formInputVisibility.showPrivateKeyInput) { + return true; + } + + return this.sshKeyService.hasValidPrivateKey(); + }); + } + ngOnInit(): void { + this.initializeFormOptions(); + this.addControlsToParentForm(this.inputFormData); + } + + ngOnDestroy(): void { + this.formService.resetCanConnectCallback(); + } + + private addControlsToParentForm(inputFormData?: any): void { + if (this.form) { + this.form.addControl( + 'authMode', + new FormControl( + inputFormData?.authMode || SshAuthMode.Username_and_Password + ) + ); + this.subscribeToAuthModeChanges(); + } + } + + private initializeFormOptions(): void { + this.formService + .getAuthModeOptions('ssh') + .pipe(takeUntil(this.destroyed$)) + .subscribe({ + next: (authModeOptions) => { + this.authModeOptions = authModeOptions; + }, + error: (error) => + console.error('Error fetching dropdown options', error), + }); + } + + private subscribeToAuthModeChanges(): void { + this.form + .get('authMode') + .valueChanges.pipe( + takeUntil(this.destroyed$), + startWith(this.form.get('authMode').value as SshAuthMode), + switchMap((authMode) => this.getFormInputVisibility(authMode)) + ) + .subscribe(() => {}); + } + + private getFormInputVisibility( + authMode: SshAuthMode + ): Observable { + return of(this.formInputVisibility).pipe( + tap((visibility) => { + visibility.showUsernameInput = + authMode === SshAuthMode.Username_and_Password || + authMode === SshAuthMode.Private_Key; + visibility.showPasswordInput = + authMode === SshAuthMode.Username_and_Password; + visibility.showPrivateKeyInput = authMode === SshAuthMode.Private_Key; + }), + map(() => { + return authMode; + }) + ); } } diff --git a/webapp/src/client/app/modules/web-client/form/form-components/vnc/vnc-form.component.ts b/webapp/src/client/app/modules/web-client/form/form-components/vnc/vnc-form.component.ts index 1a469e302..acbae629a 100644 --- a/webapp/src/client/app/modules/web-client/form/form-components/vnc/vnc-form.component.ts +++ b/webapp/src/client/app/modules/web-client/form/form-components/vnc/vnc-form.component.ts @@ -5,8 +5,8 @@ import {BaseComponent} from "@shared/bases/base.component"; import {SelectItem} from "primeng/api"; import {map, startWith, switchMap, takeUntil, tap} from "rxjs/operators"; import {WebFormService} from "@shared/services/web-form.service"; -import {AuthMode} from "@shared/enums/web-client-auth-mode.enum"; import {Observable, of} from "rxjs"; +import { VncAuthMode as AuthMode } from '@shared/enums/web-client-auth-mode.enum'; interface FormInputVisibility { showUsernameInput?: boolean; @@ -56,7 +56,7 @@ export class VncFormComponent extends BaseComponent implements OnInit { } private initializeFormOptions(): void { - this.formService.getAuthModeOptions().pipe( + this.formService.getAuthModeOptions('vnc').pipe( takeUntil(this.destroyed$) ).subscribe({ next: (authModeOptions) => { diff --git a/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.html b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.html new file mode 100644 index 000000000..0e4251455 --- /dev/null +++ b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.html @@ -0,0 +1,47 @@ +
+
+ +
+ + + + + + + + +
+
+
+
+
+ {{ getErrorMessage() }} +
+
diff --git a/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.scss b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.scss new file mode 100644 index 000000000..3dd10dc15 --- /dev/null +++ b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.scss @@ -0,0 +1,91 @@ +@import '../../../../../../../assets/css/style/mixins'; +@import "../../../../../../../assets/css/style/variables"; +@import "../../../../../../../assets/css/theme/theme-mode-variables"; + +::ng-deep p-fileUpload .p-fileupload-content .p-progressbar { + display: none !important; +} + +.file-container { + display: flex; + align-items: center; + justify-content: space-between; + margin: 10px 5px 10px 5px; +} + +.form-helper-text { + line-height: 11px; + word-wrap: break-word; + color: var(--status-error-text-color); + font-size: 11px; + font-family: Open Sans; + font-weight: 400; + padding-left: 0px; + justify-content: flex-start; + align-items: flex-start; + gap: 10px; + display: inline-flex +} + +.highlight { + background-color: #f0f0f0; +} + +.gateway-form-group { + padding: 0px 0px 18px 0px; +} + +.flex-row{ + display: flex; + align-items: center; +} + +.justify-end { + justify-content: end; +} + +.private-key-textarea{ + margin-top: 5px; + margin-bottom: 5px; + resize: none; + font-family: monospace; + font-size: xx-small; + line-height: 15px; +} + +.card { + border-radius: 10px; + margin-bottom: 1rem; +} + +.card p-fileupload { + width: 100%; +} + +::ng-deep .card p-fileupload .p-fileupload-buttonbar { + display: flex; + justify-content: end; + flex-direction: row !important; +} + +::ng-deep .card p-fileupload div { + display: flex; + flex-direction: column-reverse; +} + +.file-row { + display: flex; + align-items: center; + justify-content: space-between; +} + +::ng-deep .card p-fileupload .p-button { + background-color: white; + border : 1px solid var(--default-btn-bg-color); + color: var(--default-btn-bg-color); +} + +::ng-deep .card p-fileupload .p-button:hover { + background-color: var(--default-btn-bg-color); + color: white; +} \ No newline at end of file diff --git a/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.ts b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.ts new file mode 100644 index 000000000..674817a7c --- /dev/null +++ b/webapp/src/client/app/modules/web-client/form/form-controls/file-control/file-control.component.ts @@ -0,0 +1,102 @@ +import { + Component, + ElementRef, + Input, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { SshKeyService } from '@gateway/shared/services/ssh-key.service'; +import { ValidateFileResult } from '../../../../../shared/services/ssh-key.service'; +import { WebFormService } from '@gateway/shared/services/web-form.service'; + +@Component({ + selector: 'app-file-control', + templateUrl: './file-control.component.html', + styleUrls: ['./file-control.component.scss'], +}) +export class FileControlComponent implements OnInit, OnDestroy { + @ViewChild('publicKeyFileControl') publicKeyFileControl: ElementRef; + + private uploadedFile: File = null; + private fileValidateResult: ValidateFileResult = null; + privateKeyContent: string = ''; + + constructor( + private sshKeyService: SshKeyService, + private formService: WebFormService + ) { + this.uploadedFile = sshKeyService.getKeyFile(); + this.privateKeyContent = sshKeyService.getKeyContent(); + } + + ngOnDestroy(): void { + this.formService.resetCanConnectCallback(); + } + + ngOnInit(): void { + this.formService.canConnectIfAlsoTrue(() => { + return this.sshKeyService.hasValidPrivateKey(); + }); + } + + clearPublicKeyData() { + this.privateKeyContent = ''; + this.sshKeyService.removeFile(); + this.uploadedFile = null; + } + onDragEnter(event: any) { + event.preventDefault(); + event.stopPropagation(); + } + + onSelect(event) { + this.handleFiles(event.files); + } + + handleFiles(fileList: FileList) { + if (fileList.length !== 1) { + return; + } + + this.uploadedFile = fileList[0]; + + this.sshKeyService.validateFile(this.uploadedFile).subscribe((res) => { + this.fileValidateResult = res; + this.privateKeyContent = this.fileValidateResult.content || ''; + if (this.fileValidateResult.valid) { + this.sshKeyService.saveFile( + this.uploadedFile, + this.fileValidateResult.content + ); + } + }); + } + + isValidFile(): boolean { + return this.fileValidateResult ? this.fileValidateResult.valid : false; + } + + removeFile() { + this.uploadedFile = null; + this.fileValidateResult = null; + this.sshKeyService.removeFile(); + } + + getErrorMessage(): String { + if (!this.fileValidateResult) { + return ''; + } + if (this.fileValidateResult.valid === false) { + return this.fileValidateResult.error; + } + return ''; + } + + getFileSize(): string { + if (!this.uploadedFile) { + return ''; + } + return `${this.uploadedFile.size} bytes`; + } +} diff --git a/webapp/src/client/app/modules/web-client/form/web-client-form.component.html b/webapp/src/client/app/modules/web-client/form/web-client-form.component.html index fd8e92f50..df7d6fe2e 100644 --- a/webapp/src/client/app/modules/web-client/form/web-client-form.component.html +++ b/webapp/src/client/app/modules/web-client/form/web-client-form.component.html @@ -74,7 +74,7 @@
- diff --git a/webapp/src/client/app/modules/web-client/form/web-client-form.component.ts b/webapp/src/client/app/modules/web-client/form/web-client-form.component.ts index 1b5f8c788..56863d8a1 100644 --- a/webapp/src/client/app/modules/web-client/form/web-client-form.component.ts +++ b/webapp/src/client/app/modules/web-client/form/web-client-form.component.ts @@ -69,7 +69,7 @@ export class WebClientFormComponent extends BaseComponent implements OnInit, } onConnectSession(): void { - this.webSessionService.createWebSession(this.connectSessionForm, this.getSelectedProtocol()).pipe( + this.webSessionService.createWebSession(this.connectSessionForm, this.getSelectedProtocol(), this.formService.getExtraSessionParameter()).pipe( takeUntil(this.destroyed$), switchMap((webSession) => this.manageScreenSize(webSession)), switchMap((webSession) => this.manageWebSessionSubject(webSession)), @@ -269,4 +269,8 @@ export class WebClientFormComponent extends BaseComponent implements OnInit, }]); }, 500); } + + canConnect(): boolean { + return this.formService.canConnect(this.connectSessionForm); + } } diff --git a/webapp/src/client/app/modules/web-client/ssh/web-client-ssh.component.ts b/webapp/src/client/app/modules/web-client/ssh/web-client-ssh.component.ts index c86bfaf26..a2d51c7a8 100644 --- a/webapp/src/client/app/modules/web-client/ssh/web-client-ssh.component.ts +++ b/webapp/src/client/app/modules/web-client/ssh/web-client-ssh.component.ts @@ -180,14 +180,15 @@ export class WebClientSshComponent extends WebClientBaseComponent implements OnI ).subscribe(); } - private callConnect(connectionParameters: any): Observable { + private callConnect(connectionParameters: SshConnectionParameters): Observable { return from( this.remoteTerminal.connect( connectionParameters.host, connectionParameters.port, connectionParameters.username, connectionParameters.gatewayAddress+`?token=${connectionParameters.token}`, - connectionParameters.password + connectionParameters.password, + connectionParameters.privateKey, ) ).pipe( catchError(error => throwError(error)) @@ -207,6 +208,7 @@ export class WebClientSshComponent extends WebClientBaseComponent implements OnI const extractedData: ExtractedHostnamePort = this.utils.string.extractHostnameAndPort(hostname, DefaultSshPort); const gatewayHttpAddress: URL = new URL(WebClientSshComponent.JET_SSH_URL+`/${sessionId}`, window.location.href); const gatewayAddress: string = gatewayHttpAddress.toString().replace("http", "ws"); + const privateKey: string | null = formData.extraData?.sshPrivateKey || null; const connectionParameters: SshConnectionParameters = { host: extractedData.hostname, @@ -214,7 +216,8 @@ export class WebClientSshComponent extends WebClientBaseComponent implements OnI password: password, port: extractedData.port, gatewayAddress: gatewayAddress, - sessionId: sessionId + sessionId: sessionId, + privateKey: privateKey } return of(connectionParameters); } diff --git a/webapp/src/client/app/modules/web-client/web-client.module.ts b/webapp/src/client/app/modules/web-client/web-client.module.ts index 59c7c3779..6ffebef41 100644 --- a/webapp/src/client/app/modules/web-client/web-client.module.ts +++ b/webapp/src/client/app/modules/web-client/web-client.module.ts @@ -32,6 +32,8 @@ import { import { PreConnectionBlobControlComponent } from "@gateway/modules/web-client/form/form-controls/preconnection-blob/pre-connection-blob-control.component"; +import { FileControlComponent } from './form/form-controls/file-control/file-control.component'; +import { FormsModule } from '@angular/forms'; const routes: Routes = [ @@ -45,7 +47,8 @@ const routes: Routes = [ imports: [ RouterModule.forChild(routes), SharedModule, - KeyFilterModule + KeyFilterModule, + FormsModule ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ], declarations: [ @@ -67,7 +70,8 @@ const routes: Routes = [ PreConnectionBlobControlComponent, TabViewComponent, DynamicTabComponent, - SessionToolbarComponent + SessionToolbarComponent, + FileControlComponent ], exports: [ DynamicTabComponent diff --git a/webapp/src/client/app/shared/enums/web-client-auth-mode.enum.ts b/webapp/src/client/app/shared/enums/web-client-auth-mode.enum.ts index 1a04b193f..f5de54d0e 100644 --- a/webapp/src/client/app/shared/enums/web-client-auth-mode.enum.ts +++ b/webapp/src/client/app/shared/enums/web-client-auth-mode.enum.ts @@ -1,27 +1,43 @@ import {SelectItem} from "primeng/api"; -export enum AuthMode { +export enum VncAuthMode { 'None' = 0, 'VNC_Password', //default 'Username_and_Password' } +export enum SshAuthMode { + 'Username_and_Password', //default + 'Private_Key' +} + namespace WebClientAuthMode { - export function getEnumKey(value: AuthMode): string { - return AuthMode[value]; + export function getEnumKey(value: VncAuthMode): string { + return VncAuthMode[value]; } - export function getSelectItems(): SelectItem[] { - return Object.keys(AuthMode) - .filter((key) => isNaN(Number(key)) && typeof AuthMode[key as any] === 'number') + export function getSelectVncItems(): SelectItem[] { + return Object.keys(VncAuthMode) + .filter((key) => isNaN(Number(key)) && typeof VncAuthMode[key as any] === 'number') .map((key) => { const label = key.replaceAll('_and_', '_&_').replaceAll('_', ' '); - const value: AuthMode = AuthMode[key as keyof typeof AuthMode]; + const value: VncAuthMode = VncAuthMode[key as keyof typeof VncAuthMode]; + + return { label, value }; + }); + } + + export function getSelectSshItems(): SelectItem[] { + return Object.keys(SshAuthMode) + .filter((key) => isNaN(Number(key)) && typeof SshAuthMode[key as any] === 'number') + .map((key) => { + const label = key.replaceAll('_', ' '); + const value: SshAuthMode = SshAuthMode[key as keyof typeof SshAuthMode]; return { label, value }; }); } } -export {WebClientAuthMode}; +export {WebClientAuthMode}; \ No newline at end of file diff --git a/webapp/src/client/app/shared/interfaces/connection-params.interfaces.ts b/webapp/src/client/app/shared/interfaces/connection-params.interfaces.ts index 29c3cd46f..45df2f978 100644 --- a/webapp/src/client/app/shared/interfaces/connection-params.interfaces.ts +++ b/webapp/src/client/app/shared/interfaces/connection-params.interfaces.ts @@ -73,5 +73,6 @@ export interface SshConnectionParameters { password?: string; gatewayAddress?: string; token?: string; - sessionId?: string + sessionId?: string, + privateKey?: string } diff --git a/webapp/src/client/app/shared/interfaces/forms.interfaces.ts b/webapp/src/client/app/shared/interfaces/forms.interfaces.ts index 81788503d..8026ba9e7 100644 --- a/webapp/src/client/app/shared/interfaces/forms.interfaces.ts +++ b/webapp/src/client/app/shared/interfaces/forms.interfaces.ts @@ -57,4 +57,7 @@ export interface SSHFormDataInput { hostname: string, username?: string, password?: string, + extraData?:{ + sshPrivateKey?: string, + } } diff --git a/webapp/src/client/app/shared/services/ssh-key.service.ts b/webapp/src/client/app/shared/services/ssh-key.service.ts new file mode 100644 index 000000000..6d04b6526 --- /dev/null +++ b/webapp/src/client/app/shared/services/ssh-key.service.ts @@ -0,0 +1,130 @@ +import { Injectable } from '@angular/core'; +import { Observable, BehaviorSubject, of } from 'rxjs'; +import { WebFormService } from './web-form.service'; + +@Injectable({ + providedIn: 'root', +}) +export class SshKeyService { + private reader = new FileReader(); + private fileReadSubject = new BehaviorSubject<{ + format: SshKeyFormat; + content: string; + }>(null); + private fileContent = null; + private file: File; + + constructor(private webFormService: WebFormService) { + this.reader.onload = () => { + this.fileContent = this.reader.result as string; + const keyFormat = recognizeKeyFormat(this.fileContent); + this.fileReadSubject.next(keyFormat); + }; + } + + saveFile(file: File, content: string) { + this.file = file; + this.fileContent = content; + this.webFormService.setExtraSessionParameter({ sshPrivateKey: this.fileContent }); + } + + getKeyContent(): string { + return this.fileContent; + } + + removeFile() { + this.file = null; + this.webFormService.setExtraSessionParameter({}); + } + + hasValidPrivateKey(): boolean { + let value = this.fileReadSubject.getValue(); + return value !== null && value.format !== SshKeyFormat.PKCS8_Encrypted; + } + + public validateFile(file: File | null): Observable { + if (file === null) { + return of({ valid: false, error: 'No file selected' }); + } + + if (file.size > 10000) { + return of({ valid: false, error: 'File size is too large, must be less than 10kb' }); + } + + this.reader.readAsText(file); + + return new Observable((observer) => { + this.fileReadSubject.subscribe((value) => { + if (value === null) { + return { + valid: false, + error: 'Invalid key format', + }; + } + if (value.format === SshKeyFormat.Unknown) { + observer.next({ + valid: false, + error: 'Invalid key format', + content: value.content, + }); + } else if (value.format == SshKeyFormat.PKCS8_Encrypted) { + observer.next({ + valid: false, + error: 'Encrypted key not supported', + content: value.content, + }); + } else { + observer.next({ valid: true, content: value.content }); + } + }); + }); + } + + getKeyFile(): File { + return this.file; + } +} + +function recognizeKeyFormat(keyString): RecognizeKeyFormatResult { + let format; + if ( + keyString.includes('-----BEGIN RSA PRIVATE KEY-----') || + keyString.includes('-----BEGIN EC PRIVATE KEY-----') + ) { + format = SshKeyFormat.PEM; + } else if (keyString.includes('-----BEGIN PRIVATE KEY-----')) { + format = SshKeyFormat.PKCS8_Unencrypted; + } else if (keyString.includes('-----BEGIN ENCRYPTED PRIVATE KEY-----')) { + format = SshKeyFormat.PKCS8_Encrypted; + } else if (keyString.includes('-----BEGIN OPENSSH PRIVATE KEY-----')) { + format = SshKeyFormat.OpenSSH; + } else { + format = SshKeyFormat.Unknown; + } + + return { format, content: keyString }; +} + +export enum SshKeyFormat { + PEM, + PKCS8_Unencrypted, + PKCS8_Encrypted, + OpenSSH, + Unknown, +} + +export type ValidateFileResult = + | { + valid: true; + content: string; + } + | { + valid: false; + error: string; + content?: string; + }; + +export type RecognizeKeyFormatResult = { + format: SshKeyFormat; + content: string; +}; diff --git a/webapp/src/client/app/shared/services/web-form.service.ts b/webapp/src/client/app/shared/services/web-form.service.ts index d5b3a1acc..baf2a78ef 100644 --- a/webapp/src/client/app/shared/services/web-form.service.ts +++ b/webapp/src/client/app/shared/services/web-form.service.ts @@ -7,16 +7,21 @@ import {SelectItem} from "primeng/api"; import {ScreenSize} from "@shared/enums/screen-size.enum"; import {WebClientProtocol} from "@shared/enums/web-client-protocol.enum"; import {FormControl, FormGroup, ValidatorFn, Validators} from "@angular/forms"; +import { ExtraSessionParameter } from './web-session.service'; @Injectable({ providedIn: 'root' }) export class WebFormService extends BaseComponent { + private canConnectExtraCallback: () => boolean = () => true; + + private extraSessionParameter: ExtraSessionParameter = {}; + constructor() { super(); } - getAuthModeOptions(): Observable { - return of(WebClientAuthMode.getSelectItems()); + getAuthModeOptions(protocol:'ssh' | 'vnc'): Observable { + return protocol === 'vnc' ? of(WebClientAuthMode.getSelectVncItems()) : of(WebClientAuthMode.getSelectSshItems()); } getProtocolOptions(): Observable { @@ -27,6 +32,14 @@ export class WebFormService extends BaseComponent { return of(ScreenSize.getSelectItems()); } + setExtraSessionParameter(extraSessionParameter: ExtraSessionParameter): void { + this.extraSessionParameter = extraSessionParameter; + } + + getExtraSessionParameter(): ExtraSessionParameter { + return this.extraSessionParameter; + } + addControlToForm( formGroup: FormGroup, controlName: string, @@ -73,4 +86,16 @@ export class WebFormService extends BaseComponent { detectFormChanges(cdr: ChangeDetectorRef): void { cdr.detectChanges(); } + + public canConnect(form: FormGroup): boolean { + return form.valid && this.canConnectExtraCallback(); + } + + canConnectIfAlsoTrue(callback: () => boolean): void { + this.canConnectExtraCallback = () => callback(); + } + + resetCanConnectCallback() { + this.canConnectExtraCallback = () => true; + } } diff --git a/webapp/src/client/app/shared/services/web-session.service.ts b/webapp/src/client/app/shared/services/web-session.service.ts index ef18f6ec6..119ba3d05 100644 --- a/webapp/src/client/app/shared/services/web-session.service.ts +++ b/webapp/src/client/app/shared/services/web-session.service.ts @@ -18,6 +18,10 @@ import {WebClientArdComponent} from "@gateway/modules/web-client/ard/web-client- // KAH Jan 2024 export const SESSIONS_MENU_OFFSET: number = 1; +export interface ExtraSessionParameter { + sshPrivateKey?: string; +}; + @Injectable({ providedIn: 'root', }) @@ -61,7 +65,7 @@ export class WebSessionService { return this.webSessionDataSubject.getValue().length; } - createWebSession(form: FormGroup, protocol: Protocol): Observable> { + createWebSession(form: FormGroup, protocol: Protocol, extraData:ExtraSessionParameter): Observable> { const submittedData = form.value; submittedData.hostname = this.processHostname(submittedData.autoComplete); @@ -72,7 +76,7 @@ export class WebSessionService { console.error(`Creating session, unsupported protocol: ${protocol}`) return; } - + submittedData.extraData = extraData; const webSession = new WebSession( submittedData.hostname, sessionComponent,