Support for vmware added

This commit is contained in:
Piotr Pekala 2019-02-11 05:46:37 -08:00
parent ba8bd4d4c6
commit e16eb69fee
30 changed files with 1126 additions and 6 deletions

View File

@ -36,6 +36,10 @@ import { IosTemplatesComponent } from './components/preferences/dynamips/ios-tem
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component';
import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component';
import { VmwarePreferencesComponent } from './components/preferences/vmware/vmware-preferences/vmware-preferences.component';
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component';
const routes: Routes = [
{
@ -79,10 +83,15 @@ const routes: Routes = [
{ path: 'server/:server_id/preferences/vpcs/templates/:template_id', component: VpcsTemplateDetailsComponent},
{ path: 'server/:server_id/preferences/vpcs/addtemplate', component: AddVpcsTemplateComponent },
// { path: 'server/:server_id/preferences/virtualbox', component: VirtualBoxPreferencesComponent }
// { path: 'server/:server_id/preferences/virtualbox', component: VirtualBoxPreferencesComponent },
{ path: 'server/:server_id/preferences/virtualbox/templates', component: VirtualBoxTemplatesComponent },
{ path: 'server/:server_id/preferences/virtualbox/templates/:template_id', component: VirtualBoxTemplateDetailsComponent },
{ path: 'server/:server_id/preferences/virtualbox/addtemplate', component: AddVirtualBoxTemplateComponent }
{ path: 'server/:server_id/preferences/virtualbox/addtemplate', component: AddVirtualBoxTemplateComponent },
// { path: 'server/:server_id/preferences/vmware', component: VmwarePreferencesComponent },
{ path: 'server/:server_id/preferences/vmware/templates', component: VmwareTemplatesComponent },
{ path: 'server/:server_id/preferences/vmware/templates/:template_id', component: VmwareTemplateDetailsComponent },
{ path: 'server/:server_id/preferences/vmware/addtemplate', component: AddVmwareTemplateComponent }
]
},
{ path: 'server/:server_id/project/:project_id', component: ProjectMapComponent }

View File

@ -137,6 +137,12 @@ import { QemuConfigurationService } from './services/qemu-configuration.service'
import { VirtualBoxConfigurationService } from './services/virtual-box-configuration.service';
import { VpcsConfigurationService } from './services/vpcs-configuration.service';
import { BuiltInTemplatesConfigurationService } from './services/built-in-templates-configuration.service';
import { VmwarePreferencesComponent } from './components/preferences/vmware/vmware-preferences/vmware-preferences.component';
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
import { VmwareService } from './services/vmware.service';
import { VmwareConfigurationService } from './services/vmware-configuration.service';
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -219,7 +225,11 @@ if (environment.production) {
IosTemplatesComponent,
IosTemplateDetailsComponent,
AddIosTemplateComponent,
SymbolsComponent
SymbolsComponent,
VmwarePreferencesComponent,
VmwareTemplatesComponent,
VmwareTemplateDetailsComponent,
AddVmwareTemplateComponent
],
imports: [
BrowserModule,
@ -280,7 +290,9 @@ if (environment.production) {
QemuConfigurationService,
VirtualBoxConfigurationService,
VpcsConfigurationService,
BuiltInTemplatesConfigurationService
BuiltInTemplatesConfigurationService,
VmwareService,
VmwareConfigurationService
],
entryComponents: [
AddServerDialogComponent,

View File

@ -32,6 +32,11 @@
VirtualBox
</button>
</mat-list-item>
<mat-list-item>
<button mat-button routerLink="/server/{{serverId}}/preferences/vmware/templates">
VMware
</button>
</mat-list-item>
</mat-nav-list>
</div>
</div>

View File

@ -0,0 +1,23 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">New VMware VM template</h1>
</div>
</div>
<div class="default-content" *ngIf="vmwareTemplate">
<mat-form-field class="form-field">
<mat-select
placeholder="VM list"
[(ngModel)]="selectedVM"
[ngModelOptions]="{standalone: true}" >
<mat-option *ngFor="let vm of virtualMachines" [value]="vm">
{{vm.vmname}}
</mat-option>
</mat-select>
</mat-form-field><br/>
<mat-checkbox [(ngModel)]="vmwareTemplate.linked_clone">
Use as a linked base VM (experimental)
</mat-checkbox>
<div class="buttons-bar"><button mat-raised-button color="primary" (click)="addTemplate()">Add template</button></div>
</div>
</div>

View File

@ -0,0 +1,3 @@
.form-field {
width: 100%;
}

View File

@ -0,0 +1,62 @@
import { Component, OnInit } from "@angular/core";
import { Server } from '../../../../models/server';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { switchMap } from 'rxjs/operators';
import { ToasterService } from '../../../../services/toaster.service';
import { TemplateMocksService } from '../../../../services/template-mocks.service';
import { v4 as uuid } from 'uuid';
import { VmwareVm } from '../../../../models/vmware/vmware-vm';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { VmwareService } from '../../../../services/vmware.service';
@Component({
selector: 'app-add-vmware-template',
templateUrl: './add-vmware-template.component.html',
styleUrls: ['./add-vmware-template.component.scss']
})
export class AddVmwareTemplateComponent implements OnInit {
server: Server;
virtualMachines: VmwareVm[];
selectedVM: VmwareVm;
vmwareTemplate: VmwareTemplate;
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private vmwareService: VmwareService,
private toasterService: ToasterService,
private templateMocksService: TemplateMocksService,
private router: Router
) {}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
this.vmwareService.getVirtualMachines(this.server).subscribe((virtualMachines: VmwareVm[]) => {
this.virtualMachines = virtualMachines;
this.templateMocksService.getVmwareTemplate().subscribe((template: VmwareTemplate) => {
this.vmwareTemplate = template;
});
})
});
}
addTemplate() {
if (this.selectedVM) {
this.vmwareTemplate.name = this.selectedVM.vmname;
this.vmwareTemplate.vmx_path = this.selectedVM.vmx_path;
this.vmwareTemplate.template_id = uuid();
this.vmwareService.addTemplate(this.server, this.vmwareTemplate).subscribe((template: VmwareTemplate) => {
this.router.navigate(['/server', this.server.id, 'preferences', 'vmware', 'templates']);
});
} else {
this.toasterService.error(`Fill all required fields`);
}
}
}

