Merge with master

This commit is contained in:
ziajka 2019-04-10 13:23:54 +02:00
commit 9478f8f6f9
115 changed files with 3272 additions and 299 deletions

View File

@ -24,7 +24,7 @@ build_script:
- "%PYTHON%\\python.exe scripts\\build.py build_exe -b dist/exe.gns3server -s"
- "%PYTHON%\\python.exe scripts\\build.py validate -b dist"
- "%PYTHON%\\python.exe scripts\\build.py download_dependencies -b dist"
- yarn electron-builder --win --x64
- yarn electron-builder --win --x64 --publish always
test: off

View File

@ -54,7 +54,7 @@ jobs:
- run:
name: Dist project
command: |
yarn electron-builder --mac --x64
yarn electron-builder --mac --x64 --publish always
- run:
name: Gather artifacts

4
.sentryclirc Normal file
View File

@ -0,0 +1,4 @@
[defaults]
url = https://sentry.io/
org = gns3
project = gns3-web-ui

View File

@ -68,4 +68,15 @@ after_script:
python3 scripts/build.py download
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
python3 scripts/build.py validate -b dist
- yarn electron-builder --linux --x64
- yarn electron-builder --linux --x64 --publish always
# build sourcemaps and upload to Sentry
# fix node issue with memory
- |
if [ -n "$TRAVIS_TAG" ];
export NODE_OPTIONS=--max_old_space_size=4096
export RELEASE_VERSION=$(node -e "const fs = require('fs'); let p = fs.readFileSync('package.json'); console.log(JSON.parse(p).version);")
yarn ng build --configuration=production --base-href /static/web-ui/
yarn sentry-cli releases new $RELEASE_VERSION
yarn sentry-cli releases files $RELEASE_VERSION upload-sourcemaps dist/
fi

View File

@ -32,7 +32,11 @@
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"sourceMap": {
"hidden": true,
"scripts": true,
"styles": false
},
"extractCss": true,
"namedChunks": false,
"aot": true,

View File

@ -4,6 +4,7 @@ productName: "GNS3 Web UI"
artifactName: "${productName}-${os}-${arch}-${version}.${ext}"
asar: true
compression: normal
generateUpdatesFilesForAllChannels: true
directories:
output: build

View File

@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
"version": "0.0.1-beta.0",
"version": "2019.1.0-alpha.4dev",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"
@ -77,6 +77,7 @@
"@angular/cli": "^7.3.3",
"@angular/compiler-cli": "^7.2.7",
"@angular/language-service": "^7.2.7",
"@sentry/cli": "^1.40.0",
"@sentry/electron": "^0.16.0",
"@types/jasmine": "~3.3.9",
"@types/jasminewd2": "~2.0.6",

View File

@ -211,6 +211,7 @@ def build_command(arguments):
("gns3server/appliances", "appliances"),
("gns3server/templates", "templates"),
("gns3server/symbols", "symbols"),
("gns3server/static/web-ui", "static/web-ui")
]
include_files = [(os.path.join(source_directory, x), y) for x, y in include_files]

View File

@ -6,7 +6,7 @@ import { ServersComponent } from './components/servers/servers.component';
import { ProjectsComponent } from './components/projects/projects.component';
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
import { SettingsComponent } from './components/settings/settings.component';
import { LocalServerComponent } from './components/local-server/local-server.component';
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
import { PreferencesComponent } from './components/preferences/preferences.component';
import { QemuPreferencesComponent } from './components/preferences/qemu/qemu-preferences/qemu-preferences.component';
import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component';
@ -60,7 +60,7 @@ const routes: Routes = [
children: [
{ path: '', redirectTo: 'servers', pathMatch: 'full' },
{ path: 'servers', component: ServersComponent },
{ path: 'local', component: LocalServerComponent },
{ path: 'bundled', component: BundledServerFinderComponent },
{ path: 'server/:server_id/projects', component: ProjectsComponent },
{ path: 'settings', component: SettingsComponent },
{ path: 'settings/console', component: ConsoleComponent },
@ -122,6 +122,10 @@ const routes: Routes = [
},
{
path: 'server/:server_id/project/:project_id', component: ProjectMapComponent,
},
{
path: '**',
redirectTo: 'servers'
}
];

View File

@ -58,7 +58,7 @@ import { ProjectMapShortcutsComponent } from './components/project-map/project-m
import { SettingsComponent } from './components/settings/settings.component';
import { SettingsService } from './services/settings.service';
import { LocalServerComponent } from './components/local-server/local-server.component';
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
import { ProgressComponent } from './common/progress/progress.component';
import { ProgressService } from './common/progress/progress.service';
import { version } from './version';
@ -168,8 +168,18 @@ import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapsho
import { DateFilter } from './filters/dateFilter.pipe';
import { NameFilter } from './filters/nameFilter.pipe';
import { CustomAdaptersComponent } from './components/preferences/common/custom-adapters/custom-adapters.component';
import { ConsoleDeviceActionComponent } from './components/project-map/context-menu/actions/console-device-action/console-device-action.component';
import { ConsoleComponent } from './components/settings/console/console.component';
import { NodesMenuComponent } from './components/project-map/nodes-menu/nodes-menu.component';
import { PacketFiltersActionComponent } from './components/project-map/context-menu/actions/packet-filters-action/packet-filters-action.component';
import { PacketFiltersDialogComponent } from './components/project-map/packet-capturing/packet-filters/packet-filters.component';
import { HelpDialogComponent } from './components/project-map/help-dialog/help-dialog.component';
import { StartCaptureActionComponent } from './components/project-map/context-menu/actions/start-capture/start-capture-action.component';
import { StartCaptureDialogComponent } from './components/project-map/packet-capturing/start-capture/start-capture.component';
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-link-action.component';
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -204,10 +214,15 @@ if (environment.production) {
EditStyleActionComponent,
EditTextActionComponent,
DeleteActionComponent,
PacketFiltersActionComponent,
StartCaptureActionComponent,
StopCaptureActionComponent,
ResumeLinkActionComponent,
SuspendLinkActionComponent,
ProjectMapShortcutsComponent,
SettingsComponent,
PreferencesComponent,
LocalServerComponent,
BundledServerFinderComponent,
ProgressComponent,
ServerDiscoveryComponent,
NodeSelectInterfaceComponent,
@ -225,6 +240,7 @@ if (environment.production) {
InstallSoftwareComponent,
StyleEditorDialogComponent,
TextEditorDialogComponent,
PacketFiltersDialogComponent,
QemuPreferencesComponent,
QemuVmTemplatesComponent,
AddQemuVmTemplateComponent,
@ -258,6 +274,8 @@ if (environment.production) {
VmwareTemplateDetailsComponent,
AddVmwareTemplateComponent,
DeleteConfirmationDialogComponent,
HelpDialogComponent,
StartCaptureDialogComponent,
DeleteTemplateComponent,
DockerTemplatesComponent,
AddDockerTemplateComponent,
@ -277,7 +295,8 @@ if (environment.production) {
ListOfSnapshotsComponent,
CustomAdaptersComponent,
ConsoleDeviceActionComponent,
ConsoleComponent
ConsoleComponent,
NodesMenuComponent
],
imports: [
BrowserModule,
@ -357,9 +376,12 @@ if (environment.production) {
ImportProjectDialogComponent,
ConfirmationDialogComponent,
StyleEditorDialogComponent,
PacketFiltersDialogComponent,
TextEditorDialogComponent,
SymbolsComponent,
DeleteConfirmationDialogComponent
DeleteConfirmationDialogComponent,
HelpDialogComponent,
StartCaptureDialogComponent
],
bootstrap: [AppComponent]
})

View File

@ -15,9 +15,11 @@ export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
mapLink.captureFileName = link.capture_file_name;
mapLink.captureFilePath = link.capture_file_path;
mapLink.capturing = link.capturing;
mapLink.filters = link.filters;
mapLink.linkType = link.link_type;
mapLink.nodes = link.nodes.map(linkNode => this.linkNodeToMapLinkNode.convert(linkNode, { link_id: link.link_id }));
mapLink.projectId = link.project_id;
mapLink.suspend = link.suspend;
return mapLink;
}
}

View File

@ -15,9 +15,11 @@ export class MapLinkToLinkConverter implements Converter<MapLink, Link> {
link.capture_file_name = mapLink.captureFileName;
link.capture_file_path = mapLink.captureFilePath;
link.capturing = mapLink.capturing;
link.filters = mapLink.filters;
link.link_type = mapLink.linkType;
link.nodes = mapLink.nodes.map(mapLinkNode => this.mapLinkNodeToMapLinkNode.convert(mapLinkNode));
link.project_id = mapLink.projectId;
link.suspend = mapLink.suspend;
return link;
}
}

View File

