Merge branch 'master' into screenshots

This commit is contained in:
Piotr Pekala 2019-10-08 00:59:52 -07:00
commit e4d977fe15
41 changed files with 389 additions and 122 deletions

View File

@ -238,6 +238,8 @@ import { QemuImageCreatorComponent } from './components/project-map/node-editors
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';
import { LockActionComponent } from './components/project-map/context-menu/actions/lock-action/lock-action.component';
import { NavigationDialogComponent } from './components/projects/navigation-dialog/navigation-dialog.component';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -401,7 +403,9 @@ if (environment.production) {
TracengTemplateDetailsComponent,
QemuImageCreatorComponent,
ChooseNameDialogComponent,
StartCaptureOnStartedLinkActionComponent
StartCaptureOnStartedLinkActionComponent,
LockActionComponent,
NavigationDialogComponent
],
imports: [
BrowserModule,
@ -519,7 +523,8 @@ if (environment.production) {
ConfiguratorDialogNatComponent,
ConfiguratorDialogTracengComponent,
QemuImageCreatorComponent,
ChooseNameDialogComponent
ChooseNameDialogComponent,
NavigationDialogComponent
],
bootstrap: [AppComponent]
})

View File

@ -94,8 +94,10 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
).subscribe((evt: DraggableDrag<any>) => {
if (!this.isMapLocked) {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter(item => item instanceof MapNode);
// update nodes
let mapNodes = selected.filter(item => item instanceof MapNode);
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
selectedNodes.forEach((node: MapNode) => {
node.x += evt.dx;
node.y += evt.dy;
@ -116,52 +118,52 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
});
// update drawings
selected
.filter(item => item instanceof MapDrawing)
.forEach((drawing: MapDrawing) => {
drawing.x += evt.dx;
drawing.y += evt.dy;
this.drawingsWidget.redrawDrawing(svg, drawing);
});
let mapDrawings = selected.filter(item => item instanceof MapDrawing);
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
selectedDrawings.forEach((drawing: MapDrawing) => {
drawing.x += evt.dx;
drawing.y += evt.dy;
this.drawingsWidget.redrawDrawing(svg, drawing);
});
// update labels
selected
.filter(item => item instanceof MapLabel)
.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLabels = selected.filter(item => item instanceof MapLabel);
const selectedLabels = mapLabels.filter((item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLabels.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
const node = this.graphDataManager.getNodes().filter(node => node.id === label.nodeId)[0];
node.label.x += evt.dx;
node.label.y += evt.dy;
this.labelWidget.redrawLabel(svg, label);
});
const node = this.graphDataManager.getNodes().filter(node => node.id === label.nodeId)[0];
node.label.x += evt.dx;
node.label.y += evt.dy;
this.labelWidget.redrawLabel(svg, label);
});
// update interface labels
selected
.filter(item => item instanceof MapLinkNode)
.forEach((interfaceLabel: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === interfaceLabel.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLinkNodes = selected.filter(item => item instanceof MapLinkNode);
const selectedLinkNodes = mapLinkNodes.filter((item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLinkNodes.forEach((interfaceLabel: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === interfaceLabel.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
const link = this.graphDataManager
.getLinks()
.filter(link => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
if (link.nodes[0].id === interfaceLabel.id) {
link.nodes[0].label.x += evt.dx;
link.nodes[0].label.y += evt.dy;
}
if (link.nodes[1].id === interfaceLabel.id) {
link.nodes[1].label.x += evt.dx;
link.nodes[1].label.y += evt.dy;
}
const link = this.graphDataManager
.getLinks()
.filter(link => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
if (link.nodes[0].id === interfaceLabel.id) {
link.nodes[0].label.x += evt.dx;
link.nodes[0].label.y += evt.dy;
}
if (link.nodes[1].id === interfaceLabel.id) {
link.nodes[1].label.x += evt.dx;
link.nodes[1].label.y += evt.dy;
}
this.linksWidget.redrawLink(svg, link);
});
this.linksWidget.redrawLink(svg, link);
});
}
});
@ -173,39 +175,41 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
).subscribe((evt: DraggableEnd<any>) => {
if (!this.isMapLocked) {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter(item => item instanceof MapNode);
let mapNodes = selected.filter(item => item instanceof MapNode);
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
selectedNodes.forEach((item: MapNode) => {
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapDrawing)
.forEach((item: MapDrawing) => {
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
});
let mapDrawings = selected.filter(item => item instanceof MapDrawing);
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
selectedDrawings.forEach((item: MapDrawing) => {
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapLabel)
.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLabels = selected.filter(item => item instanceof MapLabel);
const selectedLabels = mapLabels.filter((item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLabels.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
});
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapLinkNode)
.forEach((label: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
});
}
let mapLinkNodes = selected.filter(item => item instanceof MapLinkNode);
const selectedLinkNodes = mapLinkNodes.filter((item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0)
selectedLinkNodes.forEach((label: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
});
}
});
}

View File

@ -14,6 +14,7 @@ export class DrawingToMapDrawingConverter implements Converter<Drawing, MapDrawi
mapDrawing.projectId = drawing.project_id;
mapDrawing.rotation = drawing.rotation;
mapDrawing.svg = drawing.svg;
mapDrawing.locked = drawing.locked;
mapDrawing.x = drawing.x;
mapDrawing.y = drawing.y;
mapDrawing.z = drawing.z;

View File

@ -14,6 +14,7 @@ export class MapDrawingToDrawingConverter implements Converter<MapDrawing, Drawi
drawing.project_id = mapDrawing.projectId;
drawing.rotation = mapDrawing.rotation;
drawing.svg = mapDrawing.svg;
drawing.locked = mapDrawing.locked;
drawing.x = mapDrawing.x;
drawing.y = mapDrawing.y;
drawing.z = mapDrawing.z;

View File

@ -20,6 +20,7 @@ export class MapNodeToNodeConverter implements Converter<MapNode, Node> {
node.first_port_name = mapNode.firstPortName;
node.height = mapNode.height;
node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined;
node.locked = mapNode.locked;
node.name = mapNode.name;
node.node_directory = mapNode.nodeDirectory;
node.node_type = mapNode.nodeType;

View File

@ -28,13 +28,14 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
mapNode.consoleHost = node.console_host;
mapNode.firstPortName = node.first_port_name;
mapNode.height = node.height;
mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id });
mapNode.label = this.labelToMapLabel ? this.labelToMapLabel.convert(node.label, { node_id: node.node_id }) : undefined;
mapNode.locked = node.locked;
mapNode.name = node.name;
mapNode.nodeDirectory = node.node_directory;
mapNode.nodeType = node.node_type;
mapNode.portNameFormat = node.port_name_format;
mapNode.portSegmentSize = node.port_segment_size;
mapNode.ports = node.ports.map(port => this.portToMapPort.convert(port));
mapNode.ports = node.ports ? node.ports.map(port => this.portToMapPort.convert(port)) : [];
mapNode.projectId = node.project_id;
mapNode.status = node.status;
mapNode.symbol = node.symbol;