View File

@ -0,0 +1,93 @@
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedServerService } from '../../../../services/server.service.spec';
import { ServerService } from '../../../../services/server.service';
import { Server } from '../../../../models/server';
import { ToasterService } from '../../../../services/toaster.service';
import { TemplateMocksService } from '../../../../services/template-mocks.service';
import { MockedToasterService } from '../../../../services/toaster.service.spec';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { AddVmwareTemplateComponent } from './add-vmware-template.component';
import { VmwareService } from '../../../../services/vmware.service';
import { VmwareVm } from '../../../../models/vmware/vmware-vm';
export class MockedVmwareService {
public addTemplate(server: Server, vmwareTemplate: VmwareTemplate) {
return of(vmwareTemplate);
}
public getVirtualMachines(server: Server) {
return of([]);
}
}
describe('AddVmwareTemplateComponent', () => {
let component: AddVmwareTemplateComponent;
let fixture: ComponentFixture<AddVmwareTemplateComponent>;
let mockedServerService = new MockedServerService;
let mockedVmwareService = new MockedVmwareService;
let mockedToasterService = new MockedToasterService;
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ServerService, useValue: mockedServerService },
{ provide: VmwareService, useValue: mockedVmwareService },
{ provide: ToasterService, useValue: mockedToasterService},
{ provide: TemplateMocksService, useClass: TemplateMocksService }
],
declarations: [
AddVmwareTemplateComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddVmwareTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call save template', () => {
spyOn(mockedVmwareService, 'addTemplate').and.returnValue(of({} as VmwareTemplate));
let template: VmwareVm = {
vmname: 'test',
vmx_path: ''
};
component.vmwareTemplate = {} as VmwareTemplate;
component.selectedVM = template;
component.server = {id: 1} as Server;
component.addTemplate();
expect(mockedVmwareService.addTemplate).toHaveBeenCalled();
});
it('should not call save template when virtual machine is not selected', () => {
spyOn(mockedVmwareService, 'addTemplate').and.returnValue(of({} as VmwareTemplate));
component.server = {id: 1} as Server;
component.addTemplate();
expect(mockedVmwareService.addTemplate).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,12 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">VMware preferences</h1>
</div>
</div>
<div class="default-content">
<mat-form-field class="form-field">
<input matInput type="text" [(ngModel)]="vmrunPath" placeholder="Path to vmrun:"/>
</mat-form-field>
</div>
</div>

View File

@ -0,0 +1,3 @@
.form-field {
width: 100%;
}

View File

@ -0,0 +1,52 @@
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedServerService } from '../../../../services/server.service.spec';
import { ServerService } from '../../../../services/server.service';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { VmwarePreferencesComponent } from './vmware-preferences.component';
describe('VmwarePreferencesComponent', () => {
let component: VmwarePreferencesComponent;
let fixture: ComponentFixture<VmwarePreferencesComponent>;
let mockedServerService = new MockedServerService;
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ServerService, useValue: mockedServerService }
],
declarations: [
VmwarePreferencesComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(VmwarePreferencesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should clear path to executable', () => {
component.restoreDefaults();
expect(component.vmrunPath).toBe('');
});
});

