Commit fee9f926 authored by Alicja Reniewicz's avatar Alicja Reniewicz
Browse files

Merge branch 'rc3.0' into 'master'

Rc3.0

See merge request !2
parents a4911537 0f28a767
Pipeline #8035 passed with stages
in 9 minutes and 40 seconds
......@@ -6681,6 +6681,14 @@
"integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
"dev": true
},
"ng-block-ui": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/ng-block-ui/-/ng-block-ui-2.1.8.tgz",
"integrity": "sha512-BBcjUn9b/m3+wPlXkYExuy6ko+5oK7pte79gGUVo6a3HqpLnvPQXFgKV1kUpIM97NYfKKtR/+dPj7Xhh/GSV4w==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......
......@@ -3,4 +3,5 @@ export interface AppConfig {
camundaUrl: string;
grafanaUrl: string;
webSshUrl: string;
kibanaUrl: string;
}
......@@ -36,6 +36,7 @@ export class AppConfigService {
camundaUrl = environment.camundaUrl;
grafanaUrl = environment.grafanaUrl;
webSshUrl = environment.webSshUrl;
kibanaUrl = environment.kibanaUrl;
}();
resolve();
}
......
......@@ -25,6 +25,16 @@ const routes: Routes = [
canActivate: [CommonUserAdminRoleGuard]
},
{
path: 'byon', loadChildren: './byon/byon.module#ByonModule',
canActivate: [CommonUserAdminRoleGuard]
},
{
path: 'simulation', loadChildren: './simulation/simulation.module#SimulationModule',
canActivate: [CommonUserAdminRoleGuard]
},
{path: '**', loadChildren: './user/user.module#UserModule'},
];
......
......@@ -7,6 +7,7 @@ export enum IpVersion {
}
export class IpAddress {
id: number;
ipAddressType: IpAddressType;
ipVersion: string;
value: string;
......
export class LoginCredential {
id: number;
username: string;
password: string;
privateKey: string;
......
import {OperatingSystem} from '../../process/process-details/offer/model/operating-system';
import {GeoLocation} from '../../process/process-details/offer/model/geo-location';
export class NodeProperties {
id: number;
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() {
......@@ -107,39 +108,21 @@ export class VmListComponent implements OnInit {
}
getCity(vm: NodeCloudiator): string {
const locationId = vm.originId.split('/')[0];
const location = this.locationList
.find(value => value.id === locationId);
return (location && location.geoLocation) ? location.geoLocation.city : 'UNKNOWN';
if (vm.nodeProperties.geoLocation != null && vm.nodeProperties.geoLocation.city != null) {
return vm.nodeProperties.geoLocation.city;
} else { // find location by locationId
const locationId = vm.originId.split('/')[0];
const location = this.locationList
.find(value => value.id === locationId);
return (location && location.geoLocation) ? location.geoLocation.city : 'UNKNOWN';
}
}
userHasPermissionToSshConnection(vm: NodeCloudiator): boolean {
return (UserRole.ADMIN === UserRole[this.userService.currentUser.userRole]) && vm.loginCredential.privateKey !== null;
return UserRole.ADMIN === UserRole[this.userService.currentUser.userRole];
}
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}`);
// todo add window.open(webSshUrl) after webssh fixed
}
}
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({
id: this.byonData ? this.byonData.loginCredential.id : null,
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})
: ['']
},
{