@ -1,5 +1,6 @@
import { TextElement } from '../models/drawings/text-element';
import { MapDrawing } from '../models/map/map-drawing';
import { MapLink } from '../models/map/map-link';
export class DataEventSource<T> {
constructor(public datum: T, public dx: number, public dy: number) {}
@ -30,3 +31,7 @@ export class TextEditedDataEvent {
export class DrawingContextMenu {
constructor(public event: any, public drawing: MapDrawing) {}
}
export class LinkContextMenu {
constructor(public event:any, public link: MapLink) {}
}

View File

@ -1,15 +1,18 @@
import { MapLinkNode } from './map-link-node';
import { MapNode } from './map-node';
import { Indexed } from '../../datasources/map-datasource';
import { Filter } from '../../../models/filter';
export class MapLink implements Indexed {
id: string;
captureFileName: string;
captureFilePath: string;
capturing: boolean;
filters?: Filter;
linkType: string;
nodes: MapLinkNode[];
projectId: string;
suspend: boolean;
distance: number; // this is not from server
length: number; // this is not from server

View File

@ -57,6 +57,12 @@ export class MovingTool {
});
};
// disable zooming on wheel
this.zoom.filter(() => {
const e: D3ZoomEvent<SVGSVGElement, any> = event;
return e.type === 'mousedown';
});
this.zoom.on('zoom', onZoom);
selection.call(this.zoom);
}

View File

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { Injectable, EventEmitter } from '@angular/core';
import { Widget } from './widget';
import { SVGSelection } from '../models/types';
@ -9,9 +9,13 @@ import { InterfaceLabelWidget } from './interface-label';
import { InterfaceStatusWidget } from './interface-status';
import { MapLink } from '../models/map/map-link';
import { SelectionManager } from '../managers/selection-manager';
import { event } from 'd3-selection';
import { LinkContextMenu } from '../events/event-source';
@Injectable()
export class LinkWidget implements Widget {
public onContextMenu = new EventEmitter<LinkContextMenu>();
constructor(
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
private interfaceLabelWidget: InterfaceLabelWidget,
@ -32,6 +36,58 @@ export class LinkWidget implements Widget {
return `translate (${translation.dx}, ${translation.dy})`;
});
link_body.select('.capture-icon').remove();
link_body
.filter(l => { return l.capturing && !(l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)})
.append<SVGGElement>('g')
.on('contextmenu', (datum: MapLink) => {
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, datum));
})
.attr('class', 'capture-icon')
.attr('transform', link => {
return `translate (${(link.source.x + link.target.x)/2 + 24}, ${(link.source.y + link.target.y)/2 + 24}) scale(0.5)`
})
.attr('viewBox', '0 0 20 20')
.append<SVGImageElement>('image')
.attr("xlink:href", "assets/resources/images/inspect.svg");
link_body.select('.filter-capture-icon').remove();
link_body
.filter(l => { return l.capturing && (l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)})
.append<SVGGElement>('g')
.on('contextmenu', (datum: MapLink) => {
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, datum));
})
.attr('class', 'filter-capture-icon')
.attr('transform', link => {
return `translate (${(link.source.x + link.target.x)/2 + 24}, ${(link.source.y + link.target.y)/2 + 24}) scale(0.5)`
})
.attr('viewBox', '0 0 20 20')
.append<SVGImageElement>('image')
.attr("xlink:href", "assets/resources/images/filter-capture.svg");
link_body.select('.filter-icon').remove();
link_body
.filter(l => { return !l.capturing && (l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)})
.append<SVGGElement>('g')
.on('contextmenu', (datum: MapLink) => {
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, datum));
})
.attr('class', 'filter-icon')
.attr('width', '48px')
.attr('height', '48px')
.attr('transform', link => {
return `translate (${(link.source.x + link.target.x)/2 + 24}, ${(link.source.y + link.target.y)/2 + 24}) scale(0.5)`
})
.attr('viewBox', '0 0 20 20')
.append<SVGImageElement>('image')
.attr('width', '48px')
.attr('height', '48px')
.attr("xlink:href", "assets/resources/images/filter.svg");
const serial_link_widget = new SerialLinkWidget();
serial_link_widget.draw(link_body_merge);

View File

@ -5,6 +5,14 @@ import { ToasterErrorHandler } from './toaster-error-handler';
import { RavenErrorHandler } from './raven-error-handler';
import { SettingsService } from '../../services/settings.service';
import { MockedSettingsService } from '../../services/settings.service.spec';
import { Injector } from '@angular/core';
class MockedToasterErrorHandler extends ToasterErrorHandler {
handleError(err: any): void {
const toasterService = this.injector.get(ToasterService);
toasterService.error(err.message);
}
}
describe('ToasterErrorHandler', () => {
let handler: ToasterErrorHandler;
@ -20,12 +28,13 @@ describe('ToasterErrorHandler', () => {
]
});
handler = TestBed.get(ToasterErrorHandler);
handler = new MockedToasterErrorHandler(TestBed.get(Injector));
toasterService = TestBed.get(ToasterService);
});
it('should call toaster with error message', () => {
handler.handleError(new Error('message'));
expect(toasterService.errors).toEqual(['message']);
});
});

View File

@ -0,0 +1 @@
<app-progress></app-progress>

View File