View File

@ -0,0 +1,33 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Server } from '../../../../models/server';
import { switchMap } from 'rxjs/operators';
import { ServerService } from '../../../../services/server.service';
@Component({
selector: 'app-vmware-preferences',
templateUrl: './vmware-preferences.component.html',
styleUrls: ['./vmware-preferences.component.scss']
})
export class VmwarePreferencesComponent implements OnInit {
server: Server;
vmrunPath: string;
constructor(
private route: ActivatedRoute,
private serverService: ServerService
) {}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
});
}
restoreDefaults(){
this.vmrunPath = '';
}
}

View File

@ -0,0 +1,180 @@
<div class="content" [ngClass]="{ shadowed: isSymbolSelectionOpened || isConfiguratorOpened }">
<div class="default-header">
<div class="row">
<h1 class="col">VMware VM configuration</h1>
</div>
</div>
<div class="default-content" *ngIf="vmwareTemplate">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
General settings
</mat-panel-title>
</mat-expansion-panel-header>
<form [formGroup]="inputForm">
<mat-form-field class="row">
<input
matInput type="text"
[(ngModel)]="vmwareTemplate.name"
formControlName="templateName"
placeholder="Template name">
</mat-form-field>
<mat-form-field class="row">
<input
matInput type="text"
[(ngModel)]="vmwareTemplate.default_name_format"
formControlName="defaultName"
placeholder="Default name format">
</mat-form-field>
<mat-form-field class="row">
<input
matInput type="text"
[(ngModel)]="vmwareTemplate.symbol"
formControlName="symbol"
placeholder="Symbol">
</mat-form-field>
<button mat-raised-button class="symbolSelectionButton" (click)="chooseSymbol()">Choose symbol</button><br/><br/>
<mat-form-field class="row">
<mat-select
placeholder="Category"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.category">
<mat-option *ngFor="let category of categories" [value]="category[1]">
{{category[0]}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="select">
<mat-select
placeholder="Console type"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.console_type">
<mat-option *ngFor="let type of consoleTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.console_auto_start">
Auto start console
</mat-checkbox>
<mat-form-field class="row">
<mat-select
placeholder="On close"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.on_close">
<mat-option *ngFor="let option of onCloseOptions" [value]="option[1]">
{{option[0]}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.headless">
Start VM in headless mode
</mat-checkbox><br/><br/>
<mat-checkbox
[ngModelOptions]="{standalone: true}"
[(ngModel)]="vmwareTemplate.linked_clone">
Use as a linked base VM (experimental)
</mat-checkbox>
</form>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Network
</mat-panel-title>
</mat-expansion-panel-header>
<mat-form-field class="row">
<input matInput type="number" [(ngModel)]="vmwareTemplate.adapters" placeholder="Adapters">
</mat-form-field>
<mat-form-field class="row">
<input matInput type="text" [(ngModel)]="vmwareTemplate.first_port_name" placeholder="First port name">
</mat-form-field>
<mat-form-field class="row">
<input matInput type="text" [(ngModel)]="vmwareTemplate.port_name_format" placeholder="Name format">
</mat-form-field>
<mat-form-field class="row">
<input matInput type="number" [(ngModel)]="vmwareTemplate.port_segment_size" placeholder="Segment size">
</mat-form-field>
<mat-form-field class="row">
<mat-select placeholder="Type" [(ngModel)]="vmwareTemplate.adapter_type">
<mat-option *ngFor="let type of networkTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button class="configButton" (click)="configureCustomAdapters()">Configure custom adapters</button><br/>
<mat-checkbox [(ngModel)]="vmwareTemplate.use_any_adapter">
Allow GNS3 to override non custom VMware adapter
</mat-checkbox>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Usage
</mat-panel-title>
</mat-expansion-panel-header>
<mat-form-field class="row">
<textarea matInput type="text" [(ngModel)]="vmwareTemplate.usage"></textarea>
</mat-form-field>
</mat-expansion-panel>
</mat-accordion>
<div class="buttons-bar">
<button mat-raised-button color="primary" (click)="onSave()">Save</button><br/>
</div>
</div>
</div>
<div class="content" class="configurator" *ngIf="isConfiguratorOpened">
<div class="default-header">
<div class="row">
<h1 class="col">Custom adapters configuration</h1>
</div>
</div>
<div class="default-content" *ngIf="vmwareTemplate">
<div class="container mat-elevation-z8">
<table class="table" mat-table [dataSource]="adapters">
<ng-container matColumnDef="adapter_number">
<th mat-header-cell *matHeaderCellDef> Adapter number </th>
<td mat-cell *matCellDef="let element"> Adapter {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="port_name">
<th mat-header-cell *matHeaderCellDef> Port name </th>
<td mat-cell *matCellDef="let element"> Ethernet {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="adapter_type">
<th mat-header-cell *matHeaderCellDef> Adapter type </th>
<td mat-cell *matCellDef="let element; let i = index;">
<mat-select placeholder="Type" [(ngModel)]="adapters[i].adapter_type">
<mat-option *ngFor="let type of networkTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div class="row">
<button mat-raised-button color="primary" class="configHideButton" (click)="saveCustomAdapters()">Apply</button><br/>
</div>
</div>
</div>
<div class="content" class="configurator" *ngIf="isSymbolSelectionOpened">
<div class="default-header">
<div class="row">
<h1 class="col">Symbol selection</h1>
<button class="top-button" (click)="chooseSymbol()" mat-raised-button color="primary">Save</button>
</div>
</div>
<div class="default-content">
<app-symbols [server]="server" [symbol]="vmwareTemplate.symbol" (symbolChanged)="symbolChanged($event)"></app-symbols>
</div>
</div>

View File

@ -0,0 +1,50 @@
.row {
width: 100%;
margin-left: 0px;
}
.select {
width: 100%;
}
.configButton {
width: 100%;
margin-bottom: 10px;
}
.configHideButton {
margin-left: 80%;
width: 20%;
margin-bottom: 10px;
}
.shadowed {
display: none;
transition: 0.25s;
}
.top-button {
height: 36px;
margin-top: 22px
}
.symbolSelectionButton {
width: 100%;
}
.nonshadowed {
opacity: 0;
transition: 0.25s;
}
th {
border: 0px!important;
}
th.mat-header-cell {
padding-bottom: 15px;
}
td.mat-cell {
padding-top: 15px;
}

View File

@ -0,0 +1,112 @@
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatTableModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedServerService } from '../../../../services/server.service.spec';
import { ServerService } from '../../../../services/server.service';
import { Server } from '../../../../models/server';
import { MockedToasterService } from '../../../../services/toaster.service.spec';
import { ToasterService } from '../../../../services/toaster.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { VmwareTemplateDetailsComponent } from './vmware-template-details.component';
import { VmwareService } from '../../../../services/vmware.service';
import { VmwareConfigurationService } from '../../../../services/vmware-configuration.service';
export class MockedVmwareService {
public getTemplate(server: Server, template_id: string) {
return of({} as VmwareTemplate);
}
public saveTemplate(server: Server, vmwareTemplate: VmwareTemplate) {
return of(vmwareTemplate);
}
}
describe('VmwareTemplateDetailsComponent', () => {
let component: VmwareTemplateDetailsComponent;
let fixture: ComponentFixture<VmwareTemplateDetailsComponent>;
let mockedServerService = new MockedServerService;
let mockedVmwareService = new MockedVmwareService;
let mockedToasterService = new MockedToasterService;
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatTableModule , MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ServerService, useValue: mockedServerService },
{ provide: VmwareService, useValue: mockedVmwareService },
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: VmwareConfigurationService, useClass: VmwareConfigurationService }
],
declarations: [
VmwareTemplateDetailsComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(VmwareTemplateDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call save template', () => {
spyOn(mockedVmwareService, 'saveTemplate').and.returnValue(of({} as VmwareTemplate));
component.inputForm.controls['templateName'].setValue('template name');
component.inputForm.controls['defaultName'].setValue('default name');
component.inputForm.controls['symbol'].setValue('symbol');
component.onSave();
expect(mockedVmwareService.saveTemplate).toHaveBeenCalled();
});
it('should not call save template when template name is empty', () => {
spyOn(mockedVmwareService, 'saveTemplate').and.returnValue(of({} as VmwareTemplate));
component.inputForm.controls['templateName'].setValue('');
component.inputForm.controls['defaultName'].setValue('default name');
component.inputForm.controls['symbol'].setValue('symbol');
component.onSave();
expect(mockedVmwareService.saveTemplate).not.toHaveBeenCalled();
});
it('should not call save template when default name is empty', () => {
spyOn(mockedVmwareService, 'saveTemplate').and.returnValue(of({} as VmwareTemplate));
component.inputForm.controls['templateName'].setValue('template name');
component.inputForm.controls['defaultName'].setValue('');
component.inputForm.controls['symbol'].setValue('symbol');
component.onSave();
expect(mockedVmwareService.saveTemplate).not.toHaveBeenCalled();
});
it('should not call save template when symbol path is empty', () => {
spyOn(mockedVmwareService, 'saveTemplate').and.returnValue(of({} as VmwareTemplate));
component.inputForm.controls['templateName'].setValue('template name');
component.inputForm.controls['defaultName'].setValue('default name');
component.inputForm.controls['symbol'].setValue('');
component.onSave();
expect(mockedVmwareService.saveTemplate).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,108 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { Server } from '../../../../models/server';
import { ToasterService } from '../../../../services/toaster.service';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { VmwareService } from '../../../../services/vmware.service';
import { VmwareConfigurationService } from '../../../../services/vmware-configuration.service';
import { CustomAdapter } from '../../../../models/qemu/qemu-custom-adapter';
@Component({
selector: 'app-vmware-template-details',
templateUrl: './vmware-template-details.component.html',
styleUrls: ['./vmware-template-details.component.scss']
})
export class VmwareTemplateDetailsComponent implements OnInit {
server: Server;
vmwareTemplate: VmwareTemplate;
inputForm: FormGroup;
adapters: CustomAdapter[] = [];
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
isConfiguratorOpened: boolean = false;
isSymbolSelectionOpened: boolean = false;
consoleTypes: string[] = [];
categories = [];
onCloseOptions = [];
networkTypes = [];
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private vmwareService: VmwareService,
private toasterService: ToasterService,
private formBuilder: FormBuilder,
private vmwareConfigurationService: VmwareConfigurationService
) {
this.inputForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required),
defaultName: new FormControl('', Validators.required),
symbol: new FormControl('', Validators.required)
});
}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
const template_id = this.route.snapshot.paramMap.get("template_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
this.getConfiguration();
this.vmwareService.getTemplate(this.server, template_id).subscribe((vmwareTemplate: VmwareTemplate) => {
this.vmwareTemplate = vmwareTemplate;
});
});
}
getConfiguration() {
this.consoleTypes = this.vmwareConfigurationService.getConsoleTypes();
this.categories = this.vmwareConfigurationService.getCategories();
this.onCloseOptions = this.vmwareConfigurationService.getOnCloseoptions();
this.networkTypes = this.vmwareConfigurationService.getNetworkTypes();
}
onSave() {
if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
this.vmwareService.saveTemplate(this.server, this.vmwareTemplate).subscribe((vmwareTemplate: VmwareTemplate) => {
this.toasterService.success("Changes saved");
});
}
}
configureCustomAdapters() {
this.isConfiguratorOpened = !this.isConfiguratorOpened;
this.adapters = [];
let adapters: CustomAdapter[] = [];
for (let i=0; i<this.vmwareTemplate.adapters; i++){
if (this.vmwareTemplate.custom_adapters[i]) {
adapters.push(this.vmwareTemplate.custom_adapters[i]);
} else {
adapters.push({
adapter_number: i,
adapter_type: 'e1000'
});
}
}
this.adapters = adapters;
}
saveCustomAdapters() {
this.isConfiguratorOpened = !this.isConfiguratorOpened;
this.vmwareTemplate.custom_adapters = this.adapters;
}
chooseSymbol() {
this.isSymbolSelectionOpened = !this.isSymbolSelectionOpened;
}
symbolChanged(chosenSymbol: string) {
this.vmwareTemplate.symbol = chosenSymbol;
}
}

