Commit 9d77a1c2 authored by Alicja Reniewicz's avatar Alicja Reniewicz
Browse files

support for byon in ui

parent 376e8f5a
......@@ -25,6 +25,11 @@ const routes: Routes = [
canActivate: [CommonUserAdminRoleGuard]
},
{
path: 'byon', loadChildren: './byon/byon.module#ByonModule',
canActivate: [CommonUserAdminRoleGuard]
},
{path: '**', loadChildren: './user/user.module#UserModule'},
];
......
import {OperatingSystem} from '../../process/process-details/offer/model/operating-system';
import {GeoLocation} from '../../process/process-details/offer/model/geo-location';
export class NodeProperties {
providerId: string;
numberOfCores: number;
memory: number;
disk: number;
operatingSystem: OperatingSystem;
geoLocation: GeoLocation;
}
import {TestBed} from '@angular/core/testing';
import {WebSshService} from './web-ssh.service';
describe('WebSshService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: WebSshService = TestBed.get(WebSshService);
expect(service).toBeTruthy();
});
});
import {Injectable} from '@angular/core';
import {AppConfigService} from '../../app-config/service/app-config.service';
import {LoginCredential} from '../model/login-credential';
@Injectable({
providedIn: 'root'
})
export class WebSshService {
constructor() {
}
createSshConnection(loginCredential: LoginCredential, publicIp: string, vmName: string) {
console.log(`Create SSH connection for vm: ${vmName}`);
if (loginCredential.privateKey) {
this.connectBySshWithKey(loginCredential, publicIp);
} else {
this.connectBySshWithPassword(loginCredential, publicIp);
}
}
private connectBySshWithKey(loginCredential: LoginCredential, publicIp: string) {
const formattedPrivateKey = this.formatKey(loginCredential.privateKey);
const webSshUrl = `${AppConfigService.settings.webSshUrl}/?hostname=${publicIp}&username=${loginCredential.username}` +
`&privatekey=${formattedPrivateKey}`;
console.log(`Key-based SSH connection under url: ${webSshUrl}`);
window.open(webSshUrl);
}
private connectBySshWithPassword(loginCredential: LoginCredential, publicIp: string) {
const webSshUrl = `${AppConfigService.settings.webSshUrl}/?hostname=${publicIp}&username=${loginCredential.username}` +
`&password=${btoa(loginCredential.password)}`;
console.log(`Password-based SSH connection under address: ${webSshUrl}`);
window.open(webSshUrl);
}
private formatKey(privateKey: string): string {
const lines = privateKey.split('\n');
let result = '';
for (const keyLine of lines) {
result += keyLine + '\\n';
}
return result.substring(0, result.length - 2); // remove last '\n' sign
}
}
......@@ -7,8 +7,8 @@ import {Cloud} from '../../process/process-details/offer/model/cloud';
import {ProcessOfferService} from '../../process/process-details/offer/service/process-offer.service';
import {UserService} from '../../user/service/user.service';
import {UserRole} from '../../user/model/user-role.enum';
import {AppConfigService} from '../../app-config/service/app-config.service';
import {OfferLocation} from '../../process/process-details/offer/model/offer-location';
import {WebSshService} from '../service/web-ssh.service';
@Component({
selector: 'app-vm-list',
......@@ -31,7 +31,8 @@ export class VmListComponent implements OnInit {
constructor(private applicationService: ApplicationService,
private processOfferService: ProcessOfferService,
private snackBar: MatSnackBar,
private userService: UserService) {
private userService: UserService,
private webSshService: WebSshService) {
}
ngOnInit() {
......@@ -118,28 +119,6 @@ export class VmListComponent implements OnInit {
}
onSshConnectionClick(vm: NodeCloudiator) {
console.log(`SSH connection click for vm: ${vm.name}`);
let webSshUrl;
if (vm.loginCredential.privateKey) {
console.log(`Key-based SSH connection`);
const formattedPrivateKey = this.formatKey(vm.loginCredential.privateKey);
webSshUrl = `${AppConfigService.settings.webSshUrl}/?hostname=${vm.publicIp}&username=${vm.loginCredential.username}` +
`&privatekey=${formattedPrivateKey}`;
window.open(webSshUrl);
} else {
webSshUrl = `${AppConfigService.settings.webSshUrl}/?hostname=${vm.publicIp}&username=${vm.loginCredential.username}` +
`&password=${btoa(vm.loginCredential.password)}`;
console.log(`Password-based SSH connection under address: ${webSshUrl}`);
window.open(webSshUrl);
}
}
private formatKey(privateKey: string): string {
const lines = privateKey.split('\n');
let result = '';
for (const keyLine of lines) {
result += keyLine + '\\n';
}
return result.substring(0, result.length - 2); // remove last '\n' sign
this.webSshService.createSshConnection(vm.loginCredential, vm.publicIp, vm.name);
}
}
mat-card {
padding: var(--margin-global);
margin: var(--double-margin);
}
mat-dialog-actions {
margin-bottom: var(--margin-global);
}
mat-form-field {
margin-left: var(--double-margin);
width: 20%;
}
#private-key-field {
width: 55%;
}
.mat-card-with-bigger-bottom {
padding-bottom: var(--quadruple-margin);
}
#provider-id-field {
width: 40%;
}
.node-properties-common-field {
width: 18%;
}
<mat-card>
<form #byonDefinitionFormId="ngForm" [formGroup]="byonDefinitionForm">
<mat-card-title>Byon definition</mat-card-title>
<mat-dialog-content>
<mat-card>
<mat-form-field>
<input matInput formControlName="name" placeholder="name" required>
<mat-error
*ngIf="form.name.hasError('required')">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
</mat-card>
<mat-card>
<mat-card-subtitle><h3>IP addresses</h3></mat-card-subtitle>
<mat-spinner *ngIf="byonEnumsLoadingInProgress"></mat-spinner>
<app-ip-address-list
[ipAddresses]="ipAddresses"
[byonEnums]="byonEnums"
[isReadMode]="isReadMode">
</app-ip-address-list>
</mat-card>
<mat-card class="mat-card-with-bigger-bottom">
<mat-card-subtitle><h3>Login credentials</h3></mat-card-subtitle>
<form #loginCredentialFormId="ngForm" [formGroup]="loginCredentialForm">
<mat-form-field>
<input matInput formControlName="username" placeholder="username" required>
<mat-error
*ngIf="loginCredentialFormControl.username.hasError('required') && (loginCredentialFormControl.username.dirty || loginCredentialFormControl.username.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="password" placeholder="password"
[type]="passwordHide ? 'password' : 'text'">
<mat-icon matSuffix (click)="passwordHide = !passwordHide">{{passwordHide ? 'visibility_off' :
'visibility'}}
</mat-icon>
<mat-error
*ngIf="loginCredentialFormControl.password.hasError('oneOfTwoFieldsRequired') && (loginCredentialFormControl.password.dirty || loginCredentialFormControl.password.touched)">
{{getPasswordOrKeyRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field id="private-key-field">
<mat-label>Private key</mat-label>
<textarea matInput
formControlName="privateKey"
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="10"></textarea>
<mat-error
*ngIf="loginCredentialFormControl.privateKey.hasError('oneOfTwoFieldsRequired') && (loginCredentialFormControl.privateKey.dirty || loginCredentialFormControl.privateKey.touched)">
{{getPasswordOrKeyRequiredMsg()}}
</mat-error>
</mat-form-field>
</form>
</mat-card>
<mat-card>
<mat-card-subtitle><h3>Node properties</h3></mat-card-subtitle>
<form #nodePropertiesFormId="ngForm" [formGroup]="nodePropertiesForm">
<mat-form-field id="provider-id-field">
<input matInput formControlName="providerId" placeholder="provider id" required>
<mat-error
*ngIf="nodePropertiesFormControl.providerId.hasError('required') && (nodePropertiesFormControl.providerId.dirty || nodePropertiesFormControl.providerId.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field class="node-properties-common-field">
<input matInput formControlName="numberOfCores" placeholder="number of cores" required>
<mat-error
*ngIf="nodePropertiesFormControl.numberOfCores.hasError('required') && (nodePropertiesFormControl.numberOfCores.dirty || nodePropertiesFormControl.numberOfCores.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="nodePropertiesFormControl.numberOfCores.hasError('pattern') && (nodePropertiesFormControl.numberOfCores.dirty || nodePropertiesFormControl.numberOfCores.touched)">
{{getNumberOfCoresMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field class="node-properties-common-field">
<input matInput formControlName="memory" placeholder="memory" required>
<mat-error
*ngIf="nodePropertiesFormControl.memory.hasError('required') && (nodePropertiesFormControl.memory.dirty || nodePropertiesFormControl.memory.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="nodePropertiesFormControl.memory.hasError('pattern') && (nodePropertiesFormControl.memory.dirty || nodePropertiesFormControl.memory.touched)">
{{getMemoryMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field class="node-properties-common-field">
<input matInput formControlName="disk" placeholder="disk" required>
<mat-error
*ngIf="nodePropertiesFormControl.disk.hasError('required') && (nodePropertiesFormControl.disk.dirty || nodePropertiesFormControl.disk.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="nodePropertiesFormControl.disk.hasError('pattern') && (nodePropertiesFormControl.disk.dirty || nodePropertiesFormControl.disk.touched)">
{{getDiskMsg()}}
</mat-error>
</mat-form-field>
<mat-card class="mat-card-with-bigger-bottom">
<mat-card-subtitle><h4>Operating system</h4></mat-card-subtitle>
<mat-spinner *ngIf="byonEnumsLoadingInProgress"></mat-spinner>
<form #operatingSystemFormId="ngForm" [formGroup]="operatingSystemForm">
<mat-form-field>
<mat-select formControlName="operatingSystemFamily" placeholder="operating system family"
name="operatingSystemFamily" required>
<mat-option *ngFor="let osFamily of byonEnums.osFamilies" [value]="osFamily">
{{osFamily}}
</mat-option>
</mat-select>
<mat-error
*ngIf="operatingSystemFormControl.operatingSystemFamily.hasError('required') && (operatingSystemFormControl.operatingSystemFamily.dirty || operatingSystemFormControl.operatingSystemFamily.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="operatingSystemArchitecture" placeholder="operating system architecture"
name="operatingSystemArchitecture" required>
<mat-option *ngFor="let osArchitecture of byonEnums.osArchitectures" [value]="osArchitecture">
{{osArchitecture}}
</mat-option>
</mat-select>
<mat-error
*ngIf="operatingSystemFormControl.operatingSystemArchitecture.hasError('required') && (operatingSystemFormControl.operatingSystemArchitecture.dirty || operatingSystemFormControl.operatingSystemArchitecture.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="operatingSystemVersion" placeholder="operating system version"
required>
<mat-error
*ngIf="operatingSystemFormControl.operatingSystemVersion.hasError('required') && (operatingSystemFormControl.operatingSystemVersion.dirty || operatingSystemFormControl.operatingSystemVersion.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="operatingSystemFormControl.operatingSystemVersion.hasError('pattern') && (operatingSystemFormControl.operatingSystemVersion.dirty || operatingSystemFormControl.operatingSystemVersion.touched)">
{{getOsVersionMsg()}}
</mat-error>
</mat-form-field>
</form>
</mat-card>
<mat-card class="mat-card-with-bigger-bottom">
<mat-card-subtitle><h4>Geolocation</h4></mat-card-subtitle>
<form #geoLocationFormId="ngForm" [formGroup]="geoLocationForm">
<mat-form-field>
<input matInput formControlName="city" placeholder="city" required>
<mat-error
*ngIf="geoLocationFormControl.city.hasError('required') && (geoLocationFormControl.city.dirty || geoLocationFormControl.city.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="country" placeholder="country" required>
<mat-error
*ngIf="geoLocationFormControl.country.hasError('required') && (geoLocationFormControl.country.dirty || geoLocationFormControl.country.touched)">
{{getRequiredMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="latitude" placeholder="latitude" required>
<mat-error
*ngIf="geoLocationFormControl.latitude.hasError('required') && (geoLocationFormControl.latitude.dirty || geoLocationFormControl.latitude.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="(geoLocationFormControl.latitude.hasError('pattern') || geoLocationFormControl.latitude.hasError('min') || geoLocationFormControl.latitude.hasError('max')) && (geoLocationFormControl.latitude.dirty || geoLocationFormControl.latitude.touched)">
{{getLatitudeMsg()}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="longitude" placeholder="longitude" required>
<mat-error
*ngIf="geoLocationFormControl.longitude.hasError('required') && (geoLocationFormControl.longitude.dirty || geoLocationFormControl.longitude.touched)">
{{getRequiredMsg()}}
</mat-error>
<mat-error
*ngIf="(geoLocationFormControl.longitude.hasError('pattern') || geoLocationFormControl.longitude.hasError('min') || geoLocationFormControl.longitude.hasError('max')) && (geoLocationFormControl.longitude.dirty || geoLocationFormControl.longitude.touched)">
{{getLongitudeMsg()}}
</mat-error>
</mat-form-field>
</form>
</mat-card>
</form>
</mat-card>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button type="button" color="warn" mat-dialog-close>
<mat-icon>undo</mat-icon>
Cancel
</button>
<button mat-raised-button [disabled]="byonDefinitionFormId.invalid || isReadMode"
(click)="saveByonDefinition(byonDefinitionFormId)"
color="primary">
<mat-icon>save</mat-icon>
Save
</button>
</mat-dialog-actions>
</form>
</mat-card>
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ByonDefinitionFormComponent} from './byon-definition-form.component';
describe('ByonDefinitionFormComponent', () => {
let component: ByonDefinitionFormComponent;
let fixture: ComponentFixture<ByonDefinitionFormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ByonDefinitionFormComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ByonDefinitionFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Inject, NgZone, OnInit, Optional, ViewChild} from '@angular/core';
import {FormBuilder, FormControl, FormGroup, NgForm, Validators} from '@angular/forms';
import {MAT_DIALOG_DATA, MatDialogRef, MatSnackBar} from '@angular/material';
import {IpAddress} from '../../application/model/ip-address';
import {Byon} from '../model/byon';
import {CdkTextareaAutosize} from '@angular/cdk/text-field';
import {take} from 'rxjs/operators';
import {IpAddressListComponent} from '../ip-address-list/ip-address-list.component';
import {ByonEnums} from '../model/byon-enums';
import {ByonService} from '../service/byon.service';
import {oneOfTwoFieldsRequiredValidator} from '../validator/one-of-two-fields-required.validator';
@Component({
selector: 'app-byon-definition-form',
templateUrl: './byon-definition-form.component.html',
styleUrls: ['./byon-definition-form.component.css']
})
export class ByonDefinitionFormComponent implements OnInit {
@ViewChild(IpAddressListComponent) ipAddressesComponent: IpAddressListComponent;
@ViewChild('autosize', {read: false}) autosize: CdkTextareaAutosize;
byonData: Byon;
isReadMode = false;
passwordHide = true;
byonEnums = new ByonEnums();
byonEnumsLoadingInProgress = false;
ipAddresses = new Array<IpAddress>();
loginCredentialForm: FormGroup;
geoLocationForm: FormGroup;
operatingSystemForm: FormGroup;
byonDefinitionForm: FormGroup;
nodePropertiesForm: FormGroup;
constructor(private formBuilder: FormBuilder,
private dialogRef: MatDialogRef<ByonDefinitionFormComponent>,
private ngZone: NgZone,
private byonService: ByonService,
private snackBar: MatSnackBar,
@Optional() @Inject(MAT_DIALOG_DATA) public dialogData?: any) {
}
triggerResize() {
// Wait for changes to be applied, then trigger textarea resize.
this.ngZone.onStable.pipe(take(1))
.subscribe(() => this.autosize.resizeToFitContent(true));
}
ngOnInit() {
if (this.dialogData) {
this.byonData = this.dialogData ? this.dialogData.byonData : undefined;
this.isReadMode = this.dialogData ? this.dialogData.isReadMode : false;
this.ipAddresses = this.byonData ? this.byonData.ipAddresses : [];
}
this.byonEnumsLoadingInProgress = true;
this.byonService.getByonEnums().subscribe(byonEnumsResponse => {
this.byonEnums = byonEnumsResponse;
this.byonEnumsLoadingInProgress = false;
},
error1 => {
this.byonEnumsLoadingInProgress = false;
this.snackBar.open(`Error by getting available options for byons form: ${error1.error.message}`, 'Close');
}
);
this.loginCredentialForm = this.formBuilder.group({
username: this.byonData ? new FormControl({value: this.byonData.loginCredential.username, disabled: this.isReadMode})
: ['', Validators.required],
password: this.byonData ? new FormControl({value: this.byonData.loginCredential.password, disabled: this.isReadMode})
: [''],
privateKey: this.byonData ? new FormControl({value: this.byonData.loginCredential.privateKey, disabled: this.isReadMode})
: ['']
},
{
validators: oneOfTwoFieldsRequiredValidator('password', 'privateKey')
});
this.geoLocationForm = this.formBuilder.group({
city: this.byonData ? new FormControl({value: this.byonData.nodeProperties.geoLocation.city, disabled: this.isReadMode})
: ['', Validators.required],
country: this.byonData ? new FormControl({value: this.byonData.nodeProperties.geoLocation.country, disabled: this.isReadMode})
: ['', Validators.required],
latitude: this.byonData ? new FormControl({value: this.byonData.nodeProperties.geoLocation.latitude, disabled: this.isReadMode})
: ['', [Validators.required, Validators.pattern(/^-*[0-9]*\.*[0-9]+$/),
Validators.min(-90), Validators.max(90)]], // double
longitude: this.byonData ? new FormControl({value: this.byonData.nodeProperties.geoLocation.longitude, disabled: this.isReadMode})
: ['', [Validators.required, Validators.pattern(/^-*[0-9]*\.*[0-9]+$/),
Validators.min(-180), Validators.max(180)]] // double
});
this.operatingSystemForm = this.formBuilder.group({
operatingSystemFamily: this.byonData ? new FormControl({
value: this.byonData.nodeProperties.operatingSystem.operatingSystemFamily,
disabled: this.isReadMode
}) : ['', Validators.required],
operatingSystemArchitecture: this.byonData ? new FormControl({
value:
this.byonData.nodeProperties.operatingSystem.operatingSystemArchitecture, disabled: this.isReadMode
}) : ['', Validators.required],
operatingSystemVersion: this.byonData ? new FormControl({
value: this.byonData.nodeProperties.operatingSystem.operatingSystemVersion,
disabled: this.isReadMode
}) : ['', [Validators.required, Validators.pattern(/^[0-9]*\.*[0-9]+$/)]] // bigDecimal
});
this.nodePropertiesForm = this.formBuilder.group({
providerId: this.byonData ? new FormControl({value: this.byonData.nodeProperties.providerId, disabled: this.isReadMode})
: ['', Validators.required],
numberOfCores: this.byonData ? new FormControl({value: this.byonData.nodeProperties.numberOfCores, disabled: this.isReadMode})
: ['', [Validators.required, Validators.pattern(/^[0-9]+$/)]], // int
memory: this.byonData ? new FormControl({value: this.byonData.nodeProperties.memory, disabled: this.isReadMode})
: ['', [Validators.required, Validators.pattern(/^[0-9]+$/)]], // long
disk: this.byonData ? new FormControl({value: this.byonData.nodeProperties.disk, disabled: this.isReadMode})
: ['', [Validators.required, Validators.pattern(/^[0-9]*\.*[0-9]+$/)]], // float
operatingSystem: this.operatingSystemForm,
geoLocation: this.geoLocationForm
});
this.byonDefinitionForm = this.formBuilder.group({
id: this.byonData ? this.byonData.id : null,
name: this.byonData ? new FormControl({value: this.byonData.name, disabled: this.isReadMode}) : ['', Validators.required],
loginCredential: this.loginCredentialForm,
ipAddresses: this.byonData ? this.byonData.ipAddresses : [],
nodeProperties: this.nodePropertiesForm
});
}
get form() {
return this.byonDefinitionForm.controls;
}
get loginCredentialFormControl() {
return this.loginCredentialForm.controls;
}
get nodePropertiesFormControl() {
return this.nodePropertiesForm.controls;
}
get operatingSystemFormControl() {
return this.operatingSystemForm.controls;
}
get geoLocationFormControl() {
return this.geoLocationForm.controls;
}
saveByonDefinition(byonDefinitionFrom: NgForm) {
this.form.ipAddresses.setValue(this.ipAddressesComponent.getIpAddresses());
const byonDefinition = <Byon> byonDefinitionFrom.value;
this.dialogRef.close(byonDefinition);
console.log(`Saving byon definition with name: ${byonDefinition.name}`);
}