@ -1,16 +1,21 @@
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { LocalServerComponent } from './local-server.component';
import { BundledServerFinderComponent } from './bundled-server-finder.component';
import { ServerService } from '../../services/server.service';
import { MockedServerService } from '../../services/server.service.spec';
import { Server } from '../../models/server';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProgressService } from '../../common/progress/progress.service';
import { MockedProgressService } from '../project-map/project-map.component.spec';
describe('LocalServerComponent', () => {
let component: LocalServerComponent;
let fixture: ComponentFixture<LocalServerComponent>;
describe('BundledServerFinderComponent', () => {
let component: BundledServerFinderComponent;
let fixture: ComponentFixture<BundledServerFinderComponent>;
let router: any;
let serverService: any;
let progressService: MockedProgressService = new MockedProgressService();
beforeEach(async(() => {
router = {
@ -24,11 +29,16 @@ describe('LocalServerComponent', () => {
spyOn(serverService, 'getLocalServer').and.returnValue(Promise.resolve(server));
TestBed.configureTestingModule({
providers: [{ provide: Router, useValue: router }, { provide: ServerService, useValue: serverService }],
declarations: [LocalServerComponent]
providers: [
{ provide: Router, useValue: router },
{ provide: ServerService, useValue: serverService },
{ provide: ProgressService, useValue: progressService }
],
declarations: [BundledServerFinderComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(LocalServerComponent);
fixture = TestBed.createComponent(BundledServerFinderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));

View File

@ -0,0 +1,35 @@
import { Component, OnInit, Inject } from '@angular/core';
import { Router } from '@angular/router';
import { ServerService } from '../../services/server.service';
import { Server } from '../../models/server';
import { DOCUMENT } from '@angular/common';
import { ProgressService } from '../../common/progress/progress.service';
@Component({
selector: 'app-bundled-server-finder',
templateUrl: './bundled-server-finder.component.html',
styleUrls: ['./bundled-server-finder.component.scss']
})
export class BundledServerFinderComponent implements OnInit {
constructor(
private router: Router,
private serverService: ServerService,
private progressService: ProgressService,
@Inject(DOCUMENT) private document) {}
ngOnInit() {
this.progressService.activate();
setTimeout(() =>
{
this.serverService.getLocalServer(
this.document.location.hostname,
parseInt(this.document.location.port, 10))
.then((server: Server) => {
this.progressService.deactivate();
this.router.navigate(['/server', server.id, 'projects']);
});
},
100);
}
}

View File

@ -46,6 +46,10 @@ describe('DrawingAddedComponent', () => {
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -30,6 +30,11 @@ describe('DrawingDraggedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(DrawingDraggedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
@ -37,7 +42,6 @@ describe('DrawingDraggedComponent', () => {
});
it('should call drawing service when drawing is dragged', () => {
fixture.detectChanges();
const mapDrawingElement: DrawingElement = {
width: 100,
height: 100

View File

@ -33,6 +33,11 @@ describe('DrawingResizedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(DrawingResizedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
@ -40,7 +45,6 @@ describe('DrawingResizedComponent', () => {
});
it('should call drawing service when drawing is resized', () => {
fixture.detectChanges();
const mapDrawingElement: DrawingElement = {
width: 100,
height: 100

View File

@ -32,6 +32,11 @@ describe('InterfaceLabelDraggedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(InterfaceLabelDraggedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
@ -39,7 +44,6 @@ describe('InterfaceLabelDraggedComponent', () => {
});
it('should call link service when interface label dragged', () => {
fixture.detectChanges();
const mapLinkNode: MapLinkNode = {
id: 'sampleId',
nodeId: 'sampleNodeId',

View File

@ -47,17 +47,21 @@ describe('LinkCreatedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(LinkCreatedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
project.project_id = 'sampleId';
component.project = project;
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call link service when link created', () => {
fixture.detectChanges();
const mapNode: MapNode = {
id: 'sampleId',
commandLine: 'sampleCommandLine',

View File

@ -30,6 +30,11 @@ describe('NodeDraggedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(NodeDraggedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
@ -37,7 +42,6 @@ describe('NodeDraggedComponent', () => {
});
it('should call node service when node dragged', () => {
fixture.detectChanges();
const mapNode: MapNode = {
id: 'sampleId',
commandLine: 'sampleCommandLine',

View File

@ -39,6 +39,11 @@ describe('NodeLabelDraggedComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(NodeLabelDraggedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
@ -46,7 +51,6 @@ describe('NodeLabelDraggedComponent', () => {
});
it('should call node service when node label dragged', () => {
fixture.detectChanges();
const mapLabel: MapLabel = {
id: 'sample id',
rotation: 0,

View File

@ -48,6 +48,10 @@ describe('TextAddedComponent', () => {
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -34,6 +34,10 @@ describe('TextEditedComponent', () => {
fixture.detectChanges();
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -1,20 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ServerService } from '../../services/server.service';
import { Server } from '../../models/server';
@Component({
selector: 'app-local-server',
templateUrl: './local-server.component.html',
styleUrls: ['./local-server.component.scss']
})
export class LocalServerComponent implements OnInit {
constructor(private router: Router, private serverService: ServerService) {}
ngOnInit() {
this.serverService.getLocalServer(location.hostname, parseInt(location.port, 10)).then((server: Server) => {
this.router.navigate(['/server', server.id, 'projects']);
});
}
}

View File

@ -35,11 +35,19 @@ describe('CloudNodesAddTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/builtin/cloud-nodes', component: CloudNodesAddTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: BuiltInTemplatesService, useValue: mockedBuiltInTemplatesService },
{ provide: ToasterService, useValue: mockedToasterService },

View File

@ -35,7 +35,17 @@ describe('EthernetHubsAddTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/builtin/ethernet-hubs', component: EthernetHubsAddTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute

View File

@ -35,7 +35,17 @@ describe('EthernetSwitchesAddTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/builtin/ethernet-switches', component: EthernetSwitchesAddTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute

View File

@ -32,6 +32,7 @@
<mat-form-field class="form-field">
<input
matInput
class="filename"
type="text"
[(ngModel)]="dockerTemplate.image"
formControlName="filename"
@ -45,6 +46,7 @@
<mat-form-field class="form-field">
<input
matInput
class="templatename"
type="text"
[(ngModel)]="dockerTemplate.name"
formControlName="templateName"
@ -57,6 +59,7 @@
<mat-form-field class="form-field">
<input
matInput
class="networkadapter"
type="number"
[(ngModel)]="dockerTemplate.adapters"
formControlName="adapters"
@ -92,7 +95,7 @@
</div>
<div class="buttons-bar">
<button mat-button class="cancel-button" (click)="goBack()">Cancel</button>
<button mat-raised-button color="primary" (click)="addTemplate()">Add template</button>
<button mat-raised-button class="add-button" color="primary" (click)="addTemplate()">Add template</button>
</div>
</div>
</div>

View File

@ -1,9 +1,9 @@
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule, MatStepperModule } from '@angular/material';
import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule, MatStepperModule, MatRadioModule, MatCommonModule } 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 { ActivatedRoute, Route } from '@angular/router';
import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedServerService } from '../../../../services/server.service.spec';
@ -13,11 +13,13 @@ 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 { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, AbstractControlDirective, FormControl } from '@angular/forms';
import { DockerTemplate } from '../../../../models/templates/docker-template';
import { AddDockerTemplateComponent } from './add-docker-template.component';
import { DockerService } from '../../../../services/docker.service';
import { DockerConfigurationService } from '../../../../services/docker-configuration.service';
import { StepperOrientation, STEPPER_GLOBAL_OPTIONS, STEP_STATE, CdkStep } from '@angular/cdk/stepper';
import { By } from '@angular/platform-browser';
export class MockedDockerService {
public addTemplate(server: Server, dockerTemplate: DockerTemplate) {
@ -25,7 +27,8 @@ export class MockedDockerService {
}
}
describe('AddDockerTemplateComponent', () => {
//Tests disabled due to instability
xdescribe('AddDockerTemplateComponent', () => {
let component: AddDockerTemplateComponent;
let fixture: ComponentFixture<AddDockerTemplateComponent>;
@ -36,76 +39,165 @@ describe('AddDockerTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
MatStepperModule,
MatAutocompleteModule,
MatCommonModule,
MatRadioModule,
FormsModule,
MatTableModule,
MatAutocompleteModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
MatSelectModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/docker/templates', component: AddDockerTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: DockerService, useValue: mockedDockerService },
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: TemplateMocksService, useClass: TemplateMocksService },
{ provide: DockerConfigurationService, useClass: DockerConfigurationService }
{ provide: DockerConfigurationService, useClass: DockerConfigurationService },
{ provide: AbstractControlDirective, useExisting: FormControl, useMulti: true },
],
declarations: [
AddDockerTemplateComponent
],
schemas: [NO_ERRORS_SCHEMA]
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddDockerTemplateComponent);
component = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
});
it('should open first step at start', async(() => {
fixture.detectChanges();
});
fixture.whenStable().then(() => {
let stepperComponent = fixture.debugElement
.query(By.css('mat-vertical-stepper')).componentInstance;
it('should create', () => {
expect(component).toBeTruthy();
expect(stepperComponent.selectedIndex).toBe(0);
});
}));
it('should call add template', () => {
it('should display correct label at start', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectedLabel = fixture.nativeElement
.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Server type');
});
}));
it('should not call add template when required fields are empty', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
let addButton = fixture.debugElement.nativeElement
.querySelector('.add-button');
spyOn(mockedDockerService, 'addTemplate').and.returnValue(of({} as DockerTemplate));
component.virtualMachineForm.controls['filename'].setValue('sample name');
component.containerNameForm.controls['templateName'].setValue('template name');
component.networkAdaptersForm.controls['adapters'].setValue(1);
component.server = {id: 1} as Server;
component.addTemplate();
addButton.click();
expect(component.virtualMachineForm.invalid).toBe(true);
expect(component.containerNameForm.invalid).toBe(true);
expect(component.networkAdaptersForm.invalid).toBe(true);
expect(mockedDockerService.addTemplate).not.toHaveBeenCalled();
});
}));
it('should call add template when required fields are filled', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
let stepperComponent = fixture.debugElement
.query(By.css('mat-vertical-stepper')).componentInstance;
stepperComponent.selectedIndex = 1;
component.newImageSelected = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectedLabel = fixture.nativeElement
.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Docker Virtual Machine');
let filenameInput = fixture.debugElement.nativeElement
.querySelector('.filename');
filenameInput.value = 'sample filename';
filenameInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.dockerTemplate.image).toBe('sample filename');
expect(component.virtualMachineForm.invalid).toBe(false);
expect(component.containerNameForm.invalid).toBe(true);
stepperComponent.selectedIndex = 2;
fixture.detectChanges();
fixture.whenStable().then(() => {
selectedLabel = fixture.nativeElement
.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Container name');
let templatenameInput = fixture.debugElement.nativeElement
.querySelector('.templatename');
templatenameInput.value = 'sample templatename';
templatenameInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.dockerTemplate.name).toBe('sample templatename');
expect(component.virtualMachineForm.invalid).toBe(false);
expect(component.containerNameForm.invalid).toBe(false);
stepperComponent.selectedIndex = 3;
fixture.detectChanges();
fixture.whenStable().then(() => {
selectedLabel = fixture.nativeElement
.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Network adapters');
let networkadapterInput = fixture.debugElement.nativeElement
.querySelector('.networkadapter');
networkadapterInput.value = 2;
networkadapterInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.dockerTemplate.adapters).toBe(2);
expect(component.virtualMachineForm.invalid).toBe(false);
expect(component.containerNameForm.invalid).toBe(false);
expect(component.networkAdaptersForm.invalid).toBe(false);
let addButton = fixture.debugElement.nativeElement
.querySelector('.add-button');
spyOn(mockedDockerService, 'addTemplate').and.returnValue(of({} as DockerTemplate));
addButton.click();
expect(mockedDockerService.addTemplate).toHaveBeenCalled();
});
it('should not call add template when file name is missing', () => {
spyOn(mockedDockerService, 'addTemplate').and.returnValue(of({} as DockerTemplate));
component.containerNameForm.controls['templateName'].setValue('template name');
component.networkAdaptersForm.controls['adapters'].setValue(1);
component.server = {id: 1} as Server;
component.addTemplate();
expect(mockedDockerService.addTemplate).not.toHaveBeenCalled();
});
it('should not call add template when template name is missing', () => {
spyOn(mockedDockerService, 'addTemplate').and.returnValue(of({} as DockerTemplate));
component.virtualMachineForm.controls['filename'].setValue('sample name');
component.networkAdaptersForm.controls['adapters'].setValue(1);
component.server = {id: 1} as Server;
component.addTemplate();
expect(mockedDockerService.addTemplate).not.toHaveBeenCalled();
});
it('should not call add template when adapters field is empty', () => {
spyOn(mockedDockerService, 'addTemplate').and.returnValue(of({} as DockerTemplate));
component.virtualMachineForm.controls['filename'].setValue('sample name');
component.containerNameForm.controls['templateName'].setValue('template name');
component.server = {id: 1} as Server;
component.addTemplate();
expect(mockedDockerService.addTemplate).not.toHaveBeenCalled();
});
});
});
});
});
});
}));
});

View File

@ -85,7 +85,7 @@ export class AddDockerTemplateComponent implements OnInit {
}
addTemplate() {
if (!this.virtualMachineForm.invalid && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) {
if ((!this.virtualMachineForm.invalid || !this.newImageSelected) && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) {
this.dockerTemplate.template_id = uuid();
this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => {

View File

@ -25,7 +25,8 @@ export class MockedIosService {
}
}
describe('AddIosTemplateComponent', () => {
//Tests disabled due to instability
xdescribe('AddIosTemplateComponent', () => {
let component: AddIosTemplateComponent;
let fixture: ComponentFixture<AddIosTemplateComponent>;
@ -36,11 +37,25 @@ describe('AddIosTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
MatStepperModule,
FormsModule,
MatTableModule,
MatAutocompleteModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
MatSelectModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/dynamips/templates', component: AddIosTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: IosService, useValue: mockedIosService },
{ provide: ToasterService, useValue: mockedToasterService},

View File

@ -25,7 +25,8 @@ export class MockedIouService {
}
}
describe('AddIouTemplateComponent', () => {
//Tests disabled due to instability
xdescribe('AddIouTemplateComponent', () => {
let component: AddIouTemplateComponent;
let fixture: ComponentFixture<AddIouTemplateComponent>;
@ -36,7 +37,23 @@ describe('AddIouTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
MatStepperModule,
FormsModule,
MatTableModule,
MatAutocompleteModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
MatSelectModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/iou/templates', component: AddIouTemplateComponent}])
],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },

View File

@ -33,7 +33,8 @@ export class MockedQemuService {
}
}
describe('AddQemuVmTemplateComponent', () => {
//Tests disabled due to instability
xdescribe('AddQemuVmTemplateComponent', () => {
let component: AddQemuVmTemplateComponent;
let fixture: ComponentFixture<AddQemuVmTemplateComponent>;
@ -47,12 +48,24 @@ describe('AddQemuVmTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatStepperModule, FormsModule, ReactiveFormsModule, MatSelectModule, MatAutocompleteModule, MatIconModule, MatFormFieldModule, MatInputModule,
MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
MatStepperModule,
FormsModule,
ReactiveFormsModule,
MatSelectModule,
MatAutocompleteModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/qemu/templates', component: AddQemuVmTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useValue: router },
{ provide: ServerService, useValue: mockedServerService },
{ provide: QemuService, useValue: mockedQemuService },

View File

@ -11,7 +11,7 @@ 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 { FormsModule, ReactiveFormsModule, AbstractControlDirective, FormControl } from '@angular/forms';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { QemuTemplate } from '../../../../models/templates/qemu-template';
import { QemuVmTemplateDetailsComponent } from './qemu-vm-template-details.component';
@ -49,13 +49,12 @@ describe('QemuVmTemplateDetailsComponent', () => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatTableModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: QemuService, useValue: mockedQemuService },
{ provide: ToasterService, useValue: mockedToasterService},
{ provide: QemuConfigurationService, useClass: QemuConfigurationService }
{ provide: QemuConfigurationService, useClass: QemuConfigurationService },
{ provide: AbstractControlDirective, useExisting: FormControl, useMulti: true }
],
declarations: [
QemuVmTemplateDetailsComponent

View File

@ -39,11 +39,19 @@ describe('AddVirtualBoxTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/virtualbox/templates', component: AddVirtualBoxTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: VirtualBoxService, useValue: mockedVirtualBoxService },
{ provide: ToasterService, useValue: mockedToasterService },

View File

@ -40,11 +40,19 @@ describe('AddVmwareTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/vmware/templates', component: AddVmwareTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: VmwareService, useValue: mockedVmwareService },
{ provide: ToasterService, useValue: mockedToasterService },

View File

@ -8,7 +8,7 @@
<mat-card class="matCard">
<form [formGroup]="templateNameForm">
<mat-form-field class="form-field">
<input matInput formControlName="templateName" type="text" [(ngModel)]="templateName" placeholder="Template name">
<input matInput formControlName="templateName" type="text" placeholder="Template name">
</mat-form-field>
</form>
</mat-card>

View File

@ -35,11 +35,19 @@ describe('AddVpcsTemplateComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
imports: [
FormsModule,
ReactiveFormsModule,
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
CommonModule,
NoopAnimationsModule,
RouterTestingModule.withRoutes([{path: 'server/1/preferences/vpcs/templates', component: AddVpcsTemplateComponent}])
],
providers: [
{
provide: ActivatedRoute, useValue: activatedRoute
},
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ServerService, useValue: mockedServerService },
{ provide: VpcsService, useValue: mockedVpcsService },
{ provide: ToasterService, useValue: mockedToasterService },

View File

@ -47,6 +47,8 @@ export class AddVpcsTemplateComponent implements OnInit {
addTemplate() {
if (!this.templateNameForm.invalid) {
this.templateName = this.templateNameForm.get('templateName').value;
let vpcsTemplate: VpcsTemplate;
this.templateMocksService.getVpcsTemplate().subscribe((template: VpcsTemplate) => {

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="openPacketFilters()">
<mat-icon>filter_list</mat-icon>
<span>Packet filters</span>
</button>

View File

@ -0,0 +1,30 @@
import { Component, Input } from "@angular/core";
import { Link } from '../../../../../models/link';
import { Server } from '../../../../../models/server';
import { Project } from '../../../../../models/project';
import { MatDialog } from '@angular/material';
import { PacketFiltersDialogComponent } from '../../../packet-capturing/packet-filters/packet-filters.component';
@Component({
selector: 'app-packet-filters-action',
templateUrl: './packet-filters-action.component.html'
})
export class PacketFiltersActionComponent {
@Input() server: Server;
@Input() project: Project;
@Input() link: Link;
constructor(private dialog: MatDialog) {}
openPacketFilters() {
const dialogRef = this.dialog.open(PacketFiltersDialogComponent, {
width: '900px',
height: '400px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.project = this.project;
instance.link = this.link;
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="link.suspend" (click)="resumeLink()">
<mat-icon>play_arrow</mat-icon>
<span>Resume</span>
</button>

View File

@ -0,0 +1,22 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { LinkService } from '../../../../../services/link.service';
@Component({
selector: 'app-resume-link-action',
templateUrl: './resume-link-action.component.html'
})
export class ResumeLinkActionComponent {
@Input() server: Server;
@Input() link: Link;
constructor(
private linkService: LinkService
) {}
resumeLink() {
this.link.suspend = false;
this.linkService.updateLink(this.server, this.link).subscribe(() => {});
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="!link.capturing" (click)="startCapture()">
<mat-icon>loupe</mat-icon>
<span>Start capture</span>
</button>

View File

@ -0,0 +1,26 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { MatDialog } from '@angular/material';
import { StartCaptureDialogComponent } from '../../../packet-capturing/start-capture/start-capture.component';
@Component({
selector: 'app-start-capture-action',
templateUrl: './start-capture-action.component.html'
})
export class StartCaptureActionComponent {
@Input() server: Server;
@Input() link: Link;
constructor(private dialog: MatDialog) {}
startCapture() {
const dialogRef = this.dialog.open(StartCaptureDialogComponent, {
width: '400px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.link = this.link;
}
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { NodeService } from '../../../../../services/node.service';
import { Node } from '../../../../../cartography/models/node';
@ -7,7 +7,7 @@ import { Node } from '../../../../../cartography/models/node';
selector: 'app-start-node-action',
templateUrl: './start-node-action.component.html'
})
export class StartNodeActionComponent implements OnInit {
export class StartNodeActionComponent implements OnInit, OnChanges {
@Input() server: Server;
@Input() nodes: Node[];
isNodeWithStoppedStatus: boolean;
@ -15,12 +15,18 @@ export class StartNodeActionComponent implements OnInit {
constructor(private nodeService: NodeService) {}
ngOnInit() {
}
ngOnChanges(changes) {
if(changes.nodes) {
this.isNodeWithStoppedStatus = false;
this.nodes.forEach((node) => {
if (node.status === 'stopped') {
this.isNodeWithStoppedStatus = true;
}
});
}
}
startNodes() {
this.nodes.forEach((node) => {

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="link.capturing" (click)="stopCapture()">
<mat-icon>pause_circle_filled</mat-icon>
<span>Stop capture</span>
</button>

View File

@ -0,0 +1,21 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { LinkService } from '../../../../../services/link.service';
@Component({
selector: 'app-stop-capture-action',
templateUrl: './stop-capture-action.component.html'
})
export class StopCaptureActionComponent {
@Input() server: Server;
@Input() link: Link;
constructor(
private linkService: LinkService
) {}
stopCapture() {
this.linkService.stopCaptureOnLink(this.server, this.link).subscribe(() => {});
}
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { NodeService } from '../../../../../services/node.service';
import { Node } from '../../../../../cartography/models/node';
@ -7,7 +7,7 @@ import { Node } from '../../../../../cartography/models/node';
selector: 'app-stop-node-action',
templateUrl: './stop-node-action.component.html'
})
export class StopNodeActionComponent implements OnInit {
export class StopNodeActionComponent implements OnInit, OnChanges {
@Input() server: Server;
@Input() nodes: Node[];
isNodeWithStartedStatus: boolean;
@ -15,12 +15,18 @@ export class StopNodeActionComponent implements OnInit {
constructor(private nodeService: NodeService) {}
ngOnInit() {
}
ngOnChanges(changes) {
if(changes.nodes) {
this.isNodeWithStartedStatus = false;
this.nodes.forEach((node) => {
if (node.status === 'started') {
this.isNodeWithStartedStatus = true;
}
});
}
}
stopNodes() {
this.nodes.forEach((node) => {

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="!link.suspend" (click)="suspendLink()">
<mat-icon>pause</mat-icon>
<span>Suspend</span>
</button>

View File

@ -0,0 +1,22 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { LinkService } from '../../../../../services/link.service';
@Component({
selector: 'app-suspend-link-action',
templateUrl: './suspend-link-action.component.html'
})
export class SuspendLinkActionComponent {
@Input() server: Server;
@Input() link: Link;
constructor(
private linkService: LinkService
) {}
suspendLink() {
this.link.suspend = true;
this.linkService.updateLink(this.server, this.link).subscribe(() => {});
}
}

View File

@ -32,6 +32,34 @@
[nodes]="nodes"
[drawings]="drawings"
></app-move-layer-down-action>
<app-start-capture-action
*ngIf="!projectService.isReadOnly(project) && isBundledServer
&& drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[link]="links[0]"
></app-start-capture-action>
<app-stop-capture-action
*ngIf="!projectService.isReadOnly(project) && isBundledServer
&& drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[link]="links[0]"
></app-stop-capture-action>
<app-packet-filters-action
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[project]="project"
[link]="links[0]"
></app-packet-filters-action>
<app-resume-link-action
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[link]="links[0]"
></app-resume-link-action>
<app-suspend-link-action
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[link]="links[0]"
></app-suspend-link-action>
<app-delete-action
*ngIf="!projectService.isReadOnly(project)"
[server]="server"

View File

@ -8,6 +8,7 @@ import { MatMenuModule, MatMenuTrigger } from '@angular/material';
import { Drawing } from '../../../cartography/models/drawing';
import { RectElement } from '../../../cartography/models/drawings/rect-element';
import { TextElement } from '../../../cartography/models/drawings/text-element';
import { Server } from '../../../models/server';
describe('ContextMenuComponent', () => {
let component: ContextMenuComponent;
@ -28,6 +29,7 @@ describe('ContextMenuComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ContextMenuComponent);
component = fixture.componentInstance;
component.server = {location: 'local'} as Server;
fixture.detectChanges();
});

View File

@ -33,6 +33,7 @@ export class ContextMenuComponent implements OnInit {
hasTextCapabilities = false;
isElectronApp = false;
isBundledServer: boolean = false;
constructor(
private sanitizer: DomSanitizer,
@ -43,7 +44,9 @@ export class ContextMenuComponent implements OnInit {
ngOnInit() {
this.setPosition(0, 0);
this.isElectronApp = this.electronService.isElectronApp;
this.isBundledServer = this.server.location === 'bundled';
}
public setPosition(top: number, left: number) {

View File

@ -9,6 +9,8 @@ import { MapPort } from '../../../cartography/models/map/map-port';
import { MapLinkCreated } from '../../../cartography/events/links';
import { Link } from '../../../models/link';
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
import { PortToMapPortConverter } from '../../../cartography/converters/map/port-to-map-port-converter';
import { NodeToMapNodeConverter } from '../../../cartography/converters/map/node-to-map-node-converter';
@Component({
selector: 'app-draw-link-tool',
@ -25,7 +27,9 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
private drawingLineTool: DrawingLineWidget,
private nodesEventSource: NodesEventSource,
private linksEventSource: LinksEventSource,
private mapNodeToNode: MapNodeToNodeConverter
private mapNodeToNode: MapNodeToNodeConverter,
private nodeToMapNode: NodeToMapNodeConverter,
private portToMapPort: PortToMapPortConverter
) {}
ngOnInit() {
@ -43,8 +47,8 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
}
public onChooseInterface(event) {
const node: MapNode = event.node;
const port: MapPort = event.port;
const node: MapNode = this.nodeToMapNode.convert(event.node);
const port: MapPort = this.portToMapPort.convert(event.port);
if (this.drawingLineTool.isDrawing()) {
const data = this.drawingLineTool.stop();
this.linksEventSource.created.emit(new MapLinkCreated(data['node'], data['port'], node, port));

View File

@ -0,0 +1,16 @@
<h1 mat-dialog-title>{{title}}</h1>
<div class="modal-form-container">
<div class="message" *ngFor="let message of messages; let i = index">
<h6>
{{message.name}}
</h6>
<span class="description">
{{message.description}}
</span>
</div>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onCloseClick()" color="accent">Close</button>
</div>

View File

@ -0,0 +1,7 @@
.message {
margin-bottom: 10px;
}
.description {
color: #b0bec5;
}

View File

@ -0,0 +1,21 @@
import { Component, Input } from '@angular/core';
import { MatDialogRef } from '@angular/material';
import { Message } from '../../../models/message';
@Component({
selector: 'app-help-dialog',
templateUrl: './help-dialog.component.html',
styleUrls: ['./help-dialog.component.scss']
})
export class HelpDialogComponent {
@Input() title: string;
@Input() messages: Message[];
constructor(
public dialogRef: MatDialogRef<HelpDialogComponent>,
) {}
onCloseClick() {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,32 @@
<button
matTooltip="Start/Resume all nodes"
mat-icon-button
(click)="startNodes()"
class="menu-button"
>
<mat-icon>play_arrow</mat-icon>
</button>
<button
matTooltip="Suspend all nodes"
mat-icon-button
(click)="suspendNodes()"
class="menu-button"
>
<mat-icon>pause</mat-icon>
</button>
<button
matTooltip="Stop all nodes"
mat-icon-button
(click)="stopNodes()"
class="menu-button"
>
<mat-icon>stop</mat-icon>
</button>
<button
matTooltip="Reload all nodes"
mat-icon-button
(click)="reloadNodes()"
class="menu-button"
>
<mat-icon>replay</mat-icon>
</button>

View File

@ -0,0 +1,12 @@
.menu-button {
outline: 0 !important;
transition: 0.5s;
margin-bottom: 16px;
width: 40px;
margin-right: 12px !important;
margin-left: 12px !important;
background: #263238;
padding: 0;
border: none;
background-color: transparent;
}

View File

@ -0,0 +1,74 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NodesMenuComponent } from './nodes-menu.component';
import { MockedToasterService } from '../../../services/toaster.service.spec';
import { MockedNodeService } from '../project-map.component.spec';
import { MatButtonModule, MatIconModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NodeService } from '../../../services/node.service';
import { ToasterService } from '../../../services/toaster.service';
import { of } from 'rxjs';
describe('NodesMenuComponent', () => {
let component: NodesMenuComponent;
let fixture: ComponentFixture<NodesMenuComponent>;
let mockedToasterService: MockedToasterService = new MockedToasterService();
let mockedNodeService: MockedNodeService = new MockedNodeService();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatButtonModule, MatIconModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: NodeService, useValue: mockedNodeService },
{ provide: ToasterService, useValue: mockedToasterService }
],
declarations: [
NodesMenuComponent,
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NodesMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call start all nodes', () => {
spyOn(mockedNodeService, 'startAll').and.returnValue(of());
component.startNodes();
expect(mockedNodeService.startAll).toHaveBeenCalled();
});
it('should call stop all nodes', () => {
spyOn(mockedNodeService, 'stopAll').and.returnValue(of());
component.stopNodes();
expect(mockedNodeService.stopAll).toHaveBeenCalled();
});
it('should call suspend all nodes', () => {
spyOn(mockedNodeService, 'suspendAll').and.returnValue(of());
component.suspendNodes();
expect(mockedNodeService.suspendAll).toHaveBeenCalled();
});
it('should call reload all nodes', () => {
spyOn(mockedNodeService, 'reloadAll').and.returnValue(of());
component.reloadNodes();
expect(mockedNodeService.reloadAll).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,44 @@
import { Component, Input } from "@angular/core";
import { Project } from '../../../models/project';
import { Server } from '../../../models/server';
import { NodeService } from '../../../services/node.service';
import { ToasterService } from '../../../services/toaster.service';
@Component({
selector: 'app-nodes-menu',
templateUrl: './nodes-menu.component.html',
styleUrls: ['./nodes-menu.component.scss']
})
export class NodesMenuComponent {
@Input('project') project: Project;
@Input('server') server: Server;
constructor(
private nodeService: NodeService,
private toasterService: ToasterService
) {}
startNodes() {
this.nodeService.startAll(this.server, this.project).subscribe(() => {
this.toasterService.success('All nodes successfully started');
});
}
stopNodes() {
this.nodeService.stopAll(this.server, this.project).subscribe(() => {
this.toasterService.success('All nodes successfully stopped');
});
}
suspendNodes() {
this.nodeService.suspendAll(this.server, this.project).subscribe(() => {
this.toasterService.success('All nodes successfully suspended');
});
}
reloadNodes() {
this.nodeService.reloadAll(this.server, this.project).subscribe(() => {
this.toasterService.success('All nodes successfully reloaded');
});
}
}

View File

@ -0,0 +1,44 @@
<h1 mat-dialog-title>Packet filters</h1>
<div class="modal-form-container" class="content">
<mat-tab-group *ngIf="this.filters">
<mat-tab label="Frequency drop">
<mat-form-field class="input-field">
<input matInput placeholder="Frequency" type="number" [(ngModel)]="filters.frequency_drop[0]">
</mat-form-field>
</mat-tab>
<mat-tab label="Packet loss">
<mat-form-field class="input-field">
<input matInput placeholder="Chance" type="number" [(ngModel)]="filters.packet_loss[0]">
</mat-form-field>
</mat-tab>
<mat-tab label="Delay">
<mat-form-field class="input-field">
<input matInput placeholder="Latency" type="number" [(ngModel)]="filters.delay[0]">
</mat-form-field>
<mat-form-field class="input-field">
<input matInput placeholder="Jitter" type="number" [(ngModel)]="filters.delay[1]">
</mat-form-field>
</mat-tab>
<mat-tab label="Corrupt">
<mat-form-field class="input-field">
<input matInput placeholder="Latency" type="number" [(ngModel)]="filters.corrupt[0]">
</mat-form-field>
</mat-tab>
<mat-tab label="Berkeley Packet Filter (BPF)">
<mat-form-field class="input-field">
<textarea matInput type="text" [(ngModel)]="filters.bpf[0]"></textarea>
</mat-form-field>
</mat-tab>
</mat-tab-group>
</div>
<div class="bottom-bar">
<div class="spacer"></div>
<div mat-dialog-actions layout="row" class="dialog-actions">
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
<button mat-button (click)="onResetClick()" color="accent">Reset</button>
<button mat-button (click)="onYesClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
<div class="divider"></div>
<button mat-button (click)="onHelpClick()" color="accent">Help</button>
</div>
</div>

View File

@ -0,0 +1,67 @@
.spacer {
flex-grow: 1;
}
.content {
height: 260px;
}
.item {
height: 25px;
font-size: 10pt;
margin-bottom: 10px;
}
.item-name {
margin-bottom: 10px;
}
.item-value {
width: 100%;
margin-bottom: 10px;
}
.input-field {
width: 100%;
margin-top: 10px;
}
.divider {
width: fit-content;
flex: 1 1 auto;
}
.input-color {
padding: 0px;
border-width: 0px;
width: 100%;
background-color: transparent;
outline: none;
}
input:focus {
outline: none;
}
input[type="color"] {
-webkit-appearance: none;
border: none;
height: 25px;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: none;
}
.modal-form-container {
display: flex;
flex-direction: column;
}
.modal-form-container > * {
width: 100%;
}

View File

@ -0,0 +1,63 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { LinkService } from '../../../../services/link.service';
import { MockedLinkService } from '../../project-map.component.spec';
import { Link } from '../../../../models/link';
import { of } from 'rxjs';
import { PacketFiltersDialogComponent } from './packet-filters.component';
describe('PacketFiltersDialogComponent', () => {
let component: PacketFiltersDialogComponent;
let fixture: ComponentFixture<PacketFiltersDialogComponent>;
let mockedLinkService = new MockedLinkService;
let dialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatDialogModule, FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: [] },
{ provide: LinkService, useValue: mockedLinkService }
],
declarations: [
PacketFiltersDialogComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PacketFiltersDialogComponent);
component = fixture.componentInstance;
component.link = {link_type: 'ethernet'} as Link;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call update link when filters applied', () => {
spyOn(mockedLinkService, 'updateLink').and.returnValue(of({}));
component.onYesClick();
expect(mockedLinkService.updateLink).toHaveBeenCalled();
});
it('should call update link after resetting', () => {
spyOn(mockedLinkService, 'updateLink').and.returnValue(of({}));
component.onResetClick();
expect(mockedLinkService.updateLink).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,96 @@
import { Component, OnInit } from '@angular/core';
import { Link } from '../../../../models/link';
import { Server } from '../../../../models/server';
import { Project } from '../../../../models/project';
import { MatDialogRef, MatDialog } from '@angular/material';
import { LinkService } from '../../../../services/link.service';
import { FilterDescription } from '../../../../models/filter-description';
import { HelpDialogComponent } from '../../help-dialog/help-dialog.component';
import { Message } from '../../../../models/message';
import { Filter } from '../../../../models/filter';
@Component({
selector: 'app-packet-filters',
templateUrl: './packet-filters.component.html',
styleUrls: ['./packet-filters.component.scss']
})
export class PacketFiltersDialogComponent implements OnInit{
server: Server;
project: Project;
link: Link;
filters: Filter;
availableFilters: FilterDescription[];
constructor(
private dialogRef: MatDialogRef<PacketFiltersDialogComponent>,
private linkService: LinkService,
private dialog: MatDialog
) {}
ngOnInit(){
this.linkService.getLink(this.server, this.link.project_id, this.link.link_id).subscribe((link: Link) => {
this.link = link;
this.filters = {
bpf: [],
corrupt: [0],
delay: [0, 0],
frequency_drop: [0],
packet_loss: [0]
};
if (this.link.filters) {
this.filters.bpf = this.link.filters.bpf ? this.link.filters.bpf : [];
this.filters.corrupt = this.link.filters.corrupt ? this.link.filters.corrupt : [0];
this.filters.delay = this.link.filters.delay ? this.link.filters.delay : [0, 0];
this.filters.frequency_drop = this.link.filters.frequency_drop ? this.link.filters.frequency_drop : [0];
this.filters.packet_loss = this.link.filters.packet_loss ? this.link.filters.packet_loss : [0];
}
});
this.linkService.getAvailableFilters(this.server, this.link).subscribe((availableFilters: FilterDescription[]) => {
this.availableFilters = availableFilters;
});
}
onNoClick() {
this.dialogRef.close();
}
onResetClick() {
this.link.filters = {
bpf: [],
corrupt: [0],
delay: [0, 0],
frequency_drop: [0],
packet_loss: [0]
};
this.linkService.updateLink(this.server, this.link).subscribe((link: Link) => {
this.dialogRef.close();
});
}
onYesClick() {
this.link.filters = this.filters;
this.linkService.updateLink(this.server, this.link).subscribe((link: Link) => {
this.dialogRef.close();
});
}
onHelpClick() {
const dialogRef = this.dialog.open(HelpDialogComponent, {
width: '500px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.title = 'Help for filters';
let messages: Message[] = [];
this.availableFilters.forEach((filter: FilterDescription) => {
messages.push({
name: filter.name,
description: filter.description
});
});
instance.messages = messages;
}
}

View File

@ -0,0 +1,30 @@
<h1 mat-dialog-title>Packet capture</h1>
<div class="modal-form-container">
<form [formGroup]="inputForm">
<mat-form-field class="input-field">
<mat-select
placeholder="Link type"
formControlName="linkType"
ngDefaultControl>
<mat-option *ngFor="let type of linkTypes" [value]="type[1]">
{{type[0]}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="input-field">
<input
placeholder="File name"
formControlName="fileName"
matInput type="text">
</mat-form-field>
<!-- <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="startProgram">
Start the capture visualization program
</mat-checkbox> -->
</form>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
<button mat-button (click)="onYesClick()" tabindex="2" mat-raised-button color="primary">Ok</button>
</div>

View File

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

View File

@ -0,0 +1,82 @@
import { StartCaptureDialogComponent } from "./start-capture.component";
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ToasterService } from '../../../../services/toaster.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MockedToasterService } from '../../../../services/toaster.service.spec';
import { LinkService } from '../../../../services/link.service';
import { MockedLinkService, MockedNodesDataSource } from '../../project-map.component.spec';
import { Link } from '../../../../models/link';
import { of } from 'rxjs';
import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource';
describe('StartCaptureDialogComponent', () => {
let component: StartCaptureDialogComponent;
let fixture: ComponentFixture<StartCaptureDialogComponent>;
let mockedToasterService = new MockedToasterService;
let mockedLinkService = new MockedLinkService;
let mockedNodesDataSource = new MockedNodesDataSource;
let dialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatDialogModule, FormsModule, ReactiveFormsModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: [] },
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: LinkService, useValue: mockedLinkService },
{ provide: NodesDataSource, useValue: mockedNodesDataSource }
],
declarations: [
StartCaptureDialogComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StartCaptureDialogComponent);
component = fixture.componentInstance;
component.link = {link_type: 'ethernet', nodes: [{node_id: '1'}, {node_id: '2'}]} as Link;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call link service when input is valid', () => {
component.inputForm.controls['linkType'].setValue('Ethernet');
component.inputForm.controls['fileName'].setValue('SampleFileName');
spyOn(mockedLinkService, 'startCaptureOnLink').and.returnValue(of({}));
component.onYesClick();
expect(mockedLinkService.startCaptureOnLink).toHaveBeenCalled();
});
it('should not call link service when link type is not set', () => {
component.inputForm.controls['fileName'].setValue('SampleFileName');
spyOn(mockedLinkService, 'startCaptureOnLink').and.returnValue(of({}));
component.onYesClick();
expect(mockedLinkService.startCaptureOnLink).not.toHaveBeenCalled();
});
it('should not call link service when filename is empty', () => {
component.inputForm.controls['linkType'].setValue('Ethernet');
component.inputForm.controls['fileName'].setValue('');
spyOn(mockedLinkService, 'startCaptureOnLink').and.returnValue(of({}));
component.onYesClick();
expect(mockedLinkService.startCaptureOnLink).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,85 @@
import { Component, OnInit } from '@angular/core';
import { Server } from '../../../../models/server';
import { Link } from '../../../../models/link';
import { MatDialogRef } from '@angular/material';
import { PacketFiltersDialogComponent } from '../packet-filters/packet-filters.component';
import { LinkService } from '../../../../services/link.service';
import { CapturingSettings } from '../../../../models/capturingSettings';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { ToasterService } from '../../../../services/toaster.service';
import { LinkNode } from '../../../../models/link-node';
import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource';
@Component({
selector: 'app-start-capture',
templateUrl: './start-capture.component.html',
styleUrls: ['./start-capture.component.scss']
})
export class StartCaptureDialogComponent implements OnInit {
server: Server;
link: Link;
linkTypes = [];
inputForm: FormGroup;
startProgram: boolean;
constructor(
private dialogRef: MatDialogRef<PacketFiltersDialogComponent>,
private linkService: LinkService,
private formBuilder: FormBuilder,
private toasterService: ToasterService,
private nodesDataSource: NodesDataSource
) {
this.inputForm = this.formBuilder.group({
linkType: new FormControl('', Validators.required),
fileName: new FormControl('', Validators.required)
});
}
ngOnInit() {
if (this.link.link_type === 'ethernet') {
this.linkTypes = [
["Ethernet", "DLT_EN10MB"]
];
} else {
this.linkTypes = [
["Cisco HDLC", "DLT_C_HDLC"],
["Cisco PPP", "DLT_PPP_SERIAL"],
["Frame Relay", "DLT_FRELAY"],
["ATM", "DLT_ATM_RFC1483"]
];
}
const sourceNode = this.nodesDataSource.get(this.link.nodes[0].node_id);
const targetNode = this.nodesDataSource.get(this.link.nodes[1].node_id);
const sourcePort = sourceNode.ports[this.link.nodes[0].port_number];
const targetPort = targetNode.ports[this.link.nodes[1].port_number];
this.inputForm.controls['fileName'].setValue(`${sourceNode.name}_${sourcePort.name}_to_${targetNode.name}_${targetPort.name}`);
}
onYesClick() {
let isAnyRunningDevice = false;
this.link.nodes.forEach((linkNode: LinkNode) => {
let node = this.nodesDataSource.get(linkNode.node_id);
if (node.status === 'started') isAnyRunningDevice = true;
});
if (!isAnyRunningDevice) {
this.toasterService.error(`Cannot capture because there is no running device on this link`);
} else if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
let captureSettings: CapturingSettings = {
capture_file_name: this.inputForm.get('fileName').value,
data_link_type: this.inputForm.get('linkType').value
};
this.linkService.startCaptureOnLink(this.server, this.link, captureSettings).subscribe(() => {
this.dialogRef.close();
});
}
}
onNoClick() {
this.dialogRef.close();
}
}

View File

@ -33,7 +33,7 @@
<div class="project-toolbar">
<mat-toolbar color="primary" class="project-toolbar">
<mat-toolbar-row>
<button mat-icon-button [matMenuTriggerFor]="mainMenu"><mat-icon svgIcon="gns3"></mat-icon></button>
<button matTooltip="Open menu" mat-icon-button [matMenuTriggerFor]="mainMenu"><mat-icon svgIcon="gns3"></mat-icon></button>
</mat-toolbar-row>
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
@ -48,7 +48,7 @@
</mat-menu>
<mat-toolbar-row>
<button mat-icon-button [matMenuTriggerFor]="viewMenu"><mat-icon>view_module</mat-icon></button>
<button matTooltip="Show/hide interface labels" mat-icon-button [matMenuTriggerFor]="viewMenu"><mat-icon>view_module</mat-icon></button>
</mat-toolbar-row>
<mat-menu #viewMenu="matMenu" [overlapTrigger]="false">
@ -60,13 +60,13 @@
</mat-menu>
<mat-toolbar-row *ngIf="!readonly">
<button mat-icon-button [color]="tools.draw_link ? 'primary' : 'basic'" (click)="toggleDrawLineMode()">
<button matTooltip="Add a link" mat-icon-button [color]="tools.draw_link ? 'primary' : 'basic'" (click)="toggleDrawLineMode()">
<mat-icon>timeline</mat-icon>
</button>
</mat-toolbar-row>
<mat-toolbar-row>
<button mat-icon-button [color]="tools.moving ? 'primary' : 'basic'" (click)="toggleMovingMode()">
<button matTooltip="Enable/disable moving mode" mat-icon-button [color]="tools.moving ? 'primary' : 'basic'" (click)="toggleMovingMode()">
<mat-icon>zoom_out_map</mat-icon>
</button>
</mat-toolbar-row>
@ -80,18 +80,20 @@
</mat-toolbar-row>
<mat-toolbar-row *ngIf="!readonly">
<button mat-icon-button routerLink="/server/{{server.id}}/preferences">
<button matTooltip="Go to preferences" mat-icon-button routerLink="/server/{{server.id}}/preferences">
<mat-icon>settings_applications</mat-icon>
</button>
</mat-toolbar-row>
</mat-toolbar>
</div>
<div id="show-menu-wrapper" [ngClass]="{ shadowed: !drawTools.visibility }">
<div id="show-menu-wrapper" [ngClass]="{ shadowed: !drawTools.visibility }" *ngIf="!readonly">
<button class="arrow-button" mat-icon-button (click)="showMenu()"><mat-icon>keyboard_arrow_right</mat-icon></button>
</div>
<div id="menu-wrapper" [ngClass]="{ extended: drawTools.visibility }">
<app-nodes-menu [server]="server" [project]="project"></app-nodes-menu>
<mat-divider class="divider" [vertical]="true"></mat-divider>
<button
matTooltip="Add a note"
mat-icon-button
@ -119,7 +121,7 @@
>
<mat-icon>panorama_fish_eye</mat-icon>
</button>
<button matTooltip="Draw line" mat-icon-button class="menu-button" (click)="addDrawing('line')">
<button matTooltip="Draw a line" mat-icon-button class="menu-button" (click)="addDrawing('line')">
<svg height="40" width="40">
<line
[ngClass]="{ selected: drawTools.isLineChosen }"

View File

@ -53,6 +53,7 @@ g.node:hover {
overflow: hidden;
transition: 0.15s;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
display: flex;
.menu-button {
outline: 0 !important;
@ -75,11 +76,19 @@ g.node:hover {
}
.extended {
width: 296px !important;
width: 570px !important;
height: 100%;
overflow: hidden;
}
mat-divider.divider {
height: 40px;
margin-left: 1px;
margin-right: 7px;
width: 10px;
color: gray;
}
@-moz-document url-prefix() {
/** fixes gray background of drawing menu on Firefox **/
.mat-drawer-content {

View File

@ -39,9 +39,14 @@ import { SelectionTool } from '../../cartography/tools/selection-tool';
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
import { Link } from '../../models/link';
import { Project } from '../../models/project';
import { CapturingSettings } from '../../models/capturingSettings';
import { LinkWidget } from '../../cartography/widgets/link';
export class MockedProgressService {
public activate() {}
public deactivate() {}
}
export class MockedNodeService {
@ -59,6 +64,22 @@ export class MockedNodeService {
delete(server: Server, node: Node) {
return of();
}
startAll(server: Server, project: Project) {
return of();
}
stopAll(server: Server, project: Project) {
return of();
}
suspendAll(server: Server, project: Project) {
return of();
}
reloadAll(server: Server, project: Project) {
return of();
}
}
export class MockedDrawingService {
@ -93,8 +114,16 @@ export class MockedDrawingService {
export class MockedLinkService {
constructor() {}
getLink(server: Server, projectId: string, linkId: string) {
return of({});
}
deleteLink(_server: Server, link: Link){
return of({})
return of({});
}
updateLink(server: Server, link: Link) {
return of({});
}
createLink() {
@ -104,11 +133,21 @@ export class MockedLinkService {
updateNodes() {
return of({});
}
startCaptureOnLink(server: Server, link: Link, settings: CapturingSettings) {
return of({});
}
getAvailableFilters(server: Server, link: Link) {
return of({});
}
}
export class MockedDrawingsDataSource {
add() {}
clear() {}
get() {
return of({});
}
@ -121,8 +160,10 @@ export class MockedDrawingsDataSource {
export class MockedNodesDataSource {
add() {}
clear() {}
get() {
return of({});
return {status: 'started'};
}
update() {
@ -130,11 +171,17 @@ export class MockedNodesDataSource {
}
}
export class MockedLinksDataSource {
clear() {}
}
describe('ProjectMapComponent', () => {
let component: ProjectMapComponent;
let fixture: ComponentFixture<ProjectMapComponent>;
let drawingService = new MockedDrawingService();
let drawingsDataSource = new MockedDrawingsDataSource();
let nodesDataSource = new MockedNodesDataSource();
let linksDataSource = new MockedLinksDataSource();
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -150,13 +197,14 @@ describe('ProjectMapComponent', () => {
{ provide: ProjectWebServiceHandler },
{ provide: MapChangeDetectorRef },
{ provide: NodeWidget },
{ provide: LinkWidget },
{ provide: DrawingsWidget },
{ provide: MapNodeToNodeConverter },
{ provide: MapDrawingToDrawingConverter },
{ provide: MapLabelToLabelConverter },
{ provide: MapLinkToLinkConverter },
{ provide: NodesDataSource },
{ provide: LinksDataSource },
{ provide: NodesDataSource, useValue: nodesDataSource },
{ provide: LinksDataSource, useValue: linksDataSource },
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
{ provide: SettingsService, useClass: MockedSettingsService },
{ provide: ToolsService },
@ -177,6 +225,10 @@ describe('ProjectMapComponent', () => {
component = fixture.componentInstance;
});
afterEach(() => {
component.ngOnDestroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -30,7 +30,7 @@ import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-nod
import { SettingsService, Settings } from '../../services/settings.service';
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
import { ToolsService } from '../../services/tools.service';
import { DrawingContextMenu } from '../../cartography/events/event-source';
import { DrawingContextMenu, LinkContextMenu } from '../../cartography/events/event-source';
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
import { SelectionManager } from '../../cartography/managers/selection-manager';
import { SelectionTool } from '../../cartography/tools/selection-tool';
@ -42,6 +42,7 @@ import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-l
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
import { MapLink } from '../../cartography/models/map/map-link';
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
import { LinkWidget } from '../../cartography/widgets/link';
@Component({
@ -95,6 +96,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private mapChangeDetectorRef: MapChangeDetectorRef,
private nodeWidget: NodeWidget,
private drawingsWidget: DrawingsWidget,
private linkWidget: LinkWidget,
private mapNodeToNode: MapNodeToNodeConverter,
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
private mapLabelToLabel: MapLabelToLabelConverter,
@ -219,6 +221,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.toolsService.selectionToolActivation(true);
}
const onLinkContextMenu = this.linkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
});
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
const node = this.mapNodeToNode.convert(eventNode.node);
this.contextMenu.openMenuForNode(node, eventNode.event.pageY, eventNode.event.pageX);
@ -253,6 +260,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX);
});
this.subscriptions.push(onLinkContextMenu);
this.subscriptions.push(onNodeContextMenu);
this.subscriptions.push(onDrawingContextMenu);
this.subscriptions.push(onContextMenu);

View File

@ -21,11 +21,11 @@
>
</div>
<div>
<!-- <div>
<mat-checkbox [(ngModel)]="settings.angular_map"
>Enable experimental Angular Map (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox
>
</div>
</div> -->
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">

View File

@ -3,9 +3,14 @@
<button mat-button class="top-button" color="accent" (click)="onNoClick()" routerLink="/server/{{server.id}}/project/{{project.project_id}}/snapshots">Go to snapshots</button>
</div>
<div mat-dialog-content>
<form [formGroup]="inputForm">
<mat-form-field class="name-input">
<input matInput tabindex="1" [(ngModel)]="snapshot.name" placeholder="Name" />
<input
matInput tabindex="1"
formControlName="snapshotName"
placeholder="Name" />
</mat-form-field>
</form>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">No Thanks</button>

View File

@ -3,6 +3,11 @@ import { Snapshot } from '../../../models/snapshot';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { Server } from '../../../models/server';
import { Project } from '../../../models/project';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { ToasterService } from '../../../services/toaster.service';
import { SnapshotService } from '../../../services/snapshot.service';
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
import { Node } from '../../../cartography/models/node';
@Component({
@ -14,18 +19,54 @@ export class CreateSnapshotDialogComponent {
server: Server;
project: Project;
snapshot: Snapshot = new Snapshot();
inputForm: FormGroup;
snapshots: string[] = [];
isInRunningState: boolean;
constructor(
public dialogRef: MatDialogRef<CreateSnapshotDialogComponent>,
private formBuilder: FormBuilder,
private toasterService: ToasterService,
private snapshotService: SnapshotService,
private nodesDataSource: NodesDataSource,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.server = data['server'];
this.project = data['project'];
this.inputForm = this.formBuilder.group({
snapshotName: new FormControl('', Validators.required)
});
this.snapshotService.list(this.server, this.project.project_id).subscribe((snapshots: Snapshot[]) => {
snapshots.forEach((snapshot: Snapshot) => {
this.snapshots.push(snapshot.name);
});
});
this.nodesDataSource.getItems().forEach((node: Node) => {
if (node.status !== 'stopped' && !this.isAlwaysRunningNode(node.node_type)) {
this.isInRunningState = true;
}
});
}
isAlwaysRunningNode(nodeType: string) {
return !["qemu", "docker", "dynamips", "vpcs", "vmware", "virtualbox", "iou", "traceng"].includes(nodeType);
}
onAddClick(): void {
if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else if (this.snapshots.includes(this.inputForm.get('snapshotName').value)) {
this.toasterService.error(`Snapshot with this name already exists`);
} else if (this.isInRunningState) {
this.toasterService.error(`Project must be stopped in order to export it`);
} else {
this.snapshot.name = this.inputForm.get('snapshotName').value;
this.dialogRef.close(this.snapshot);
}
}
onNoClick(): void {
this.dialogRef.close();

View File

@ -1 +1 @@
<button mat-icon-button (click)="createSnapshotModal()"><mat-icon>snooze</mat-icon></button>
<button matTooltip="Manage snapshots" mat-icon-button (click)="createSnapshotModal()"><mat-icon>snooze</mat-icon></button>

View File

@ -48,11 +48,6 @@ export class SnapshotMenuItemComponent implements OnInit {
(created_snapshot: Snapshot) => {
this.toaster.success(`Snapshot '${snapshot.name}' has been created.`);
progress.close();
},
response => {
const error = response.json();
this.toaster.error(`Cannot create snapshot: ${error.message}`);
progress.close();
}
);

View File

@ -1 +1 @@
<button mat-icon-button (click)="listTemplatesModal()"><mat-icon>add_to_queue</mat-icon></button>
<button matTooltip="Browse all devices" mat-icon-button (click)="listTemplatesModal()"><mat-icon>add_to_queue</mat-icon></button>

View File

@ -67,6 +67,10 @@ header {
width: 100%;
}
.container {
padding: 0%;
}
.mat-dialog-content > * {
width: 100%;
}

View File

@ -32,6 +32,6 @@
<app-progress></app-progress>
<footer class="footer mat-app-background">
GNS3 Web UI &copy; 2019
GNS3 Web UI &copy; 2019 - v{{ uiVersion }}
</footer>

View File

@ -17,17 +17,20 @@ class ElectronServiceMock {
public isElectronApp: boolean;
}
class MockedServerManagementService {
public serverStatusChanged;
public stopAll() {}
}
describe('DefaultLayoutComponent', () => {
let component: DefaultLayoutComponent;
let fixture: ComponentFixture<DefaultLayoutComponent>;
let electronServiceMock: ElectronServiceMock;
let serverManagementService;
let serverManagementService = new MockedServerManagementService();
beforeEach(async(() => {
electronServiceMock = new ElectronServiceMock();
serverManagementService = {
serverStatusChanged: new Subject<ServerStateEvent>()
};
serverManagementService.serverStatusChanged = new Subject<ServerStateEvent>();
TestBed.configureTestingModule({
declarations: [DefaultLayoutComponent, ProgressComponent],

View File

@ -5,6 +5,8 @@ import { ServerManagementService } from '../../services/server-management.servic
import { Subscription } from 'rxjs';
import { ToasterService } from '../../services/toaster.service';
import { ProgressService } from '../../common/progress/progress.service';
import { version } from './../../version';
@Component({
selector: 'app-default-layout',
@ -14,6 +16,7 @@ import { ProgressService } from '../../common/progress/progress.service';
})
export class DefaultLayoutComponent implements OnInit, OnDestroy {
public isInstalledSoftwareAvailable = false;
public uiVersion = version;
serverStatusSubscription: Subscription;
shouldStopServersOnClosing = true;

View File

@ -19,7 +19,8 @@ import {
MatTooltipModule,
MatStepperModule,
MatRadioModule,
MatGridListModule
MatGridListModule,
MatTabsModule
} from '@angular/material';
export const MATERIAL_IMPORTS = [
@ -43,5 +44,6 @@ export const MATERIAL_IMPORTS = [
MatTooltipModule,
MatStepperModule,
MatRadioModule,
MatGridListModule
MatGridListModule,
MatTabsModule
];

View File

@ -0,0 +1,4 @@
export class CapturingSettings {
capture_file_name: string;
data_link_type: string;
}

View File

@ -0,0 +1,14 @@
export class FilterDescription {
description: string;
name: string;
parameters: Parameter[];
type: string;
}
interface Parameter {
maximum?: number;
minimum?: number;
name: string;
type: string;
unit?: string;
}

Some files were not shown because too many files have changed in this diff Show More