View File

@ -0,0 +1,17 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">VMware VM templates</h1>
<button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/vmware/addtemplate" mat-raised-button color="primary">Add VMware template</button>
</div>
</div>
<div class="default-content">
<div class="container mat-elevation-z8">
<mat-nav-list *ngIf="server">
<mat-list-item *ngFor='let template of vmwareTemplates' routerLink="{{template.template_id}}">
{{template.name}}
</mat-list-item>
</mat-nav-list>
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
.top-button {
height: 36px;
margin-top: 22px
}

View File

@ -0,0 +1,57 @@
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedServerService } from '../../../../services/server.service.spec';
import { ServerService } from '../../../../services/server.service';
import { Server } from '../../../../models/server';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { VmwareTemplatesComponent } from './vmware-templates.component';
import { VmwareService } from '../../../../services/vmware.service';
export class MockedVmwareService {
public getTemplates(server: Server) {
return of([{} as VmwareTemplate]);
}
}
describe('VmwareTemplatesComponent', () => {
let component: VmwareTemplatesComponent;
let fixture: ComponentFixture<VmwareTemplatesComponent>;
let mockedServerService = new MockedServerService;
let mockedVmwareService = new MockedVmwareService;
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ServerService, useValue: mockedServerService },
{ provide: VmwareService, useValue: mockedVmwareService }
],
declarations: [
VmwareTemplatesComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(VmwareTemplatesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,38 @@
import { Component, OnInit } from "@angular/core";
import { Server } from '../../../../models/server';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { VmwareTemplate } from '../../../../models/templates/vmware-template';
import { VmwareService } from '../../../../services/vmware.service';
@Component({
selector: 'app-vmware-templates',
templateUrl: './vmware-templates.component.html',
styleUrls: ['./vmware-templates.component.scss']
})
export class VmwareTemplatesComponent implements OnInit {
server: Server;
vmwareTemplates: VmwareTemplate[] = [];
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private vmwareService: VmwareService
) {}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
this.vmwareService.getTemplates(this.server).subscribe((vpcsTemplates: VmwareTemplate[]) => {
vpcsTemplates.forEach((template) => {
if ((template.template_type === 'vmware') && !template.builtin) {
this.vmwareTemplates.push(template);
}
});
});
});
}
}

