mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-04-24 13:05:48 +00:00
Merge branch 'master' into screenshots
This commit is contained in:
commit
e4d977fe15
@ -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]
|
||||
})
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -5,6 +5,7 @@ export class Drawing {
|
||||
project_id: string;
|
||||
rotation: number;
|
||||
svg: string;
|
||||
locked: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
|
@ -6,6 +6,7 @@ export class MapDrawing implements Indexed {
|
||||
projectId: string;
|
||||
rotation: number;
|
||||
svg: string;
|
||||
locked: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
|
@ -12,6 +12,7 @@ export class MapNode implements Indexed {
|
||||
firstPortName: string;
|
||||
height: number;
|
||||
label: MapLabel;
|
||||
locked: boolean;
|
||||
name: string;
|
||||
nodeDirectory: string;
|
||||
nodeType: string;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -48,6 +48,7 @@ describe('DrawingDraggedComponent', () => {
|
||||
};
|
||||
const mapDrawing: MapDrawing = {
|
||||
id: 'sampleId',
|
||||
locked: false,
|
||||
projectId: 'sampleprojectId',
|
||||
rotation: 0,
|
||||
svg: 'sampleSvg',
|
||||
|
@ -51,6 +51,7 @@ describe('DrawingResizedComponent', () => {
|
||||
};
|
||||
const mapDrawing: MapDrawing = {
|
||||
id: 'sampleId',
|
||||
locked: false,
|
||||
projectId: 'sampleprojectId',
|
||||
rotation: 0,
|
||||
svg: 'sampleSvg',
|
||||
|
@ -72,6 +72,7 @@ describe('LinkCreatedComponent', () => {
|
||||
firstPortName: 'sampleFirstPortName',
|
||||
height: 0,
|
||||
label: {} as MapLabel,
|
||||
locked: false,
|
||||
name: 'sampleName',
|
||||
nodeDirectory: 'sampleNodeDirectory',
|
||||
nodeType: 'sampleNodeType',
|
||||
|
@ -52,6 +52,7 @@ describe('NodeDraggedComponent', () => {
|
||||
firstPortName: 'sampleFirstPortName',
|
||||
height: 0,
|
||||
label: {} as MapLabel,
|
||||
locked: false,
|
||||
name: 'sampleName',
|
||||
nodeDirectory: 'sampleNodeDirectory',
|
||||
nodeType: 'sampleNodeType',
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,4 @@
|
||||
<button mat-menu-item (click)="lock()">
|
||||
<mat-icon>lock</mat-icon>
|
||||
<span>{{command}}</span>
|
||||
</button>
|
@ -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) });
|
||||
});
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -2,3 +2,8 @@
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.textAreaTab {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
@ -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 },
|
||||
|
@ -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.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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([])
|
||||
|
@ -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]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user