Merge pull request #516 from GNS3/Ability-to-lock-single-item-on-map

Ability to lock single item on map
This commit is contained in:
piotrpekala7 2019-10-02 15:43:41 +02:00 committed by GitHub
commit 6cf670d5cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 147 additions and 67 deletions

View File

@ -238,6 +238,7 @@ import { QemuImageCreatorComponent } from './components/project-map/node-editors
import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component'; import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component';
import { PacketCaptureService } from './services/packet-capture.service'; 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 { 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';
if (environment.production) { if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -401,7 +402,8 @@ if (environment.production) {
TracengTemplateDetailsComponent, TracengTemplateDetailsComponent,
QemuImageCreatorComponent, QemuImageCreatorComponent,
ChooseNameDialogComponent, ChooseNameDialogComponent,
StartCaptureOnStartedLinkActionComponent StartCaptureOnStartedLinkActionComponent,
LockActionComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
mapNode.firstPortName = node.first_port_name; mapNode.firstPortName = node.first_port_name;
mapNode.height = node.height; mapNode.height = node.height;
mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id }); mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id });
mapNode.locked = node.locked;
mapNode.name = node.name; mapNode.name = node.name;
mapNode.nodeDirectory = node.node_directory; mapNode.nodeDirectory = node.node_directory;
mapNode.nodeType = node.node_type; mapNode.nodeType = node.node_type;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 } 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 OnInit {
@Input() server: Server;
@Input() nodes: Node[];
@Input() drawings: Drawing[];
command: string;
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) {}
ngOnInit() {
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" [server]="server"
[link]="links[0]" [link]="links[0]"
></app-suspend-link-action> ></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 <app-delete-action
*ngIf="!projectService.isReadOnly(project) && (drawings.length>0 || nodes.length>0 || links.length>0) && linkNodes.length === 0" *ngIf="!projectService.isReadOnly(project) && (drawings.length>0 || nodes.length>0 || links.length>0) && linkNodes.length === 0"
[server]="server" [server]="server"

View File

@ -111,6 +111,7 @@ describe('DrawingService', () => {
drawing.z = 30; drawing.z = 30;
drawing.rotation = 0; drawing.rotation = 0;
drawing.svg = '<svg></svg>'; drawing.svg = '<svg></svg>';
drawing.locked = false;
service.update(server, drawing).subscribe(); service.update(server, drawing).subscribe();
@ -121,7 +122,8 @@ describe('DrawingService', () => {
y: 20, y: 20,
z: 30, z: 30,
rotation: 0, 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> { update(server: Server, drawing: Drawing): Observable<Drawing> {
return this.httpServer.put<Drawing>(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, { return this.httpServer.put<Drawing>(server, `/projects/${drawing.project_id}/drawings/${drawing.drawing_id}`, {
locked: drawing.locked,
svg: drawing.svg, svg: drawing.svg,
rotation: drawing.rotation, rotation: drawing.rotation,
x: Math.round(drawing.x), 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}`, { return this.httpServer.put<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}`, {
console_type: node.console_type, console_type: node.console_type,
console_auto_start: node.console_auto_start, console_auto_start: node.console_auto_start,
locked: node.locked,
name: node.name, name: node.name,
properties: node.properties properties: node.properties
}); });