View File

@ -21,7 +21,7 @@ export class VpcsPreferencesComponent implements OnInit {
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
const template_id = this.route.snapshot.paramMap.get("template_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
});

View File

@ -62,7 +62,7 @@ export class VpcsTemplateDetailsComponent implements OnInit {
if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
this.vpcsService.saveTemplate(this.server, this.vpcsTemplate).subscribe((vpcsTemaple: VpcsTemplate) => {
this.vpcsService.saveTemplate(this.server, this.vpcsTemplate).subscribe((vpcsTemplate: VpcsTemplate) => {
this.toasterService.success("Changes saved");
});
}

View File

@ -0,0 +1,26 @@
import { CustomAdapter } from '../qemu/qemu-custom-adapter';
export class VmwareTemplate {
adapter_type: string;
adapters: number;
builtin: boolean;
category: string;
compute_id: string;
console_auto_start: boolean;
console_type: string;
custom_adapters: CustomAdapter[];
default_name_format: string;
first_port_name: string;
headless: boolean;
linked_clone: boolean;
name: string;
on_close: string;
port_name_format: string;
port_segment_size: number;
symbol: string;
template_id: string;
template_type: string;
usage: string;
use_any_adapter: boolean;
vmx_path: string;
}

View File

@ -0,0 +1,4 @@
export class VmwareVm {
vmname: string;
vmx_path: string;
}

View File

@ -7,6 +7,7 @@ import { EthernetHubTemplate } from '../models/templates/ethernet-hub-template';
import { CloudTemplate } from '../models/templates/cloud-template';
import { EthernetSwitchTemplate } from '../models/templates/ethernet-switch-template';
import { IosTemplate } from '../models/templates/ios-template';
import { VmwareTemplate } from '../models/templates/vmware-template';
@Injectable()
export class TemplateMocksService {
@ -193,4 +194,33 @@ export class TemplateMocksService {
return of(template);
}
getVmwareTemplate() : Observable<VmwareTemplate> {
let template: VmwareTemplate = {
adapter_type: 'e1000',
adapters: 1,
builtin: false,
category: 'guest',
compute_id: 'local',
console_auto_start: false,
console_type: 'none',
custom_adapters: [],
default_name_format: '{name}-{0}',
first_port_name: '',
headless: false,
linked_clone: false,
name: '',
on_close: 'power-off',
port_name_format: 'Ethernet{0}',
port_segment_size: 0,
symbol: ':/symbols/vmware_guest.svg',
template_id: '',
template_type: 'vmware',
usage: '',
use_any_adapter: false,
vmx_path: ''
};
return of(template);
}
}

View File

@ -101,4 +101,11 @@ describe('VirtualBoxService', () => {
expect(req.request.method).toEqual('POST');
expect(req.request.body).toEqual(template);
}));
it('should get available virtual machines', inject([VirtualBoxService], (service: VirtualBoxService) => {
service.getVirtualMachines(server).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/compute/virtualbox/vms');
expect(req.request.method).toEqual('GET');
}));
});

