Merge pull request #508 from GNS3/winpcap-support

User should have possibility to start capture in WebUI (branch from node configurator to avoid conflicts) & console connect to all nodes
This commit is contained in:
piotrpekala7 2019-09-25 14:23:32 +02:00 committed by GitHub
commit bb11e88f13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 224 additions and 31 deletions

View File

@ -236,6 +236,8 @@ import { TracengService } from './services/traceng.service';
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
import { QemuImageCreatorComponent } from './components/project-map/node-editors/configurator/qemu/qemu-image-creator/qemu-image-creator.component';
import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component';
import { PacketCaptureService } from './services/packet-capture.service';
import { StartCaptureOnStartedLinkActionComponent } from './components/project-map/context-menu/actions/start-capture-on-started-link/start-capture-on-started-link.component';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -398,7 +400,8 @@ if (environment.production) {
TracengTemplatesComponent,
TracengTemplateDetailsComponent,
QemuImageCreatorComponent,
ChooseNameDialogComponent
ChooseNameDialogComponent,
StartCaptureOnStartedLinkActionComponent
],
imports: [
BrowserModule,
@ -478,7 +481,8 @@ if (environment.production) {
MapSettingsService,
InfoService,
ComputeService,
TracengService
TracengService,
PacketCaptureService
],
entryComponents: [
AddServerDialogComponent,

View File

@ -11,6 +11,8 @@ import { SettingsService } from '../../../../../services/settings.service';
import { MockedSettingsService } from '../../../../../services/settings.service.spec';
import { Node } from '../../../../../cartography/models/node';
import { Server } from '../../../../../models/server';
import { MockedNodeService } from '../../../project-map.component.spec';
import { NodeService } from '../../../../../services/node.service';
describe('ConsoleDeviceActionComponent', () => {
@ -20,7 +22,8 @@ describe('ConsoleDeviceActionComponent', () => {
let server: Server;
let mockedSettingsService: MockedSettingsService;
let mockedServerService: MockedServerService;
let mockedToaster: MockedToasterService
let mockedToaster: MockedToasterService;
let mockedNodeService: MockedNodeService = new MockedNodeService();
beforeEach(() => {
electronService = {
@ -47,7 +50,8 @@ describe('ConsoleDeviceActionComponent', () => {
{ provide: ElectronService, useValue: electronService },
{ provide: ServerService, useValue: mockedServerService },
{ provide: SettingsService, useValue: mockedSettingsService },
{ provide: ToasterService, useValue: mockedToaster }
{ provide: ToasterService, useValue: mockedToaster },
{ provide: NodeService, useValue: mockedNodeService }
],
imports: [
MatIconModule

View File

@ -5,6 +5,7 @@ import { ElectronService } from 'ngx-electron';
import { ServerService } from '../../../../../services/server.service';
import { SettingsService } from '../../../../../services/settings.service';
import { ToasterService } from '../../../../../services/toaster.service';
import { NodeService } from '../../../../../services/node.service';
@Component({
selector: 'app-console-device-action',
@ -18,19 +19,14 @@ export class ConsoleDeviceActionComponent implements OnInit {
private electronService: ElectronService,
private serverService: ServerService,
private settingsService: SettingsService,
private toasterService: ToasterService
private toasterService: ToasterService,
private nodeService: NodeService
) { }
ngOnInit() {
}
ngOnInit() {}
async console() {
let consoleCommand = this.settingsService.get<string>('console_command');
if(consoleCommand === undefined) {
consoleCommand = `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
}
let consoleCommand = this.settingsService.get<string>('console_command') ? this.settingsService.get<string>('console_command') : this.nodeService.getDefaultCommand();
const startedNodes = this.nodes.filter(node => node.status === 'started');
if(startedNodes.length === 0) {

View File

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

View File

@ -0,0 +1,24 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { Project } from '../../../../../models/project';
import { PacketCaptureService } from '../../../../../services/packet-capture.service';
@Component({
selector: 'app-start-capture-on-started-link-action',
templateUrl: './start-capture-on-started-link.component.html'
})
export class StartCaptureOnStartedLinkActionComponent {
@Input() server: Server;
@Input() project: Project;
@Input() link: Link;
constructor(
private packetCaptureService: PacketCaptureService
) {}
startCapture() {
var splittedFileName = this.link.capture_file_name.split('.');
this.packetCaptureService.startCapture(this.server, this.project, this.link, splittedFileName[0]);
}
}

View File

@ -3,6 +3,7 @@ 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';
import { Project } from '../../../../../models/project';
@Component({
selector: 'app-start-capture-action',
@ -10,6 +11,7 @@ import { StartCaptureDialogComponent } from '../../../packet-capturing/start-cap
})
export class StartCaptureActionComponent {
@Input() server: Server;
@Input() project: Project;
@Input() link: Link;
constructor(private dialog: MatDialog) {}
@ -21,6 +23,7 @@ export class StartCaptureActionComponent {
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.project = this.project;
instance.link = this.link;
}
}

View File

@ -86,17 +86,25 @@
[drawings]="drawings"
></app-bring-to-front-action>
<app-start-capture-action
*ngIf="!projectService.isReadOnly(project) && isBundledServer
*ngIf="!projectService.isReadOnly(project)
&& drawings.length===0 && nodes.length===0 && links.length===1"
[server]="server"
[project]="project"
[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 && linkNodes.length === 0"
*ngIf="!projectService.isReadOnly(project)
&& drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
[server]="server"
[link]="links[0]"
></app-stop-capture-action>
<app-start-capture-on-started-link-action
*ngIf="!projectService.isReadOnly(project)
&& drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
[server]="server"
[project]="project"
[link]="links[0]"
></app-start-capture-on-started-link-action>
<app-packet-filters-action
*ngIf="!projectService.isReadOnly(project) && drawings.length===0 && nodes.length===0 && links.length===1 && linkNodes.length === 0"
[server]="server"

View File

@ -1,3 +1,11 @@
<button
matTooltip="Console connect to all nodes"
mat-icon-button
(click)="startConsoleForAllNodes()"
class="menu-button"
>
<mat-icon>web_asset</mat-icon>
</button>
<button
matTooltip="Start/Resume all nodes"
mat-icon-button

View File

@ -1,7 +1,7 @@
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 { MockedNodeService, MockedNodesDataSource } from '../project-map.component.spec';
import { MatButtonModule, MatIconModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@ -9,19 +9,28 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NodeService } from '../../../services/node.service';
import { ToasterService } from '../../../services/toaster.service';
import { of } from 'rxjs';
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
import { ServerService } from '../../../services/server.service';
import { SettingsService } from '../../../services/settings.service';
import { ElectronService } from 'ngx-electron';
describe('NodesMenuComponent', () => {
let component: NodesMenuComponent;
let fixture: ComponentFixture<NodesMenuComponent>;
let mockedToasterService: MockedToasterService = new MockedToasterService();
let mockedNodeService: MockedNodeService = new MockedNodeService();
let mockedNodesDataSource: MockedNodesDataSource = new MockedNodesDataSource();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatButtonModule, MatIconModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: NodeService, useValue: mockedNodeService },
{ provide: ToasterService, useValue: mockedToasterService }
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
{ provide: ServerService },
{ provide: SettingsService },
{ provide: ElectronService }
],
declarations: [
NodesMenuComponent,

View File

@ -3,6 +3,10 @@ import { Project } from '../../../models/project';
import { Server } from '../../../models/server';
import { NodeService } from '../../../services/node.service';
import { ToasterService } from '../../../services/toaster.service';
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
import { ElectronService } from 'ngx-electron';
import { SettingsService } from '../../../services/settings.service';
import { ServerService } from '../../../services/server.service';
@Component({
selector: 'app-nodes-menu',
@ -15,9 +19,36 @@ export class NodesMenuComponent {
constructor(
private nodeService: NodeService,
private toasterService: ToasterService
private nodesDataSource: NodesDataSource,
private toasterService: ToasterService,
private serverService: ServerService,
private settingsService: SettingsService,
private electronService: ElectronService
) {}
async startConsoleForAllNodes() {
if (this.electronService.isElectronApp) {
let consoleCommand = this.settingsService.get<string>('console_command') ? this.settingsService.get<string>('console_command') : this.nodeService.getDefaultCommand();
let nodes = this.nodesDataSource.getItems();
for(var node of nodes) {
const request = {
command: consoleCommand,
type: node.console_type,
host: node.console_host,
port: node.console,
name: node.name,
project_id: node.project_id,
node_id: node.node_id,
server_url: this.serverService.getServerUrl(this.server)
};
await this.electronService.remote.require('./console-executor.js').openConsole(request);
}
} else {
this.toasterService.error("Starting all nodes available only in Electron app.");
}
}
startNodes() {
this.nodeService.startAll(this.server, this.project).subscribe(() => {
this.toasterService.success('All nodes successfully started');

View File

@ -18,9 +18,9 @@
formControlName="fileName"
matInput type="text">
</mat-form-field>
<!-- <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="startProgram">
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="startProgram">
Start the capture visualization program
</mat-checkbox> -->
</mat-checkbox>
</form>
</div>

View File

@ -12,6 +12,7 @@ import { MockedLinkService, MockedNodesDataSource } from '../../project-map.comp
import { Link } from '../../../../models/link';
import { of } from 'rxjs';
import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource';
import { PacketCaptureService } from '../../../../services/packet-capture.service';
describe('StartCaptureDialogComponent', () => {
let component: StartCaptureDialogComponent;
@ -32,7 +33,8 @@ describe('StartCaptureDialogComponent', () => {
{ provide: MAT_DIALOG_DATA, useValue: [] },
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: LinkService, useValue: mockedLinkService },
{ provide: NodesDataSource, useValue: mockedNodesDataSource }
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
{ provide: PacketCaptureService }
],
declarations: [
StartCaptureDialogComponent

View File

@ -9,6 +9,8 @@ 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';
import { PacketCaptureService } from '../../../../services/packet-capture.service';
import { Project } from '../../../../models/project';
@Component({
selector: 'app-start-capture',
@ -17,6 +19,7 @@ import { NodesDataSource } from '../../../../cartography/datasources/nodes-datas
})
export class StartCaptureDialogComponent implements OnInit {
server: Server;
project: Project;
link: Link;
linkTypes = [];
inputForm: FormGroup;
@ -27,7 +30,8 @@ export class StartCaptureDialogComponent implements OnInit {
private linkService: LinkService,
private formBuilder: FormBuilder,
private toasterService: ToasterService,
private nodesDataSource: NodesDataSource
private nodesDataSource: NodesDataSource,
private packetCaptureService: PacketCaptureService
) {
this.inputForm = this.formBuilder.group({
linkType: new FormControl('', Validators.required),
@ -73,6 +77,10 @@ export class StartCaptureDialogComponent implements OnInit {
data_link_type: this.inputForm.get('linkType').value
};
if (this.startProgram) {
this.packetCaptureService.startCapture(this.server, this.project, this.link, captureSettings.capture_file_name);
}
this.linkService.startCaptureOnLink(this.server, this.link, captureSettings).subscribe(() => {
this.dialogRef.close();
});

View File

@ -80,7 +80,7 @@ g.node:hover {
}
.extended {
width: 770px !important;
width: 830px !important;
height: 100%;
overflow: hidden;
}

View File

@ -64,6 +64,10 @@ export class MockedProgressService {
export class MockedNodeService {
public node = { label: {} } as Node;
constructor() {}
getDefaultCommand(): string {
return `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
}
updateLabel(): Observable<Node> {
return of(this.node);

View File

@ -9,9 +9,14 @@
<div class="summaryFilters">
Filter by status <br/>
<div class="filterBox">
<mat-checkbox (change)="applyStatusFilter($event.checked, 'started')">Started</mat-checkbox>
<mat-checkbox (change)="applyStatusFilter($event.checked, 'suspended')">Suspended</mat-checkbox>
<mat-checkbox (change)="applyStatusFilter($event.checked, 'stopped')">Stopped</mat-checkbox>
<mat-checkbox (change)="applyStatusFilter($event.checked, 'started')">started</mat-checkbox>
<mat-checkbox (change)="applyStatusFilter($event.checked, 'suspended')">suspended</mat-checkbox>
<mat-checkbox (change)="applyStatusFilter($event.checked, 'stopped')">stopped</mat-checkbox>
</div>
Show devices with <br/>
<div class="filterBox">
<mat-checkbox (change)="applyCaptureFilter($event.checked, 'capture')">active capture(s)</mat-checkbox>
<mat-checkbox (change)="applyCaptureFilter($event.checked, 'packet')">active packet filters</mat-checkbox>
</div>
</div>
<div class="summarySorting">

View File

@ -36,7 +36,7 @@
.summaryContent {
margin-left: 5px;
margin-right: 5px;
max-height: 230px;
max-height: 180px;
overflow: auto;
scrollbar-color: darkgrey #263238;
scrollbar-width: thin;

View File

@ -6,13 +6,14 @@ import { MatTableModule, MatTooltipModule, MatIconModule, MatSortModule, MatDial
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { MockedNodesDataSource } from '../project-map/project-map.component.spec';
import { MockedNodesDataSource, MockedLinksDataSource } from '../project-map/project-map.component.spec';
import { NodesDataSource } from '../../cartography/datasources/nodes-datasource';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Project } from '../../models/project';
import { Node } from '../../cartography/models/node';
import { Server } from '../../models/server';
import { ComputeService } from '../../services/compute.service';
import { LinksDataSource } from '../../cartography/datasources/links-datasource';
export class MockedComputeService {
getComputes(server: Server) {
@ -26,6 +27,7 @@ describe('TopologySummaryComponent', () => {
let mockedProjectService: MockedProjectService = new MockedProjectService();
let mockedNodesDataSource: MockedNodesDataSource = new MockedNodesDataSource();
let mockedComputeService: MockedComputeService = new MockedComputeService();
let mockedLinksDataSource: MockedLinksDataSource = new MockedLinksDataSource();
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -41,7 +43,8 @@ describe('TopologySummaryComponent', () => {
providers: [
{ provide: ProjectService, useValue: mockedProjectService },
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
{ provide: ComputeService, useValue: mockedComputeService}
{ provide: ComputeService, useValue: mockedComputeService},
{ provide: LinksDataSource, useValue: mockedLinksDataSource }
],
declarations: [TopologySummaryComponent],
schemas: [NO_ERRORS_SCHEMA]

View File

@ -8,6 +8,7 @@ import { ProjectService } from '../../services/project.service';
import { ProjectStatistics } from '../../models/project-statistics';
import { Compute } from '../../models/compute';
import { ComputeService } from '../../services/compute.service';
import { LinksDataSource } from '../../cartography/datasources/links-datasource';
@Component({
@ -29,13 +30,16 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
startedStatusFilterEnabled: boolean = false;
suspendedStatusFilterEnabled: boolean = false;
stoppedStatusFilterEnabled: boolean = false;
captureFilterEnabled: boolean = false;
packetFilterEnabled: boolean = false;
computes: Compute[] = [];
isTopologyVisible: boolean = true;
constructor(
private nodesDataSource: NodesDataSource,
private projectService: ProjectService,
private computeService: ComputeService
private computeService: ComputeService,
private linksDataSource: LinksDataSource
) {}
ngOnInit() {
@ -97,6 +101,15 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
this.applyFilters();
}
applyCaptureFilter(value: boolean, filter: string) {
if (filter === 'capture') {
this.captureFilterEnabled = value;
} else if (filter === 'packet') {
this.packetFilterEnabled = value;
}
this.applyFilters();
}
applyFilters() {
let nodes: Node[] = [];
@ -116,6 +129,14 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
nodes = nodes.concat(this.nodes);
}
if (this.captureFilterEnabled) {
nodes = this.checkCapturing(nodes);
}
if(this.packetFilterEnabled) {
nodes = this.checkPacketFilters(nodes);
}
if (this.sortingOrder === 'asc') {
this.filteredNodes = nodes.sort(this.compareAsc);
} else {
@ -123,6 +144,48 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
}
}
checkCapturing(nodes: Node[]): Node[] {
let links = this.linksDataSource.getItems();
let nodesWithCapturing: string[] = [];
links.forEach(link => {
if (link.capturing) {
link.nodes.forEach(node => {
nodesWithCapturing.push(node.node_id);
});
}
});
let filteredNodes: Node[] = [];
nodes.forEach(node => {
if (nodesWithCapturing.includes(node.node_id)) {
filteredNodes.push(node);
}
});
return filteredNodes;
}
checkPacketFilters(nodes: Node[]): Node[] {
let links = this.linksDataSource.getItems();
let nodesWithPacketFilters: string[] = [];
links.forEach(link => {
if (link.filters.bpf || link.filters.corrupt || link.filters.corrupt || link.filters.packet_loss || link.filters.frequency_drop) {
link.nodes.forEach(node => {
nodesWithPacketFilters.push(node.node_id);
});
}
});
let filteredNodes: Node[] = [];
nodes.forEach(node => {
if (nodesWithPacketFilters.includes(node.node_id)) {
filteredNodes.push(node);
}
});
return filteredNodes;
}
close() {
this.closeTopologySummary.emit(false);
}

View File

@ -122,6 +122,10 @@ export class NodeService {
return this.httpServer.get(server, `/projects/${node.project_id}/nodes/${node.node_id}`)
}
getDefaultCommand(): string {
return `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
}
getConfiguration(server: Server, node: Node) {
let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}`

View File

@ -0,0 +1,13 @@
import { Injectable } from "@angular/core";
import { Server } from '../models/server';
import { Project } from '../models/project';
import { Link } from '../models/link';
@Injectable()
export class PacketCaptureService {
constructor() {}
startCapture(server: Server, project: Project, link: Link, name: string) {
location.assign(`gns3+pcap://${server.host}:${server.port}?project_id=${project.project_id}&link_id=${link.link_id}&name=${name}`);
}
}