View File

@ -34,31 +34,39 @@ export class GraphDataManager {
) {}
public setNodes(nodes: Node[]) {
const mapNodes = nodes.map(n => this.nodeToMapNode.convert(n));
this.mapNodesDataSource.set(mapNodes);
if (nodes) {
const mapNodes = nodes.map(n => this.nodeToMapNode.convert(n));
this.mapNodesDataSource.set(mapNodes);
this.assignDataToLinks();
this.onDataUpdate();
this.assignDataToLinks();
this.onDataUpdate();
}
}
public setLinks(links: Link[]) {
const mapLinks = links.map(l => this.linkToMapLink.convert(l));
this.mapLinksDataSource.set(mapLinks);
if (links) {
const mapLinks = links.map(l => this.linkToMapLink.convert(l));
this.mapLinksDataSource.set(mapLinks);
this.assignDataToLinks();
this.onDataUpdate();
this.assignDataToLinks();
this.onDataUpdate();
}
}
public setDrawings(drawings: Drawing[]) {
const mapDrawings = drawings.map(d => this.drawingToMapDrawing.convert(d));
this.mapDrawingsDataSource.set(mapDrawings);
this.onDataUpdate();
if (drawings) {
const mapDrawings = drawings.map(d => this.drawingToMapDrawing.convert(d));
this.mapDrawingsDataSource.set(mapDrawings);
this.onDataUpdate();
}
}
public setSymbols(symbols: Symbol[]) {
const mapSymbols = symbols.map(s => this.symbolToMapSymbol.convert(s));
this.mapSymbolsDataSource.set(mapSymbols);
if (symbols) {
const mapSymbols = symbols.map(s => this.symbolToMapSymbol.convert(s));
this.mapSymbolsDataSource.set(mapSymbols);
}
}
public getNodes() {

View File

@ -5,6 +5,7 @@ export class Drawing {
project_id: string;
rotation: number;
svg: string;
locked: boolean;
x: number;
y: number;
z: number;

View File

@ -6,6 +6,7 @@ export class MapDrawing implements Indexed {
projectId: string;
rotation: number;
svg: string;
locked: boolean;
x: number;
y: number;
z: number;

View File

@ -12,6 +12,7 @@ export class MapNode implements Indexed {
firstPortName: string;
height: number;
label: MapLabel;
locked: boolean;
name: string;
nodeDirectory: string;
nodeType: string;

View File

@ -69,6 +69,7 @@ export class Node {
first_port_name: string;
height: number;
label: Label;
locked: boolean;
name: string;
node_directory: string;
node_id: string;

View File

@ -16,16 +16,17 @@ export class InterfaceStatusWidget implements Widget {
const link_group = select<SVGGElement, MapLink>(this);
const link_path = link_group.select<SVGPathElement>('path');
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 45);
let statuses = [];
if (link_path.node()) {
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 45);
if (link_path.node().getTotalLength() > 2 * 45 + 10) {
statuses = [
new LinkStatus(start_point.x, start_point.y, l.source.status),
new LinkStatus(end_point.x, end_point.y, l.target.status)
];
if (link_path.node().getTotalLength() > 2 * 45 + 10) {
statuses = [
new LinkStatus(start_point.x, start_point.y, l.source.status),
new LinkStatus(end_point.x, end_point.y, l.target.status)
];
}
}
const status_started = link_group

View File

@ -48,6 +48,7 @@ describe('DrawingDraggedComponent', () => {
};
const mapDrawing: MapDrawing = {
id: 'sampleId',
locked: false,
projectId: 'sampleprojectId',
rotation: 0,
svg: 'sampleSvg',

View File

@ -51,6 +51,7 @@ describe('DrawingResizedComponent', () => {
};
const mapDrawing: MapDrawing = {
id: 'sampleId',
locked: false,
projectId: 'sampleprojectId',
rotation: 0,
svg: 'sampleSvg',

View File

@ -72,6 +72,7 @@ describe('LinkCreatedComponent', () => {
firstPortName: 'sampleFirstPortName',
height: 0,
label: {} as MapLabel,
locked: false,
name: 'sampleName',
nodeDirectory: 'sampleNodeDirectory',
nodeType: 'sampleNodeType',

View File

@ -52,6 +52,7 @@ describe('NodeDraggedComponent', () => {
firstPortName: 'sampleFirstPortName',
height: 0,
label: {} as MapLabel,
locked: false,
name: 'sampleName',
nodeDirectory: 'sampleNodeDirectory',
nodeType: 'sampleNodeType',

View File

@ -1,4 +1,6 @@
<button mat-menu-item (click)="editConfig()">
<button mat-menu-item
*ngIf="node.node_type==='vpcs' || node.node_type==='iou' || node.node_type==='dynamips'"
(click)="editConfig()">
<mat-icon>settings</mat-icon>
<span>Edit config</span>
</button>

View File

@ -1,4 +1,6 @@
<button mat-menu-item (click)="exportConfig()">
<button mat-menu-item
*ngIf="node.node_type==='vpcs' || node.node_type==='dynamips' || node.node_type==='iou'"
(click)="exportConfig()">
<mat-icon>call_made</mat-icon>
<span>Export config</span>
</button>

View File

@ -16,7 +16,7 @@ export class ExportConfigActionComponent {
) {}
exportConfig() {
this.nodeService.getConfiguration(this.server, this.node).subscribe((config: any) => {
this.nodeService.getStartupConfiguration(this.server, this.node).subscribe((config: any) => {
this.downloadByHtmlTag(config);
});
}
@ -25,7 +25,11 @@ export class ExportConfigActionComponent {
const element = document.createElement('a');
const fileType = 'text/plain';
element.setAttribute('href', `data:${fileType};charset=utf-8,${encodeURIComponent(config)}`);
element.setAttribute('download', `${this.node.name}_startup.vpc`);
if (this.node.node_type === 'vpcs') {
element.setAttribute('download', `${this.node.name}_startup.vpc`);
} else if (this.node.node_type === 'iou' || this.node.node_type === 'dynamips') {
element.setAttribute('download', `${this.node.name}_startup.cfg`);
}
var event = new MouseEvent("click");
element.dispatchEvent(event);

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="lock()">
<mat-icon>lock</mat-icon>
<span>{{command}}</span>
</button>

View File

@ -0,0 +1,48 @@
import { Component, OnInit, Input, OnDestroy, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
import { NodeService } from '../../../../../services/node.service';
import { Drawing } from '../../../../../cartography/models/drawing';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { DrawingService } from '../../../../../services/drawing.service';
@Component({
selector: 'app-lock-action',
templateUrl: './lock-action.component.html'
})
export class LockActionComponent implements OnChanges {
@Input() server: Server;
@Input() nodes: Node[];
@Input() drawings: Drawing[];
command: string;
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) {}
ngOnChanges() {
if (this.nodes.length === 1 && this.drawings.length === 0) {
this.command = this.nodes[0].locked ? 'Unlock item' : 'Lock item';
} else if (this.nodes.length === 0 && this.drawings.length === 1) {
this.command = this.drawings[0].locked ? 'Unlock item' : 'Lock item';
} else {
this.command = 'Lock/unlock items';
}
}
lock() {
this.nodes.forEach((node) => {
node.locked = !node.locked;
this.nodeService.updateNode(this.server, node).subscribe((node) => { this.nodesDataSource.update(node) });
});
this.drawings.forEach((drawing) => {
drawing.locked = ! drawing.locked;
this.drawingService.update(this.server, drawing).subscribe((drawing) => { this.drawingsDataSource.update(drawing) });
});
}
}

View File

@ -121,6 +121,12 @@
[server]="server"
[link]="links[0]"
></app-suspend-link-action>
<app-lock-action
*ngIf="!projectService.isReadOnly(project) && (drawings.length>0 || nodes.length>0)"
[server]="server"
[nodes]="nodes"
[drawings]="drawings"
></app-lock-action>
<app-delete-action
*ngIf="!projectService.isReadOnly(project) && (drawings.length>0 || nodes.length>0 || links.length>0) && linkNodes.length === 0"
[server]="server"

View File

@ -1,9 +1,18 @@
<h1 mat-dialog-title>Configuration for node {{node.name}}</h1>
<div class="modal-form-container">
<div *ngIf="node.node_type==='vpcs'" class="modal-form-container">
<textarea #textArea id="textArea" class="textArea" [(ngModel)]="config"></textarea>
</div>
<mat-tab-group *ngIf="node.node_type==='iou' || node.node_type==='dynamips'">
<mat-tab label="Startup config">
<textarea #textArea id="textArea" class="textAreaTab" [(ngModel)]="config"></textarea>
</mat-tab>
<mat-tab label="Private config">
<textarea #textArea id="textArea" class="textAreaTab" [(ngModel)]="privateConfig"></textarea>
</mat-tab>
</mat-tab-group>
<div mat-dialog-actions>
<button mat-button (click)="onCancelClick()" color="accent">Cancel</button>
<button mat-button (click)="onSaveClick()" tabindex="2" mat-raised-button color="primary">Apply</button>

View File

@ -2,3 +2,8 @@
width: 100%;
height: 350px;
}
.textAreaTab {
width: 100%;
height: 300px;
}

View File

@ -5,7 +5,8 @@ import {
MatFormFieldModule,
MatDialogRef,
MAT_DIALOG_DATA,
MatSnackBarModule
MatSnackBarModule,
MatTabsModule
} from '@angular/material';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ToasterService } from '../../../../services/toaster.service';
@ -36,7 +37,8 @@ describe('ConfigEditorDialogComponent', () => {
MatFormFieldModule,
NoopAnimationsModule,
MatSnackBarModule,
FormsModule
FormsModule,
MatTabsModule
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },

View File

@ -17,6 +17,7 @@ export class ConfigEditorDialogComponent implements OnInit {
node: Node;
config: any;
privateConfig: any;
constructor(
public dialogRef: MatDialogRef<ConfigEditorDialogComponent>,
@ -25,15 +26,28 @@ export class ConfigEditorDialogComponent implements OnInit {
) {}
ngOnInit() {
this.nodeService.getConfiguration(this.server, this.node).subscribe((config: any) => {
this.nodeService.getStartupConfiguration(this.server, this.node).subscribe((config: any) => {
this.config = config;
});
if (this.node.node_type === 'iou' || this.node.node_type === 'dynamips') {
this.nodeService.getPrivateConfiguration(this.server, this.node).subscribe((privateConfig: any) => {
this.privateConfig = privateConfig;
});
}
}
onSaveClick() {
this.nodeService.saveConfiguration(this.server, this.node, this.config).subscribe((response) => {
this.dialogRef.close();
this.toasterService.success(`Configuration for node ${this.node.name} saved.`);
if (this.node.node_type === 'iou' || this.node.node_type === 'dynamips') {
this.nodeService.savePrivateConfiguration(this.server, this.node, this.privateConfig).subscribe((resp) => {
this.dialogRef.close();
this.toasterService.success(`Configuration for node ${this.node.name} saved.`);
});
} else {
this.dialogRef.close();
this.toasterService.success(`Configuration for node ${this.node.name} saved.`);
}
});
}

View File

@ -45,7 +45,7 @@ export class NodesMenuComponent {
await this.electronService.remote.require('./console-executor.js').openConsole(request);
}
} else {
this.toasterService.error("Starting all nodes available only in Electron app.");
this.toasterService.error("Option to start all nodes not available in web browser.");
}
}

View File

@ -115,6 +115,12 @@
<mat-icon>settings_applications</mat-icon>
</button>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="!readonly">
<button matTooltip="Center view" mat-icon-button (click)="centerView()">
<mat-icon>center_focus_strong</mat-icon>
</button>
</mat-toolbar-row>
</mat-toolbar>
</div>

View File

@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectMapComponent } from './project-map.component';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule } from '@angular/material';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule, MatBottomSheetModule } from '@angular/material';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ServerService } from '../../services/server.service';
import { ProjectService } from '../../services/project.service';
@ -119,7 +119,7 @@ export class MockedNodeService {
return of(node);
}
getConfiguration(server: Server, node: Node) {
getStartupConfiguration(server: Server, node: Node) {
return of('sample config');
}
@ -254,7 +254,7 @@ describe('ProjectMapComponent', () => {
};
TestBed.configureTestingModule({
imports: [MatIconModule, MatDialogModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
imports: [MatBottomSheetModule, MatIconModule, MatDialogModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: ActivatedRoute },
{ provide: ServerService, useClass: MockedServerService },

View File

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation, ElementRef } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Observable, Subject, Subscription, from } from 'rxjs';
@ -53,7 +53,7 @@ import { MapLinkNodeToLinkNodeConverter } from '../../cartography/converters/map
import { ProjectMapMenuComponent } from './project-map-menu/project-map-menu.component';
import { ToasterService } from '../../services/toaster.service';
import { ImportProjectDialogComponent } from '../projects/import-project-dialog/import-project-dialog.component';
import { MatDialog } from '@angular/material';
import { MatDialog, MatBottomSheet } from '@angular/material';
import { AddBlankProjectDialogComponent } from '../projects/add-blank-project-dialog/add-blank-project-dialog.component';
import { SaveProjectDialogComponent } from '../projects/save-project-dialog/save-project-dialog.component';
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource, Indexed } from '../../cartography/datasources/map-datasource';
@ -61,6 +61,7 @@ import { MapSettingsService } from '../../services/mapsettings.service';
import { EditProjectDialogComponent } from '../projects/edit-project-dialog/edit-project-dialog.component';
import { EthernetLinkWidget } from '../../cartography/widgets/links/ethernet-link';
import { SerialLinkWidget } from '../../cartography/widgets/links/serial-link';
import { NavigationDialogComponent } from '../projects/navigation-dialog/navigation-dialog.component';
@Component({
@ -137,7 +138,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private mapSymbolsDataSource: MapSymbolsDataSource,
private mapSettingsService: MapSettingsService,
private ethernetLinkWidget: EthernetLinkWidget,
private serialLinkWidget: SerialLinkWidget
private serialLinkWidget: SerialLinkWidget,
private bottomSheet: MatBottomSheet
) {}
ngOnInit() {
@ -392,6 +394,17 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
});
}
public centerView() {
if (this.project) {
let scrollX: number = (this.project.scene_width - document.documentElement.clientWidth) > 0 ? (this.project.scene_width - document.documentElement.clientWidth)/2 : 0;
let scrollY: number = (this.project.scene_height - document.documentElement.clientHeight) > 0 ? (this.project.scene_height - document.documentElement.clientHeight)/2 : 0;
window.scrollTo(scrollX, scrollY);
} else {
this.toasterService.error('Please wait until all components are loaded.');
}
}
public onDrawingSaved() {
this.projectMapMenuComponent.resetDrawToolChoice();
}
@ -510,8 +523,16 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(() => {
subscription.unsubscribe();
if (uuid) {
this.projectService.open(this.server, uuid).subscribe(() => {
this.router.navigate(['/server', this.server.id, 'project', uuid]);
this.bottomSheet.open(NavigationDialogComponent);
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
bottomSheetRef.instance.projectMessage = 'imported project';
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
if (result) {
this.projectService.open(this.server, uuid).subscribe(() => {
this.router.navigate(['/server', this.server.id, 'project', uuid]);
});
}
});
}
});

View File

@ -34,6 +34,10 @@
Leave this project running in the background after closing
</mat-checkbox>
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="project.show_interface_labels">
Show interface labels at start
</mat-checkbox>
<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">Apply</button>

View File

@ -0,0 +1,7 @@
<div class="dialogWrapper">
<div class="title"> Do you want to navigate to {{projectMessage}}?</div>
<div>
<button mat-button (click)="onNoClick()">No</button>
<button mat-button (click)="onYesClick()">Yes</button>
</div>
</div>

View File

@ -0,0 +1,17 @@
.dialogWrapper {
background-color: #263238;
padding: 10px 20px;
margin-bottom: -8px;
display: flex;
justify-content: space-between;
align-items: center;
}
mat-bottom-sheet-container {
background: #263238;
}
.title {
margin-right: 10px;
margin-left: 10px;
}

View File

@ -0,0 +1,23 @@
import { Component, OnInit, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA, MatBottomSheetRef } from '@angular/material';
@Component({
selector: 'app-navigation-dialog',
templateUrl: 'navigation-dialog.component.html',
styleUrls: ['navigation-dialog.component.scss']
})
export class NavigationDialogComponent implements OnInit {
projectMessage: string = '';
constructor(private bottomSheetRef: MatBottomSheetRef<NavigationDialogComponent>) {}
ngOnInit() {}
onNoClick(): void {
this.bottomSheetRef.dismiss(false);
}
onYesClick(): void {
this.bottomSheetRef.dismiss(true);
}
}

View File

@ -1,5 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatDialogRef, MatDialogContainer } from '@angular/material';
import { MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatDialogRef, MatDialogContainer, MatBottomSheetModule } from '@angular/material';
import { RouterTestingModule } from '@angular/router/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@ -44,6 +44,7 @@ describe('ProjectsComponent', () => {
NoopAnimationsModule,
MatFormFieldModule,
MatInputModule,
MatBottomSheetModule,
FormsModule,
ReactiveFormsModule,
RouterTestingModule.withRoutes([])

View File

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { MatSort, MatSortable, MatDialog } from '@angular/material';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { MatSort, MatSortable, MatDialog, MatBottomSheet } from '@angular/material';
import { DataSource } from '@angular/cdk/collections';
@ -17,6 +17,7 @@ import { ProgressService } from '../../common/progress/progress.service';
import { ImportProjectDialogComponent } from './import-project-dialog/import-project-dialog.component';
import { AddBlankProjectDialogComponent } from './add-blank-project-dialog/add-blank-project-dialog.component';
import { ChooseNameDialogComponent } from './choose-name-dialog/choose-name-dialog.component';
import { NavigationDialogComponent } from './navigation-dialog/navigation-dialog.component';
@Component({
selector: 'app-projects',
@ -40,7 +41,9 @@ export class ProjectsComponent implements OnInit {
private projectService: ProjectService,
private settingsService: SettingsService,
private progressService: ProgressService,
public dialog: MatDialog
public dialog: MatDialog,
private router: Router,
private bottomSheet: MatBottomSheet
) {}
ngOnInit() {
@ -133,15 +136,33 @@ export class ProjectsComponent implements OnInit {
}
importProject() {
let uuid: string = '';
const dialogRef = this.dialog.open(ImportProjectDialogComponent, {
width: '400px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
const subscription = dialogRef.componentInstance.onImportProject.subscribe((projectId: string) => {
uuid = projectId;
});
dialogRef.afterClosed().subscribe(() => {
this.refresh();
subscription.unsubscribe();
if (uuid) {
this.bottomSheet.open(NavigationDialogComponent);
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
bottomSheetRef.instance.projectMessage = 'imported project';
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
if (result) {
this.projectService.open(this.server, uuid).subscribe(() => {
this.router.navigate(['/server', this.server.id, 'project', uuid]);
});
}
});
}
});
}
}

View File

@ -21,7 +21,8 @@ import {
MatRadioModule,
MatGridListModule,
MatTabsModule,
MatTreeModule
MatTreeModule,
MatBottomSheetModule
} from '@angular/material';
export const MATERIAL_IMPORTS = [
@ -47,5 +48,6 @@ export const MATERIAL_IMPORTS = [
MatRadioModule,
MatGridListModule,
MatTabsModule,
MatTreeModule
MatTreeModule,
MatBottomSheetModule
];

View File

@ -111,6 +111,7 @@ describe('DrawingService', () => {
drawing.z = 30;
drawing.rotation = 0;
drawing.svg = '<svg></svg>';
drawing.locked = false;
service.update(server, drawing).subscribe();
@ -121,7 +122,8 @@ describe('DrawingService', () => {
y: 20,
z: 30,
rotation: 0,
svg: '<svg></svg>'
svg: '<svg></svg>',
locked: false
});
}));

View File

@ -55,6 +55,7 @@ export class DrawingService {
update(server: Server, drawing: Drawing): Observable<Drawing> {
return this.httpServer.put<Drawing>(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {
locked: drawing.locked,
svg: drawing.svg,
rotation: drawing.rotation,
x: Math.round(drawing.x),

View File

@ -90,6 +90,7 @@ export class NodeService {
return this.httpServer.put<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}`, {
console_type: node.console_type,
console_auto_start: node.console_auto_start,
locked: node.locked,
name: node.name,
properties: node.properties
});
@ -126,13 +127,30 @@ export class NodeService {
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}`
getStartupConfiguration(server: Server, node: Node) {
let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}`;
if (node.node_type === 'vpcs') {
urlPath += '/files/startup.vpc';
return this.httpServer.get(server, urlPath, { responseType: 'text' as 'json'});
} else if (node.node_type === 'iou') {
urlPath += '/files/startup-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.node_id}_startup-config.cfg`;
}
return this.httpServer.get(server, urlPath, { responseType: 'text' as 'json'});
}
getPrivateConfiguration(server: Server, node: Node) {
let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}`;
if (node.node_type === 'iou') {
urlPath += '/files/private-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.node_id}_private-config.cfg`;
}
return this.httpServer.get(server, urlPath, { responseType: 'text' as 'json'});
}
saveConfiguration(server: Server, node: Node, configuration: string) {
@ -140,7 +158,24 @@ export class NodeService {
if (node.node_type === 'vpcs') {
urlPath += '/files/startup.vpc';
return this.httpServer.post(server, urlPath, configuration);
} else if (node.node_type === 'iou') {
urlPath += '/files/startup-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.node_id}_startup-config.cfg`;
}
return this.httpServer.post(server, urlPath, configuration);
}
savePrivateConfiguration(server: Server, node: Node, configuration: string) {
let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}`
if (node.node_type === 'iou') {
urlPath += '/files/private-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.node_id}_private-config.cfg`;
}
return this.httpServer.post(server, urlPath, configuration);
}
}

View File

@ -54,7 +54,8 @@ export class ProjectService {
grid_size: project.grid_size,
name: project.name,
scene_width: project.scene_width,
scene_height: project.scene_height
scene_height: project.scene_height,
show_interface_labels: project.show_interface_labels
});
}