View File

@ -0,0 +1,39 @@
import { Injectable } from "@angular/core";
@Injectable()
export class VmwareConfigurationService{
getConsoleTypes() {
return ['telnet', 'none'];
}
getOnCloseoptions() {
let onCloseOptions = [["Power off the VM", "power_off"],
["Send the shutdown signal (ACPI)", "shutdown_signal"],
["Save the VM state", "save_vm_state"]];
return onCloseOptions;
}
getCategories() {
let categories = [["Default", "guest"],
["Routers", "routers"],
["Switches", "switches"],
["End devices", "end_devices"],
["Security devices", "security_devices"]];
return categories;
}
getNetworkTypes() {
let networkTypes = ["default",
"e1000",
"e1000e",
"flexible",
"vlance",
"vmxnet",
"vmxnet2",
"vmxnet3"];
return networkTypes;
}
}

View File

@ -0,0 +1,109 @@
import { TestBed, inject } from '@angular/core/testing';
import { HttpClient } from '@angular/common/http';
import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpServer } from './http-server.service';
import { Server } from '../models/server';
import { getTestServer } from './testing';
import { AppTestingModule } from '../testing/app-testing/app-testing.module';
import { VmwareService } from './vmware.service';
import { VmwareTemplate } from '../models/templates/vmware-template';
describe('VmwareService', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
let httpServer: HttpServer;
let server: Server;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, AppTestingModule],
providers: [HttpServer, VmwareService]
});
httpClient = TestBed.get(HttpClient);
httpTestingController = TestBed.get(HttpTestingController);
httpServer = TestBed.get(HttpServer);
server = getTestServer();
});
afterEach(() => {
httpTestingController.verify();
});
it('should be created', inject([VmwareService], (service: VmwareService) => {
expect(service).toBeTruthy();
}));
it('should update vmware template', inject([VmwareService], (service: VmwareService) => {
const template: VmwareTemplate = {
adapter_type: 'e1000',
adapters: 1,
builtin: false,
category: 'guest',
compute_id: 'local',
console_auto_start: false,
console_type: 'none',
custom_adapters: [],
default_name_format: '{name}-{0}',
first_port_name: '',
headless: false,
linked_clone: false,
name: '',
on_close: 'power-off',
port_name_format: 'Ethernet{0}',
port_segment_size: 0,
symbol: ':/symbols/vmware_guest.svg',
template_id: '1',
template_type: 'vmware',
usage: '',
use_any_adapter: false,
vmx_path: ''
};
service.saveTemplate(server, template).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/templates/1');
expect(req.request.method).toEqual('PUT');
expect(req.request.body).toEqual(template);
}));
it('should add vmware template', inject([VmwareService], (service: VmwareService) => {
const template: VmwareTemplate = {
adapter_type: 'e1000',
adapters: 1,
builtin: false,
category: 'guest',
compute_id: 'local',
console_auto_start: false,
console_type: 'none',
custom_adapters: [],
default_name_format: '{name}-{0}',
first_port_name: '',
headless: false,
linked_clone: false,
name: '',
on_close: 'power-off',
port_name_format: 'Ethernet{0}',
port_segment_size: 0,
symbol: ':/symbols/vmware_guest.svg',
template_id: '1',
template_type: 'vmware',
usage: '',
use_any_adapter: false,
vmx_path: ''
};
service.addTemplate(server, template).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/templates');
expect(req.request.method).toEqual('POST');
expect(req.request.body).toEqual(template);
}));
it('should get available virtual machines', inject([VmwareService], (service: VmwareService) => {
service.getVirtualMachines(server).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/compute/vmware/vms');
expect(req.request.method).toEqual('GET');
}));
});

View File

@ -0,0 +1,31 @@
import { Injectable } from "@angular/core";
import { HttpServer } from './http-server.service';
import { Server } from '../models/server';
import { Observable } from 'rxjs';
import { VmwareTemplate } from '../models/templates/vmware-template';
import { VmwareVm } from '../models/vmware/vmware-vm';
@Injectable()
export class VmwareService {
constructor(private httpServer: HttpServer) {}
getTemplates(server: Server): Observable<VmwareTemplate[]> {
return this.httpServer.get<VmwareTemplate[]>(server, '/templates') as Observable<VmwareTemplate[]>;
}
getTemplate(server: Server, template_id: string): Observable<VmwareTemplate> {
return this.httpServer.get<VmwareTemplate>(server, `/templates/${template_id}`) as Observable<VmwareTemplate>;
}
addTemplate(server: Server, vmwareTemplate: VmwareTemplate): Observable<VmwareTemplate> {
return this.httpServer.post<VmwareTemplate>(server, `/templates`, vmwareTemplate) as Observable<VmwareTemplate>;
}
saveTemplate(server: Server, vmwareTemplate: VmwareTemplate): Observable<VmwareTemplate> {
return this.httpServer.put<VmwareTemplate>(server, `/templates/${vmwareTemplate.template_id}`, vmwareTemplate) as Observable<VmwareTemplate>;
}
getVirtualMachines(server: Server) : Observable<VmwareVm[]> {
return this.httpServer.get<VmwareVm[]>(server, '/compute/vmware/vms') as Observable<VmwareVm[]>;
}
}

View File

@ -34,6 +34,7 @@
})();
</script>
</head>
<!-- <body class="mat-app-background" oncontextmenu="return false;"> -->
<body class="mat-app-background" oncontextmenu="return false;">
<app-root></app-root>
</body>