mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-04-08 11:04:15 +00:00
Merge pull request #90 from GNS3/selection
Select nodes on topology, Ref. #16
This commit is contained in:
commit
b033b7b9b3
@ -36,10 +36,10 @@ jobs:
|
||||
brew --config
|
||||
brew upgrade python
|
||||
python -V
|
||||
pip install -r scripts/requirements.txt
|
||||
python scripts/build.py download
|
||||
python scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||
python scripts/build.py validate -b dist
|
||||
pip3 install -r scripts/requirements.txt
|
||||
python3 scripts/build.py download
|
||||
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||
python3 scripts/build.py validate -b dist
|
||||
|
||||
- run:
|
||||
name: Dist project
|
||||
|
@ -53,6 +53,12 @@ import { ApplianceListDialogComponent } from './appliance/appliance-list-dialog/
|
||||
import { NodeSelectInterfaceComponent } from './shared/node-select-interface/node-select-interface.component';
|
||||
import { CartographyModule } from './cartography/cartography.module';
|
||||
import { ToasterService } from './shared/services/toaster.service';
|
||||
import {ProjectWebServiceHandler} from "./shared/handlers/project-web-service-handler";
|
||||
import {LinksDataSource} from "./cartography/shared/datasources/links-datasource";
|
||||
import {NodesDataSource} from "./cartography/shared/datasources/nodes-datasource";
|
||||
import {SymbolsDataSource} from "./cartography/shared/datasources/symbols-datasource";
|
||||
import {SelectionManager} from "./cartography/shared/managers/selection-manager";
|
||||
import {InRectangleHelper} from "./cartography/map/helpers/in-rectangle-helper";
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -108,7 +114,13 @@ import { ToasterService } from './shared/services/toaster.service';
|
||||
HttpServer,
|
||||
SnapshotService,
|
||||
ProgressDialogService,
|
||||
ToasterService
|
||||
ToasterService,
|
||||
ProjectWebServiceHandler,
|
||||
LinksDataSource,
|
||||
NodesDataSource,
|
||||
SymbolsDataSource,
|
||||
SelectionManager,
|
||||
InRectangleHelper
|
||||
],
|
||||
entryComponents: [
|
||||
AddServerDialogComponent,
|
||||
|
@ -68,9 +68,11 @@ export class ApplianceDatabase {
|
||||
}
|
||||
|
||||
constructor(private server: Server, private applianceService: ApplianceService) {
|
||||
this.applianceService.list(this.server).subscribe((appliances: Appliance[]) => {
|
||||
this.dataChange.next(appliances);
|
||||
});
|
||||
this.applianceService
|
||||
.list(this.server)
|
||||
.subscribe((appliances) => {
|
||||
this.dataChange.next(appliances);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import {MatDialog} from "@angular/material";
|
||||
import {ApplianceListDialogComponent} from "./appliance-list-dialog/appliance-list-dialog.component";
|
||||
|
||||
import {Server} from "../shared/models/server";
|
||||
import {Appliance} from "../shared/models/appliance";
|
||||
|
||||
@Component({
|
||||
selector: 'app-appliance',
|
||||
@ -26,7 +27,7 @@ export class ApplianceComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((appliance: AppendMode) => {
|
||||
dialogRef.afterClosed().subscribe((appliance: Appliance) => {
|
||||
if (appliance !== null) {
|
||||
this.onNodeCreation.emit(appliance);
|
||||
}
|
||||
|
11
src/app/cartography/map/helpers/in-rectangle-helper.ts
Normal file
11
src/app/cartography/map/helpers/in-rectangle-helper.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {Selectable} from "../../shared/managers/selection-manager";
|
||||
import {Rectangle} from "../../shared/models/rectangle";
|
||||
import {Injectable} from "@angular/core";
|
||||
|
||||
@Injectable()
|
||||
export class InRectangleHelper {
|
||||
public inRectangle(item: Selectable, rectangle: Rectangle): boolean {
|
||||
return (rectangle.x <= item.x && item.x < (rectangle.x + rectangle.width)
|
||||
&& rectangle.y <= item.y && item.y < (rectangle.y + rectangle.height));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import {Link} from "../../shared/models/link.model";
|
||||
import {Link} from "../../shared/models/link";
|
||||
|
||||
export class MultiLinkCalculatorHelper {
|
||||
LINK_WIDTH = 2;
|
||||
|
@ -2,7 +2,4 @@ svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
image.over {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,13 @@ import {
|
||||
import { D3, D3Service } from 'd3-ng2-service';
|
||||
import {select, Selection} from 'd3-selection';
|
||||
|
||||
import { Node } from "../shared/models/node.model";
|
||||
import { Link } from "../shared/models/link.model";
|
||||
import { Node } from "../shared/models/node";
|
||||
import { Link } from "../shared/models/link";
|
||||
import { GraphLayout } from "../shared/widgets/graph.widget";
|
||||
import { Context } from "../../map/models/context";
|
||||
import { Size } from "../shared/models/size.model";
|
||||
import { Drawing } from "../shared/models/drawing.model";
|
||||
import {Symbol} from "../../shared/models/symbol";
|
||||
import { Context } from "../shared/models/context";
|
||||
import { Size } from "../shared/models/size";
|
||||
import { Drawing } from "../shared/models/drawing";
|
||||
import {Symbol} from "../shared/models/symbol";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -68,9 +68,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.svg.empty && !this.svg.empty()) {
|
||||
this.svg.selectAll('*').remove();
|
||||
}
|
||||
this.graphLayout.disconnect(this.svg);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -78,14 +76,12 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
let rootElement: Selection<HTMLElement, any, null, undefined>;
|
||||
|
||||
const self = this;
|
||||
|
||||
if (this.parentNativeElement !== null) {
|
||||
rootElement = d3.select(this.parentNativeElement);
|
||||
|
||||
this.svg = rootElement.select<SVGSVGElement>('svg');
|
||||
|
||||
this.graphContext = new Context(this.svg);
|
||||
this.graphContext = new Context(true);
|
||||
|
||||
if (this.windowFullSize) {
|
||||
this.graphContext.setSize(this.getSize());
|
||||
@ -94,6 +90,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
this.graphLayout = new GraphLayout();
|
||||
this.graphLayout.connect(this.svg, this.graphContext);
|
||||
|
||||
this.graphLayout.getNodesWidget().addOnNodeDraggingCallback((event: any, n: Node) => {
|
||||
const linksWidget = this.graphLayout.getLinksWidget();
|
||||
@ -131,8 +128,6 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
.attr('height', this.graphContext.getSize().height);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.graphLayout.setNodes(this.nodes);
|
||||
this.graphLayout.setLinks(this.links);
|
||||
this.graphLayout.setDrawings(this.drawings);
|
||||
@ -155,6 +150,11 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (target_id in nodes_by_id) {
|
||||
link.target = nodes_by_id[target_id];
|
||||
}
|
||||
|
||||
if (link.source && link.target) {
|
||||
link.x = link.source.x + (link.target.x - link.source.x) * 0.5;
|
||||
link.y = link.source.y + (link.target.y - link.source.y) * 0.5;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
73
src/app/cartography/shared/datasources/datasource.spec.ts
Normal file
73
src/app/cartography/shared/datasources/datasource.spec.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {DataSource} from "./datasource";
|
||||
|
||||
class Item {
|
||||
constructor(public id: string, public property1?: string, public property2?: string) {}
|
||||
}
|
||||
|
||||
|
||||
class TestDataSource extends DataSource<Item> {
|
||||
protected findIndex(item: Item) {
|
||||
return this.data.findIndex((i: Item) => i.id === item.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
describe('TestDataSource', () => {
|
||||
let dataSource: TestDataSource;
|
||||
let data: Item[];
|
||||
|
||||
beforeEach(() => {
|
||||
dataSource = new TestDataSource();
|
||||
dataSource.connect().subscribe((updated: Item[]) => {
|
||||
data = updated;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Item can be added', () => {
|
||||
beforeEach(() => {
|
||||
dataSource.add(new Item("test1", "property1"));
|
||||
});
|
||||
|
||||
it('item should be in data', () => {
|
||||
expect(data).toEqual([new Item("test1", "property1")]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Items can be set', () => {
|
||||
beforeEach(() => {
|
||||
dataSource.set([new Item("test1", "property1"), new Item("test2", "property2")]);
|
||||
});
|
||||
|
||||
it('items should be in data', () => {
|
||||
expect(data).toEqual([new Item("test1", "property1"), new Item("test2", "property2")]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Items can be removed', () => {
|
||||
beforeEach(() => {
|
||||
dataSource.set([new Item("test1", "property1"), new Item("test2", "property2")]);
|
||||
dataSource.remove(new Item("test1", "property1"));
|
||||
});
|
||||
|
||||
it('item should not be in data', () => {
|
||||
expect(data).toEqual([new Item("test2", "property2")]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Item can be updated', () => {
|
||||
beforeEach(() => {
|
||||
dataSource.set([new Item("test1", "property1", "another"), new Item("test2", "property2")]);
|
||||
dataSource.update(new Item("test1", "property3"));
|
||||
});
|
||||
|
||||
it('item should be updated', () => {
|
||||
expect(data).toEqual([
|
||||
new Item("test1", "property3"),
|
||||
new Item("test2", "property2")
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
42
src/app/cartography/shared/datasources/datasource.ts
Normal file
42
src/app/cartography/shared/datasources/datasource.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {BehaviorSubject} from "rxjs/BehaviorSubject";
|
||||
|
||||
export abstract class DataSource<T> {
|
||||
protected data: T[] = [];
|
||||
protected dataChange: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);
|
||||
|
||||
public getItems(): T[] {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public add(item: T) {
|
||||
this.data.push(item);
|
||||
this.dataChange.next(this.data);
|
||||
}
|
||||
|
||||
public set(data: T[]) {
|
||||
this.data = data;
|
||||
this.dataChange.next(this.data);
|
||||
}
|
||||
|
||||
public update(item: T) {
|
||||
const index = this.findIndex(item);
|
||||
if (index >= 0) {
|
||||
this.data[index] = Object.assign(this.data[index], item);
|
||||
this.dataChange.next(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
public remove(item: T) {
|
||||
const index = this.findIndex(item);
|
||||
if (index >= 0) {
|
||||
this.data.splice(index, 1);
|
||||
this.dataChange.next(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
public connect() {
|
||||
return this.dataChange;
|
||||
}
|
||||
|
||||
protected abstract findIndex(item: T): number;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import {LinksDataSource} from "./links-datasource";
|
||||
import {Link} from "../models/link";
|
||||
|
||||
|
||||
describe('LinksDataSource', () => {
|
||||
let dataSource: LinksDataSource;
|
||||
let data: Link[];
|
||||
|
||||
beforeEach(() => {
|
||||
dataSource = new LinksDataSource();
|
||||
dataSource.connect().subscribe((links: Link[]) => {
|
||||
data = links;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Link can be updated', () => {
|
||||
beforeEach(() => {
|
||||
const link = new Link();
|
||||
link.link_id = "1";
|
||||
link.project_id = "project-1";
|
||||
dataSource.add(link);
|
||||
|
||||
link.project_id = "project-2";
|
||||
dataSource.update(link);
|
||||
});
|
||||
|
||||
it('project_id should change', () => {
|
||||
expect(data[0].link_id).toEqual("1");
|
||||
expect(data[0].project_id).toEqual("project-2");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
12
src/app/cartography/shared/datasources/links-datasource.ts
Normal file
12
src/app/cartography/shared/datasources/links-datasource.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
|
||||
import {DataSource} from "./datasource";
|
||||
import {Link} from "../models/link";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class LinksDataSource extends DataSource<Link> {
|
||||
protected findIndex(link: Link) {
|
||||
return this.data.findIndex((l: Link) => l.link_id === link.link_id);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import {NodesDataSource} from "./nodes-datasource";
|
||||
import {Node} from "../models/node";
|
||||
|
||||
|
||||
describe('NodesDataSource', () => {
|
||||
let dataSource: NodesDataSource;
|
||||
let data: Node[];
|
||||
|
||||
beforeEach(() => {
|
||||
dataSource = new NodesDataSource();
|
||||
dataSource.connect().subscribe((nodes: Node[]) => {
|
||||
data = nodes;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Node can be updated', () => {
|
||||
beforeEach(() => {
|
||||
const node = new Node();
|
||||
node.node_id = "1";
|
||||
node.name = "Node 1";
|
||||
dataSource.add(node);
|
||||
|
||||
node.name = "Node 2";
|
||||
dataSource.update(node);
|
||||
});
|
||||
|
||||
it('name should change', () => {
|
||||
expect(data[0].node_id).toEqual("1");
|
||||
expect(data[0].name).toEqual("Node 2");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
11
src/app/cartography/shared/datasources/nodes-datasource.ts
Normal file
11
src/app/cartography/shared/datasources/nodes-datasource.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {Node} from "../models/node";
|
||||
import {DataSource} from "./datasource";
|
||||
import {Injectable} from "@angular/core";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class NodesDataSource extends DataSource<Node> {
|
||||
protected findIndex(node: Node) {
|
||||
return this.data.findIndex((n: Node) => n.node_id === node.node_id);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import {SymbolsDataSource} from "./symbols-datasource";
|
||||
import {Symbol} from "../models/symbol";
|
||||
|
||||
|
||||
describe('SymbolsDataSource', () => {
|
||||
let dataSource: SymbolsDataSource;
|
||||
let data: Symbol[];
|
||||
|
||||
beforeEach(() => {
|
||||
dataSource = new SymbolsDataSource();
|
||||
dataSource.connect().subscribe((symbols: Symbol[]) => {
|
||||
data = symbols;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Symbol can be updated', () => {
|
||||
beforeEach(() => {
|
||||
const symbol = new Symbol();
|
||||
symbol.symbol_id = "1";
|
||||
symbol.filename = "test-1";
|
||||
dataSource.add(symbol);
|
||||
|
||||
symbol.filename = "test-2";
|
||||
dataSource.update(symbol);
|
||||
});
|
||||
|
||||
it('filename should change', () => {
|
||||
expect(data[0].symbol_id).toEqual("1");
|
||||
expect(data[0].filename).toEqual("test-2");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
12
src/app/cartography/shared/datasources/symbols-datasource.ts
Normal file
12
src/app/cartography/shared/datasources/symbols-datasource.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {Node} from "../models/node";
|
||||
import {DataSource} from "./datasource";
|
||||
import {Injectable} from "@angular/core";
|
||||
import {Symbol} from "../models/symbol";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class SymbolsDataSource extends DataSource<Symbol> {
|
||||
protected findIndex(symbol: Symbol) {
|
||||
return this.data.findIndex((s: Symbol) => s.symbol_id === symbol.symbol_id);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import { Subject} from "rxjs/Subject";
|
||||
|
||||
import { Node } from "../models/node";
|
||||
import { Link } from "../models/link";
|
||||
import { Rectangle } from "../models/rectangle";
|
||||
import { SelectionManager } from "./selection-manager";
|
||||
import { NodesDataSource } from "../datasources/nodes-datasource";
|
||||
import { LinksDataSource } from "../datasources/links-datasource";
|
||||
import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper";
|
||||
|
||||
|
||||
describe('SelectionManager', () => {
|
||||
let manager: SelectionManager;
|
||||
let selectedRectangleSubject: Subject<Rectangle>;
|
||||
let nodesDataSource: NodesDataSource;
|
||||
|
||||
beforeEach(() => {
|
||||
const linksDataSource = new LinksDataSource();
|
||||
const inRectangleHelper = new InRectangleHelper();
|
||||
|
||||
selectedRectangleSubject = new Subject<Rectangle>();
|
||||
|
||||
nodesDataSource = new NodesDataSource();
|
||||
|
||||
manager = new SelectionManager(nodesDataSource, linksDataSource, inRectangleHelper);
|
||||
manager.subscribe(selectedRectangleSubject);
|
||||
|
||||
const node_1 = new Node();
|
||||
node_1.node_id = "test1";
|
||||
node_1.name = "Node 1";
|
||||
node_1.x = 150;
|
||||
node_1.y = 150;
|
||||
|
||||
nodesDataSource.add(node_1);
|
||||
|
||||
const node_2 = new Node();
|
||||
node_2.node_id = "test2";
|
||||
node_2.name = "Node 2";
|
||||
node_2.x = 300;
|
||||
node_2.y = 300;
|
||||
nodesDataSource.add(node_2);
|
||||
|
||||
const link_1 = new Link();
|
||||
link_1.link_id = "test1";
|
||||
linksDataSource.add(link_1);
|
||||
});
|
||||
|
||||
it('node should be selected', () => {
|
||||
selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100));
|
||||
expect(nodesDataSource.getItems()[0].is_selected).toEqual(true);
|
||||
expect(manager.getSelectedNodes().length).toEqual(1);
|
||||
expect(manager.getSelectedLinks().length).toEqual(0);
|
||||
});
|
||||
|
||||
it('node should be selected and deselected', () => {
|
||||
selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100));
|
||||
selectedRectangleSubject.next(new Rectangle(350, 350, 100, 100));
|
||||
expect(nodesDataSource.getItems()[0].is_selected).toEqual(false);
|
||||
expect(manager.getSelectedNodes().length).toEqual(0);
|
||||
expect(manager.getSelectedLinks().length).toEqual(0);
|
||||
});
|
||||
|
||||
it('nodes should be manually selected', () => {
|
||||
const node = new Node();
|
||||
node.node_id = "test1";
|
||||
manager.setSelectedNodes([node]);
|
||||
expect(manager.getSelectedNodes().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('links should be manually selected', () => {
|
||||
const link = new Link();
|
||||
link.link_id = "test1";
|
||||
manager.setSelectedLinks([link]);
|
||||
expect(manager.getSelectedLinks().length).toEqual(1);
|
||||
});
|
||||
});
|
84
src/app/cartography/shared/managers/selection-manager.ts
Normal file
84
src/app/cartography/shared/managers/selection-manager.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { Subject } from "rxjs/Subject";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
|
||||
import { NodesDataSource } from "../datasources/nodes-datasource";
|
||||
import { LinksDataSource } from "../datasources/links-datasource";
|
||||
import { Node } from "../models/node";
|
||||
import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper";
|
||||
import { Rectangle } from "../models/rectangle";
|
||||
import { Link} from "../models/link";
|
||||
import { DataSource } from "../datasources/datasource";
|
||||
|
||||
|
||||
export interface Selectable {
|
||||
x: number;
|
||||
y: number;
|
||||
is_selected: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SelectionManager {
|
||||
private selectedNodes: Node[] = [];
|
||||
private selectedLinks: Link[] = [];
|
||||
private subscription: Subscription;
|
||||
|
||||
constructor(private nodesDataSource: NodesDataSource,
|
||||
private linksDataSource: LinksDataSource,
|
||||
private inRectangleHelper: InRectangleHelper) {}
|
||||
|
||||
|
||||
public subscribe(subject: Subject<Rectangle>) {
|
||||
this.subscription = subject.subscribe((rectangle: Rectangle) => {
|
||||
this.selectedNodes = this.getSelectedItemsInRectangle<Node>(this.nodesDataSource, rectangle);
|
||||
this.selectedLinks = this.getSelectedItemsInRectangle<Link>(this.linksDataSource, rectangle);
|
||||
});
|
||||
}
|
||||
|
||||
public getSelectedNodes() {
|
||||
return this.selectedNodes;
|
||||
}
|
||||
|
||||
public getSelectedLinks() {
|
||||
return this.selectedLinks;
|
||||
}
|
||||
|
||||
public setSelectedNodes(nodes: Node[]) {
|
||||
this.selectedNodes = this.setSelectedItems<Node>(this.nodesDataSource, (node: Node) => {
|
||||
return !!nodes.find((n: Node) => node.node_id === n.node_id);
|
||||
});
|
||||
}
|
||||
|
||||
public setSelectedLinks(links: Link[]) {
|
||||
this.selectedLinks = this.setSelectedItems<Link>(this.linksDataSource, (link: Link) => {
|
||||
return !!links.find((l: Link) => link.link_id === l.link_id);
|
||||
});
|
||||
}
|
||||
|
||||
private getSelectedItemsInRectangle<T extends Selectable>(dataSource: DataSource<T>, rectangle: Rectangle) {
|
||||
return this.setSelectedItems<T>(dataSource, (item: T) => {
|
||||
return this.inRectangleHelper.inRectangle(item, rectangle);
|
||||
});
|
||||
}
|
||||
|
||||
private setSelected<T extends Selectable>(item: T, isSelected: boolean, dataSource: DataSource<T>): boolean {
|
||||
if (item.is_selected !== isSelected) {
|
||||
item.is_selected = isSelected;
|
||||
dataSource.update(item);
|
||||
}
|
||||
return item.is_selected;
|
||||
}
|
||||
|
||||
private setSelectedItems<T extends Selectable>(dataSource: DataSource<T>, discriminator: (item: T) => boolean) {
|
||||
const selected: T[] = [];
|
||||
dataSource.getItems().forEach((item: T) => {
|
||||
const isSelected = discriminator(item);
|
||||
this.setSelected<T>(item, isSelected, dataSource);
|
||||
if (isSelected) {
|
||||
selected.push(item);
|
||||
}
|
||||
});
|
||||
return selected;
|
||||
}
|
||||
}
|
26
src/app/cartography/shared/models/context.ts
Normal file
26
src/app/cartography/shared/models/context.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {Size} from "./size";
|
||||
import {Point} from "./point";
|
||||
|
||||
|
||||
export class Context {
|
||||
private size: Size;
|
||||
|
||||
constructor(private centerZeroZeroPoint = false) {
|
||||
this.size = new Size(0, 0);
|
||||
}
|
||||
|
||||
public getSize(): Size {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public setSize(size: Size): void {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public getZeroZeroTransformationPoint() {
|
||||
if (this.centerZeroZeroPoint) {
|
||||
return new Point(this.getSize().width / 2., this.getSize().height / 2.);
|
||||
}
|
||||
return new Point(0, 0);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import {Point} from "./point.model";
|
||||
import {Point} from "./point";
|
||||
|
||||
export class DrawingLine {
|
||||
start: Point;
|
@ -1,6 +1,7 @@
|
||||
import {Node} from "./node.model";
|
||||
import {Node} from "./node";
|
||||
import {Selectable} from "../managers/selection-manager";
|
||||
|
||||
export class Link {
|
||||
export class Link implements Selectable {
|
||||
capture_file_name: string;
|
||||
capture_file_path: string;
|
||||
capturing: boolean;
|
||||
@ -12,4 +13,8 @@ export class Link {
|
||||
length: number; // this is not from server
|
||||
source: Node; // this is not from server
|
||||
target: Node; // this is not from server
|
||||
|
||||
is_selected = false;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import {Label} from "./label.model";
|
||||
import {Label} from "./label";
|
||||
import {Port} from "../../../shared/models/port";
|
||||
import {Selectable} from "../managers/selection-manager";
|
||||
|
||||
export class Node {
|
||||
export class Node implements Selectable {
|
||||
command_line: string;
|
||||
compute_id: string;
|
||||
console: number;
|
||||
@ -24,4 +25,5 @@ export class Node {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
is_selected = false;
|
||||
}
|
8
src/app/cartography/shared/models/rectangle.ts
Normal file
8
src/app/cartography/shared/models/rectangle.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export class Rectangle {
|
||||
constructor(
|
||||
public x?: number,
|
||||
public y?: number,
|
||||
public width?: number,
|
||||
public height?: number
|
||||
) {}
|
||||
}
|
7
src/app/cartography/shared/tool.ts
Normal file
7
src/app/cartography/shared/tool.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {SVGSelection} from "./models/types";
|
||||
|
||||
export interface Tool {
|
||||
connect(selection: SVGSelection);
|
||||
activate();
|
||||
deactivate();
|
||||
}
|
75
src/app/cartography/shared/tools/moving-tool.spec.ts
Normal file
75
src/app/cartography/shared/tools/moving-tool.spec.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { select } from "d3-selection";
|
||||
import { Context } from "../models/context";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { MovingTool } from "./moving-tool";
|
||||
|
||||
|
||||
describe('MovingTool', () => {
|
||||
let tool: MovingTool;
|
||||
let svg: SVGSelection;
|
||||
let context: Context;
|
||||
let node: SVGSelection;
|
||||
let canvas: SVGSelection;
|
||||
|
||||
beforeEach(() => {
|
||||
tool = new MovingTool();
|
||||
|
||||
svg = select('body')
|
||||
.append<SVGSVGElement>('svg')
|
||||
.attr('width', 1000)
|
||||
.attr('height', 1000);
|
||||
|
||||
canvas = svg.append<SVGGElement>('g').attr('class', 'canvas');
|
||||
|
||||
node = canvas
|
||||
.append<SVGGElement>('g')
|
||||
.attr('class', 'node')
|
||||
.attr('x', 10)
|
||||
.attr('y', 20);
|
||||
|
||||
|
||||
context = new Context();
|
||||
|
||||
tool.connect(svg, context);
|
||||
tool.draw(svg, context);
|
||||
tool.activate();
|
||||
|
||||
});
|
||||
|
||||
describe('MovingTool can move canvas', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(
|
||||
new MouseEvent('mousedown', {
|
||||
clientX: 100, clientY: 100, relatedTarget: svg.node(),
|
||||
screenY: 1024, screenX: 1024, view: window
|
||||
})
|
||||
);
|
||||
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 200, clientY: 200}));
|
||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 200, clientY: 200, view: window}));
|
||||
});
|
||||
|
||||
it('canvas should transformed', () => {
|
||||
expect(canvas.attr('transform')).toEqual('translate(100, 100) scale(1)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MovingTool can be deactivated', () => {
|
||||
beforeEach(() => {
|
||||
tool.deactivate();
|
||||
|
||||
svg.node().dispatchEvent(
|
||||
new MouseEvent('mousedown', {
|
||||
clientX: 100, clientY: 100, relatedTarget: svg.node(),
|
||||
screenY: 1024, screenX: 1024, view: window
|
||||
})
|
||||
);
|
||||
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 200, clientY: 200}));
|
||||
});
|
||||
|
||||
it('canvas cannot be transformed', () => {
|
||||
expect(canvas.attr('transform')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
49
src/app/cartography/shared/tools/moving-tool.ts
Normal file
49
src/app/cartography/shared/tools/moving-tool.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {SVGSelection} from "../models/types";
|
||||
import {Context} from "../models/context";
|
||||
import {D3ZoomEvent, zoom, ZoomBehavior} from "d3-zoom";
|
||||
import { event } from "d3-selection";
|
||||
|
||||
export class MovingTool {
|
||||
private selection: SVGSelection;
|
||||
private context: Context;
|
||||
private zoom: ZoomBehavior<SVGSVGElement, any>;
|
||||
|
||||
constructor() {
|
||||
this.zoom = zoom<SVGSVGElement, any>()
|
||||
.scaleExtent([1 / 2, 8]);
|
||||
}
|
||||
|
||||
public connect(selection: SVGSelection, context: Context) {
|
||||
this.selection = selection;
|
||||
this.context = context;
|
||||
|
||||
}
|
||||
|
||||
public draw(selection: SVGSelection, context: Context) {
|
||||
this.selection = selection;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public activate() {
|
||||
const self = this;
|
||||
|
||||
const onZoom = function(this: SVGSVGElement) {
|
||||
|
||||
const canvas = self.selection.select<SVGGElement>("g.canvas");
|
||||
const e: D3ZoomEvent<SVGSVGElement, any> = event;
|
||||
canvas.attr(
|
||||
'transform',
|
||||
`translate(${self.context.getSize().width / 2 + e.transform.x}, ` +
|
||||
`${self.context.getSize().height / 2 + e.transform.y}) scale(${e.transform.k})`);
|
||||
};
|
||||
|
||||
this.zoom.on('zoom', onZoom);
|
||||
this.selection.call(this.zoom);
|
||||
}
|
||||
|
||||
public deactivate() {
|
||||
// d3.js preserves event `mousedown.zoom` and blocks selection
|
||||
this.selection.on('mousedown.zoom', null);
|
||||
this.zoom.on('zoom', null);
|
||||
}
|
||||
}
|
120
src/app/cartography/shared/tools/selection-tool.spec.ts
Normal file
120
src/app/cartography/shared/tools/selection-tool.spec.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { select } from "d3-selection";
|
||||
|
||||
import { SelectionTool } from "./selection-tool";
|
||||
import { Context } from "../models/context";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { Rectangle } from "../models/rectangle";
|
||||
|
||||
|
||||
describe('SelectionTool', () => {
|
||||
let tool: SelectionTool;
|
||||
let svg: SVGSelection;
|
||||
let context: Context;
|
||||
let selection_line_tool: SVGSelection;
|
||||
let path_selection: SVGSelection;
|
||||
let selected_rectangle: Rectangle;
|
||||
|
||||
beforeEach(() => {
|
||||
tool = new SelectionTool();
|
||||
|
||||
tool.rectangleSelected.subscribe((rectangle: Rectangle) => {
|
||||
selected_rectangle = rectangle;
|
||||
});
|
||||
|
||||
svg = select('body')
|
||||
.append<SVGSVGElement>('svg')
|
||||
.attr('width', 1000)
|
||||
.attr('height', 1000);
|
||||
|
||||
svg.append<SVGGElement>('g').attr('class', 'canvas');
|
||||
|
||||
context = new Context();
|
||||
|
||||
tool.connect(svg, context);
|
||||
tool.draw(svg, context);
|
||||
tool.activate();
|
||||
|
||||
selection_line_tool = svg.select('g.selection-line-tool');
|
||||
path_selection = selection_line_tool.select('path.selection');
|
||||
});
|
||||
|
||||
it('creates selection-line-tool container with path', () => {
|
||||
expect(selection_line_tool.node()).not.toBeNull();
|
||||
expect(selection_line_tool.select('path')).not.toBeNull();
|
||||
expect(path_selection.attr('visibility')).toEqual('hidden');
|
||||
});
|
||||
|
||||
describe('SelectionTool can handle start of selection', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||
});
|
||||
|
||||
it('path should be visible and have parameters', () => {
|
||||
expect(path_selection.attr('visibility')).toEqual('visible');
|
||||
expect(path_selection.attr('d')).toEqual('M95,86 l0,0 l0,0 l0,0z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SelectionTool can handle move of selection', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
||||
});
|
||||
|
||||
it('path should have got changed parameters', () => {
|
||||
expect(path_selection.attr('d')).toEqual('M95,86 l200,0 l0,200 l-200,0z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SelectionTool can handle end of selection', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 200, clientY: 200}));
|
||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 200, clientY: 200}));
|
||||
});
|
||||
|
||||
it('path should be hidden', () => {
|
||||
expect(path_selection.attr('visibility')).toEqual('hidden');
|
||||
});
|
||||
|
||||
it('rectangle should be selected', () => {
|
||||
expect(selected_rectangle).toEqual(new Rectangle(95, 86, 100, 100));
|
||||
});
|
||||
|
||||
describe('SelectionTool can deselect after click outside', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 300, clientY: 300}));
|
||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300}));
|
||||
});
|
||||
|
||||
it('rectangle should be selected', () => {
|
||||
expect(selected_rectangle).toEqual(new Rectangle(295, 286, 0, 0));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SelectionTool can handle end of selection in reverse direction', () => {
|
||||
beforeEach(() => {
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 200, clientY: 200}));
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 100, clientY: 100}));
|
||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 100, clientY: 100}));
|
||||
});
|
||||
|
||||
it('rectangle should be selected', () => {
|
||||
expect(selected_rectangle).toEqual(new Rectangle(95, 86, 100, 100));
|
||||
});
|
||||
});
|
||||
|
||||
describe('SelectionTool can be deactivated', () => {
|
||||
beforeEach(() => {
|
||||
tool.deactivate();
|
||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||
});
|
||||
|
||||
it('path should be still hiden', () => {
|
||||
expect(path_selection.attr('visibility')).toEqual('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
112
src/app/cartography/shared/tools/selection-tool.ts
Normal file
112
src/app/cartography/shared/tools/selection-tool.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { mouse, select } from "d3-selection";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { Context } from "../models/context";
|
||||
import { Rectangle } from "../models/rectangle";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class SelectionTool {
|
||||
static readonly SELECTABLE_CLASS = '.selectable';
|
||||
|
||||
public rectangleSelected: Subject<Rectangle>;
|
||||
|
||||
private selection: SVGSelection;
|
||||
private path;
|
||||
private context: Context;
|
||||
|
||||
public constructor() {
|
||||
this.rectangleSelected = new Subject<Rectangle>();
|
||||
}
|
||||
|
||||
public connect(selection: SVGSelection, context: Context) {
|
||||
this.selection = selection;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public activate() {
|
||||
const self = this;
|
||||
|
||||
|
||||
this.selection.on("mousedown", function() {
|
||||
const subject = select(window);
|
||||
const parent = this.parentElement;
|
||||
|
||||
const start = self.transformation(mouse(parent));
|
||||
self.startSelection(start);
|
||||
|
||||
// clear selection
|
||||
self.selection
|
||||
.selectAll(SelectionTool.SELECTABLE_CLASS)
|
||||
.classed("selected", false);
|
||||
|
||||
subject
|
||||
.on("mousemove.selection", function() {
|
||||
const end = self.transformation(mouse(parent));
|
||||
self.moveSelection(start, end);
|
||||
}).on("mouseup.selection", function() {
|
||||
const end = self.transformation(mouse(parent));
|
||||
self.endSelection(start, end);
|
||||
subject
|
||||
.on("mousemove.selection", null)
|
||||
.on("mouseup.selection", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deactivate() {
|
||||
this.selection.on('mousedown', null);
|
||||
}
|
||||
|
||||
public draw(selection: SVGSelection, context: Context) {
|
||||
const canvas = selection.select<SVGGElement>("g.canvas");
|
||||
|
||||
if (!canvas.select<SVGGElement>("g.selection-line-tool").node()) {
|
||||
const g = canvas.append<SVGGElement>('g');
|
||||
g.attr("class", "selection-line-tool");
|
||||
|
||||
this.path = g.append("path");
|
||||
this.path
|
||||
.attr("class", "selection")
|
||||
.attr("visibility", "hidden");
|
||||
}
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
private startSelection(start) {
|
||||
this.path
|
||||
.attr("d", this.rect(start[0], start[1], 0, 0))
|
||||
.attr("visibility", "visible");
|
||||
}
|
||||
|
||||
private moveSelection(start, move) {
|
||||
this.path.attr("d", this.rect(start[0], start[1], move[0] - start[0], move[1] - start[1]));
|
||||
this.selectedEvent(start, move);
|
||||
}
|
||||
|
||||
private endSelection(start, end) {
|
||||
this.path.attr("visibility", "hidden");
|
||||
this.selectedEvent(start, end);
|
||||
}
|
||||
|
||||
private selectedEvent(start, end) {
|
||||
const x = Math.min(start[0], end[0]);
|
||||
const y = Math.min(start[1], end[1]);
|
||||
const width = Math.abs(start[0] - end[0]);
|
||||
const height = Math.abs(start[1] - end[1]);
|
||||
this.rectangleSelected.next(new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
private rect(x: number, y: number, w: number, h: number) {
|
||||
return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z";
|
||||
}
|
||||
|
||||
private transformation(point) {
|
||||
const transformation_point = this.context.getZeroZeroTransformationPoint();
|
||||
return [point[0] - transformation_point.x, point[1] - transformation_point.y];
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import {DrawingLine} from "../models/drawing-line.model";
|
||||
import {SVGSelection} from "../../../map/models/types";
|
||||
import {Point} from "../models/point.model";
|
||||
import {DrawingLine} from "../models/drawing-line";
|
||||
import {SVGSelection} from "../models/types";
|
||||
import {Point} from "../models/point";
|
||||
import {line} from "d3-shape";
|
||||
import {event, mouse, select} from "d3-selection";
|
||||
import {mouse} from "d3-selection";
|
||||
import {Context} from "../models/context";
|
||||
|
||||
export class DrawingLineWidget {
|
||||
private drawingLine: DrawingLine = new DrawingLine();
|
||||
@ -23,11 +24,11 @@ export class DrawingLineWidget {
|
||||
const coordinates = mouse(node);
|
||||
self.drawingLine.end.x = coordinates[0];
|
||||
self.drawingLine.end.y = coordinates[1];
|
||||
self.draw();
|
||||
self.draw(null, null);
|
||||
};
|
||||
|
||||
this.selection.on('mousemove', over);
|
||||
this.draw();
|
||||
this.draw(null, null);
|
||||
}
|
||||
|
||||
public isDrawing() {
|
||||
@ -37,19 +38,20 @@ export class DrawingLineWidget {
|
||||
public stop() {
|
||||
this.drawing = false;
|
||||
this.selection.on('mousemove', null);
|
||||
this.draw();
|
||||
this.draw(null, null);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public connect(selection: SVGSelection) {
|
||||
public connect(selection: SVGSelection, context: Context) {
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
public draw(selection: SVGSelection, context: Context) {
|
||||
const canvas = this.selection.select<SVGGElement>("g.canvas");
|
||||
if (!canvas.select<SVGGElement>("g.drawing-line-tool").node()) {
|
||||
canvas.append<SVGGElement>('g').attr("class", "drawing-line-tool");
|
||||
}
|
||||
}
|
||||
|
||||
public draw() {
|
||||
let link_data = [];
|
||||
|
||||
if (this.drawing) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Widget} from "./widget";
|
||||
import {Drawing} from "../models/drawing.model";
|
||||
import {SVGSelection} from "../../../map/models/types";
|
||||
import {Drawing} from "../models/drawing";
|
||||
import {SVGSelection} from "../models/types";
|
||||
|
||||
|
||||
export class DrawingsWidget implements Widget {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {Widget} from "./widget";
|
||||
import {SVGSelection} from "../../../map/models/types";
|
||||
import {SVGSelection} from "../models/types";
|
||||
|
||||
import { line } from "d3-shape";
|
||||
import {Link} from "../models/link.model";
|
||||
import {Link} from "../models/link";
|
||||
|
||||
export class EthernetLinkWidget implements Widget {
|
||||
|
||||
@ -15,6 +15,7 @@ export class EthernetLinkWidget implements Widget {
|
||||
const value_line = line();
|
||||
|
||||
let link_path = view.select<SVGPathElement>('path');
|
||||
link_path.classed('selected', (l: Link) => l.is_selected);
|
||||
|
||||
if (!link_path.node()) {
|
||||
link_path = view.append<SVGPathElement>('path');
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Context } from "../../../map/models/context";
|
||||
import { Node } from "../models/node.model";
|
||||
import { Link } from "../models/link.model";
|
||||
import { Context } from "../models/context";
|
||||
import { Node } from "../models/node";
|
||||
import { Link } from "../models/link";
|
||||
import { NodesWidget } from "./nodes.widget";
|
||||
import { Widget } from "./widget";
|
||||
import { SVGSelection } from "../../../map/models/types";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { LinksWidget } from "./links.widget";
|
||||
import { D3ZoomEvent, zoom } from "d3-zoom";
|
||||
import { event } from "d3-selection";
|
||||
import { Drawing } from "../models/drawing.model";
|
||||
import { Drawing } from "../models/drawing";
|
||||
import { DrawingsWidget } from "./drawings.widget";
|
||||
import { DrawingLineWidget } from "./drawing-line.widget";
|
||||
import {SelectionTool} from "../tools/selection-tool";
|
||||
import {MovingTool} from "../tools/moving-tool";
|
||||
|
||||
export class GraphLayout implements Widget {
|
||||
private nodes: Node[] = [];
|
||||
@ -20,6 +20,8 @@ export class GraphLayout implements Widget {
|
||||
private nodesWidget: NodesWidget;
|
||||
private drawingsWidget: DrawingsWidget;
|
||||
private drawingLineTool: DrawingLineWidget;
|
||||
private selectionTool: SelectionTool;
|
||||
private movingTool: MovingTool;
|
||||
|
||||
private centerZeroZeroPoint = true;
|
||||
|
||||
@ -28,6 +30,8 @@ export class GraphLayout implements Widget {
|
||||
this.nodesWidget = new NodesWidget();
|
||||
this.drawingsWidget = new DrawingsWidget();
|
||||
this.drawingLineTool = new DrawingLineWidget();
|
||||
this.selectionTool = new SelectionTool();
|
||||
this.movingTool = new MovingTool();
|
||||
}
|
||||
|
||||
public setNodes(nodes: Node[]) {
|
||||
@ -54,9 +58,23 @@ export class GraphLayout implements Widget {
|
||||
return this.drawingLineTool;
|
||||
}
|
||||
|
||||
draw(view: SVGSelection, context: Context) {
|
||||
const self = this;
|
||||
public getMovingTool() {
|
||||
return this.movingTool;
|
||||
}
|
||||
|
||||
public getSelectionTool() {
|
||||
return this.selectionTool;
|
||||
}
|
||||
|
||||
connect(view: SVGSelection, context: Context) {
|
||||
this.drawingLineTool.connect(view, context);
|
||||
this.selectionTool.connect(view, context);
|
||||
this.movingTool.connect(view, context);
|
||||
|
||||
this.selectionTool.activate();
|
||||
}
|
||||
|
||||
draw(view: SVGSelection, context: Context) {
|
||||
const canvas = view
|
||||
.selectAll<SVGGElement, Context>('g.canvas')
|
||||
.data([context]);
|
||||
@ -71,29 +89,18 @@ export class GraphLayout implements Widget {
|
||||
(ctx: Context) => `translate(${ctx.getSize().width / 2}, ${ctx.getSize().height / 2})`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.linksWidget.draw(canvas, this.links);
|
||||
this.nodesWidget.draw(canvas, this.nodes);
|
||||
this.drawingsWidget.draw(canvas, this.drawings);
|
||||
|
||||
this.drawingLineTool.connect(view);
|
||||
|
||||
const onZoom = function(this: SVGSVGElement) {
|
||||
const e: D3ZoomEvent<SVGSVGElement, any> = event;
|
||||
if (self.centerZeroZeroPoint) {
|
||||
canvas.attr(
|
||||
'transform',
|
||||
`translate(${context.getSize().width / 2 + e.transform.x}, ` +
|
||||
`${context.getSize().height / 2 + e.transform.y}) scale(${e.transform.k})`);
|
||||
} else {
|
||||
canvas.attr('transform', e.transform.toString());
|
||||
}
|
||||
};
|
||||
|
||||
view.call(zoom<SVGSVGElement, any>()
|
||||
.scaleExtent([1 / 2, 8])
|
||||
.on('zoom', onZoom));
|
||||
this.drawingLineTool.draw(view, context);
|
||||
this.selectionTool.draw(view, context);
|
||||
this.movingTool.draw(view, context);
|
||||
}
|
||||
|
||||
disconnect(view: SVGSelection) {
|
||||
if (view.empty && !view.empty()) {
|
||||
view.selectAll('*').remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {BaseType, select, Selection} from "d3-selection";
|
||||
|
||||
import { Widget } from "./widget";
|
||||
import { SVGSelection } from "../../../map/models/types";
|
||||
import { Link } from "../models/link.model";
|
||||
import { LinkStatus } from "../models/link-status.model";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { Link } from "../models/link";
|
||||
import { LinkStatus } from "../models/link-status";
|
||||
import { MultiLinkCalculatorHelper } from "../../map/helpers/multi-link-calculator-helper";
|
||||
import {SerialLinkWidget} from "./serial-link.widget";
|
||||
import {EthernetLinkWidget} from "./ethernet-link.widget";
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Widget } from "./widget";
|
||||
import { Node } from "../models/node.model";
|
||||
import { SVGSelection } from "../../../map/models/types";
|
||||
import { Node } from "../models/node";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import {event, select} from "d3-selection";
|
||||
import {D3DragEvent, drag} from "d3-drag";
|
||||
import {Symbol} from "../../../shared/models/symbol";
|
||||
import {Symbol} from "../models/symbol";
|
||||
|
||||
|
||||
export class NodesWidget implements Widget {
|
||||
@ -136,6 +136,7 @@ export class NodesWidget implements Widget {
|
||||
|
||||
const node_merge = node
|
||||
.merge(node_enter)
|
||||
.classed('selected', (n: Node) => n.is_selected)
|
||||
.on("contextmenu", function (n: Node, i: number) {
|
||||
event.preventDefault();
|
||||
if (self.onContextMenuCallback !== null) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Widget} from "./widget";
|
||||
import {SVGSelection} from "../../../map/models/types";
|
||||
import {Link} from "../models/link.model";
|
||||
import {SVGSelection} from "../models/types";
|
||||
import {Link} from "../models/link";
|
||||
import { path } from "d3-path";
|
||||
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
import {Size} from "../../cartography/shared/models/size.model";
|
||||
import {Selection} from "d3-selection";
|
||||
|
||||
export class Context {
|
||||
private size: Size;
|
||||
private root: Selection<SVGSVGElement, any, null, undefined>;
|
||||
|
||||
constructor(root: Selection<SVGSVGElement, any, null, undefined>) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public getSize(): Size {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public setSize(size: Size): void {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
@ -43,6 +43,37 @@ g.node text {
|
||||
}
|
||||
|
||||
|
||||
svg image:hover, svg image.chosen {
|
||||
svg image:hover, svg image.chosen, g.selected {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
path.selected {
|
||||
stroke: darkred;
|
||||
}
|
||||
|
||||
.selection-line-tool .selection {
|
||||
fill: #7ccbe1;
|
||||
stroke: #66aec2 ;
|
||||
fill-opacity: 0.3;
|
||||
stroke-opacity: 0.7;
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 5, 5;
|
||||
}
|
||||
|
||||
|
||||
g.node text,
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Disable outline after button click */
|
||||
.project-toolbar button {
|
||||
outline: 0;
|
||||
border: none;
|
||||
-moz-outline-style: none
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<mat-icon svgIcon="gns3"></mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
|
||||
|
||||
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
|
||||
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
|
||||
<mat-icon>work</mat-icon>
|
||||
@ -22,12 +22,14 @@
|
||||
</mat-menu>
|
||||
|
||||
<mat-toolbar-row>
|
||||
<button mat-icon-button (click)="turnOnDrawLineMode()" *ngIf="!drawLineMode">
|
||||
<button mat-icon-button [color]="drawLineMode ? 'primary': 'basic'" (click)="toggleDrawLineMode()">
|
||||
<mat-icon>timeline</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
|
||||
<button mat-icon-button color="primary" (click)="turnOffDrawLineMode()" *ngIf="drawLineMode">
|
||||
<mat-icon>timeline</mat-icon>
|
||||
<mat-toolbar-row>
|
||||
<button mat-icon-button [color]="movingMode ? 'primary': 'basic'" (click)="toggleMovingMode()">
|
||||
<mat-icon>zoom_out_map</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
|
||||
|
@ -12,27 +12,33 @@ import 'rxjs/add/observable/dom/webSocket';
|
||||
|
||||
|
||||
import { Project } from '../shared/models/project';
|
||||
import { Node } from '../cartography/shared/models/node.model';
|
||||
import { Node } from '../cartography/shared/models/node';
|
||||
import { SymbolService } from '../shared/services/symbol.service';
|
||||
import { Link } from "../cartography/shared/models/link.model";
|
||||
import { Link } from "../cartography/shared/models/link";
|
||||
import { MapComponent } from "../cartography/map/map.component";
|
||||
import { ServerService } from "../shared/services/server.service";
|
||||
import { ProjectService } from '../shared/services/project.service';
|
||||
import { Server } from "../shared/models/server";
|
||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef, MatSnackBar } from "@angular/material";
|
||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material";
|
||||
import { SnapshotService } from "../shared/services/snapshot.service";
|
||||
import { Snapshot } from "../shared/models/snapshot";
|
||||
import { ProgressDialogService } from "../shared/progress-dialog/progress-dialog.service";
|
||||
import { ProgressDialogComponent } from "../shared/progress-dialog/progress-dialog.component";
|
||||
import { Drawing } from "../cartography/shared/models/drawing.model";
|
||||
import { Drawing } from "../cartography/shared/models/drawing";
|
||||
import { NodeContextMenuComponent } from "../shared/node-context-menu/node-context-menu.component";
|
||||
import { Appliance } from "../shared/models/appliance";
|
||||
import { NodeService } from "../shared/services/node.service";
|
||||
import { Symbol } from "../shared/models/symbol";
|
||||
import { Symbol } from "../cartography/shared/models/symbol";
|
||||
import { NodeSelectInterfaceComponent } from "../shared/node-select-interface/node-select-interface.component";
|
||||
import { Port } from "../shared/models/port";
|
||||
import { LinkService } from "../shared/services/link.service";
|
||||
import { ToasterService } from '../shared/services/toaster.service';
|
||||
import { NodesDataSource } from "../cartography/shared/datasources/nodes-datasource";
|
||||
import { LinksDataSource } from "../cartography/shared/datasources/links-datasource";
|
||||
import { ProjectWebServiceHandler } from "../shared/handlers/project-web-service-handler";
|
||||
import { Rectangle } from "../cartography/shared/models/rectangle";
|
||||
import { SelectionManager } from "../cartography/shared/managers/selection-manager";
|
||||
import { InRectangleHelper } from "../cartography/map/helpers/in-rectangle-helper";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -52,6 +58,8 @@ export class ProjectMapComponent implements OnInit {
|
||||
|
||||
private ws: Subject<any>;
|
||||
private drawLineMode = false;
|
||||
private movingMode = false;
|
||||
|
||||
public isLoading = true;
|
||||
|
||||
@ViewChild(MapComponent) mapChild: MapComponent;
|
||||
@ -69,7 +77,11 @@ export class ProjectMapComponent implements OnInit {
|
||||
private linkService: LinkService,
|
||||
private dialog: MatDialog,
|
||||
private progressDialogService: ProgressDialogService,
|
||||
private toaster: ToasterService) {
|
||||
private toaster: ToasterService,
|
||||
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||
protected nodesDataSource: NodesDataSource,
|
||||
protected linksDataSource: LinksDataSource,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -102,6 +114,19 @@ export class ProjectMapComponent implements OnInit {
|
||||
this.symbols = symbols;
|
||||
});
|
||||
|
||||
this.nodesDataSource.connect().subscribe((nodes: Node[]) => {
|
||||
this.nodes = nodes;
|
||||
if (this.mapChild) {
|
||||
this.mapChild.reload();
|
||||
}
|
||||
});
|
||||
|
||||
this.linksDataSource.connect().subscribe((links: Link[]) => {
|
||||
this.links = links;
|
||||
if (this.mapChild) {
|
||||
this.mapChild.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onProjectLoad(project: Project) {
|
||||
@ -115,11 +140,11 @@ export class ProjectMapComponent implements OnInit {
|
||||
return this.projectService.links(this.server, project.project_id);
|
||||
})
|
||||
.flatMap((links: Link[]) => {
|
||||
this.links = links;
|
||||
this.linksDataSource.set(links);
|
||||
return this.projectService.nodes(this.server, project.project_id);
|
||||
})
|
||||
.subscribe((nodes: Node[]) => {
|
||||
this.nodes = nodes;
|
||||
this.nodesDataSource.set(nodes);
|
||||
|
||||
this.setUpMapCallbacks(project);
|
||||
this.setUpWS(project);
|
||||
@ -132,85 +157,33 @@ export class ProjectMapComponent implements OnInit {
|
||||
setUpWS(project: Project) {
|
||||
this.ws = Observable.webSocket(
|
||||
this.projectService.notificationsPath(this.server, project.project_id));
|
||||
|
||||
this.ws.subscribe((o: any) => {
|
||||
if (o.action === 'node.updated') {
|
||||
const node: Node = o.event;
|
||||
const index = this.nodes.findIndex((n: Node) => n.node_id === node.node_id);
|
||||
if (index >= 0) {
|
||||
this.nodes[index] = node;
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
if (o.action === 'node.created') {
|
||||
const node: Node = o.event;
|
||||
const index = this.nodes.findIndex((n: Node) => n.node_id === node.node_id);
|
||||
if (index === -1) {
|
||||
this.nodes.push(node);
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
if (o.action === 'node.deleted') {
|
||||
const node: Node = o.event;
|
||||
const index = this.nodes.findIndex((n: Node) => n.node_id === node.node_id);
|
||||
if (index >= 0) {
|
||||
this.nodes.splice(index, 1);
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
if (o.action === 'link.created') {
|
||||
const link: Link = o.event;
|
||||
const index = this.links.findIndex((l: Link) => l.link_id === link.link_id);
|
||||
if (index === -1) {
|
||||
this.links.push(link);
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
if (o.action === 'link.updated') {
|
||||
const link: Link = o.event;
|
||||
const index = this.links.findIndex((l: Link) => l.link_id === link.link_id);
|
||||
if (index >= 0) {
|
||||
this.links[index] = link;
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
if (o.action === 'link.deleted') {
|
||||
const link: Link = o.event;
|
||||
const index = this.links.findIndex((l: Link) => l.link_id === link.link_id);
|
||||
if (index >= 0) {
|
||||
this.links.splice(index, 1);
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
}
|
||||
}
|
||||
});
|
||||
this.projectWebServiceHandler.connect(this.ws);
|
||||
}
|
||||
|
||||
|
||||
setUpMapCallbacks(project: Project) {
|
||||
const selectionManager = new SelectionManager(this.nodesDataSource, this.linksDataSource, new InRectangleHelper());
|
||||
|
||||
this.mapChild.graphLayout.getNodesWidget().setOnContextMenuCallback((event: any, node: Node) => {
|
||||
this.nodeContextMenu.open(node, event.clientY, event.clientX);
|
||||
});
|
||||
|
||||
this.mapChild.graphLayout.getNodesWidget().setOnNodeClickedCallback((event: any, node: Node) => {
|
||||
selectionManager.setSelectedNodes([node]);
|
||||
if (this.drawLineMode) {
|
||||
this.nodeSelectInterfaceMenu.open(node, event.clientY, event.clientX);
|
||||
}
|
||||
});
|
||||
|
||||
this.mapChild.graphLayout.getNodesWidget().setOnNodeDraggedCallback((event: any, node: Node) => {
|
||||
const index = this.nodes.findIndex((n: Node) => n.node_id === node.node_id);
|
||||
if (index >= 0) {
|
||||
this.nodes[index] = node;
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
|
||||
this.nodeService
|
||||
.updatePosition(this.server, node, node.x, node.y)
|
||||
.subscribe((n: Node) => {
|
||||
this.nodes[index] = node;
|
||||
this.mapChild.reload(); // temporary invocation
|
||||
});
|
||||
}
|
||||
this.nodesDataSource.update(node);
|
||||
this.nodeService
|
||||
.updatePosition(this.server, node, node.x, node.y)
|
||||
.subscribe((n: Node) => {
|
||||
this.nodesDataSource.update(n);
|
||||
});
|
||||
});
|
||||
|
||||
selectionManager.subscribe(this.mapChild.graphLayout.getSelectionTool().rectangleSelected);
|
||||
}
|
||||
|
||||
onNodeCreation(appliance: Appliance) {
|
||||
@ -220,8 +193,7 @@ export class ProjectMapComponent implements OnInit {
|
||||
this.projectService
|
||||
.nodes(this.server, this.project.project_id)
|
||||
.subscribe((nodes: Node[]) => {
|
||||
this.nodes = nodes;
|
||||
this.mapChild.reload();
|
||||
this.nodesDataSource.set(nodes);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -255,13 +227,22 @@ export class ProjectMapComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public turnOnDrawLineMode() {
|
||||
this.drawLineMode = true;
|
||||
public toggleDrawLineMode() {
|
||||
this.drawLineMode = !this.drawLineMode;
|
||||
if (!this.drawLineMode) {
|
||||
this.mapChild.graphLayout.getDrawingLineTool().stop();
|
||||
}
|
||||
}
|
||||
|
||||
public turnOffDrawLineMode() {
|
||||
this.drawLineMode = false;
|
||||
this.mapChild.graphLayout.getDrawingLineTool().stop();
|
||||
public toggleMovingMode() {
|
||||
this.movingMode = !this.movingMode;
|
||||
if (this.movingMode) {
|
||||
this.mapChild.graphLayout.getSelectionTool().deactivate();
|
||||
this.mapChild.graphLayout.getMovingTool().activate();
|
||||
} else {
|
||||
this.mapChild.graphLayout.getMovingTool().deactivate();
|
||||
this.mapChild.graphLayout.getSelectionTool().activate();
|
||||
}
|
||||
}
|
||||
|
||||
public onChooseInterface(event) {
|
||||
@ -284,8 +265,7 @@ export class ProjectMapComponent implements OnInit {
|
||||
.createLink(this.server, source_node, source_port, target_node, target_port)
|
||||
.subscribe(() => {
|
||||
this.projectService.links(this.server, this.project.project_id).subscribe((links: Link[]) => {
|
||||
this.links = links;
|
||||
this.mapChild.reload();
|
||||
this.linksDataSource.set(links);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
import {Node} from "../../cartography/shared/models/node.model";
|
||||
|
||||
export class Database<T> {
|
||||
|
||||
}
|
||||
|
||||
export class NodeDatabase extends Database<Node> {
|
||||
|
||||
}
|
123
src/app/shared/handlers/project-web-service-handler.spec.ts
Normal file
123
src/app/shared/handlers/project-web-service-handler.spec.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import {ProjectWebServiceHandler, WebServiceMessage} from "./project-web-service-handler";
|
||||
import {Subject} from "rxjs/Subject";
|
||||
import {inject, TestBed} from "@angular/core/testing";
|
||||
import {NodesDataSource} from "../../cartography/shared/datasources/nodes-datasource";
|
||||
import {LinksDataSource} from "../../cartography/shared/datasources/links-datasource";
|
||||
import {Node} from "../../cartography/shared/models/node";
|
||||
import {Link} from "../../cartography/shared/models/link";
|
||||
|
||||
|
||||
|
||||
describe('ProjectWebServiceHandler', () => {
|
||||
let ws: Subject<WebServiceMessage>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ProjectWebServiceHandler, NodesDataSource, LinksDataSource]
|
||||
});
|
||||
|
||||
ws = new Subject<WebServiceMessage>();
|
||||
});
|
||||
|
||||
it('should be created', inject([ProjectWebServiceHandler], (service: ProjectWebServiceHandler) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('node should be added', inject([ProjectWebServiceHandler, NodesDataSource],
|
||||
(service: ProjectWebServiceHandler, nodesDataSource: NodesDataSource) => {
|
||||
spyOn(nodesDataSource, 'add');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "node.created";
|
||||
message.event = new Node();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(nodesDataSource.add).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
|
||||
it('node should be updated', inject([ProjectWebServiceHandler, NodesDataSource],
|
||||
(service: ProjectWebServiceHandler, nodesDataSource: NodesDataSource) => {
|
||||
spyOn(nodesDataSource, 'update');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "node.updated";
|
||||
message.event = new Node();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(nodesDataSource.update).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
|
||||
|
||||
it('node should be removed', inject([ProjectWebServiceHandler, NodesDataSource],
|
||||
(service: ProjectWebServiceHandler, nodesDataSource: NodesDataSource) => {
|
||||
spyOn(nodesDataSource, 'remove');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "node.deleted";
|
||||
message.event = new Node();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(nodesDataSource.remove).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
|
||||
it('link should be added', inject([ProjectWebServiceHandler, LinksDataSource],
|
||||
(service: ProjectWebServiceHandler, linksDataSource: LinksDataSource) => {
|
||||
spyOn(linksDataSource, 'add');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "link.created";
|
||||
message.event = new Link();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(linksDataSource.add).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
|
||||
it('link should be updated', inject([ProjectWebServiceHandler, LinksDataSource],
|
||||
(service: ProjectWebServiceHandler, linksDataSource: LinksDataSource) => {
|
||||
spyOn(linksDataSource, 'update');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "link.updated";
|
||||
message.event = new Link();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(linksDataSource.update).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
|
||||
|
||||
it('link should be removed', inject([ProjectWebServiceHandler, LinksDataSource],
|
||||
(service: ProjectWebServiceHandler, linksDataSource: LinksDataSource) => {
|
||||
spyOn(linksDataSource, 'remove');
|
||||
|
||||
service.connect(ws);
|
||||
|
||||
const message = new WebServiceMessage();
|
||||
message.action = "link.deleted";
|
||||
message.event = new Link();
|
||||
|
||||
ws.next(message);
|
||||
|
||||
expect(service).toBeTruthy();
|
||||
expect(linksDataSource.remove).toHaveBeenCalledWith(message.event);
|
||||
}));
|
||||
});
|
41
src/app/shared/handlers/project-web-service-handler.ts
Normal file
41
src/app/shared/handlers/project-web-service-handler.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {NodesDataSource} from "../../cartography/shared/datasources/nodes-datasource";
|
||||
import {LinksDataSource} from "../../cartography/shared/datasources/links-datasource";
|
||||
import {Subject} from "rxjs/Subject";
|
||||
import {Link} from "../../cartography/shared/models/link";
|
||||
import {Node} from "../../cartography/shared/models/node";
|
||||
|
||||
|
||||
export class WebServiceMessage {
|
||||
action: string;
|
||||
event: Node | Link;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ProjectWebServiceHandler {
|
||||
constructor(private nodesDataSource: NodesDataSource,
|
||||
private linksDataSource: LinksDataSource) {}
|
||||
|
||||
public connect(ws: Subject<WebServiceMessage>) {
|
||||
ws.subscribe((message: WebServiceMessage) => {
|
||||
if (message.action === 'node.updated') {
|
||||
this.nodesDataSource.update(message.event as Node);
|
||||
}
|
||||
if (message.action === 'node.created') {
|
||||
this.nodesDataSource.add(message.event as Node);
|
||||
}
|
||||
if (message.action === 'node.deleted') {
|
||||
this.nodesDataSource.remove(message.event as Node);
|
||||
}
|
||||
if (message.action === 'link.created') {
|
||||
this.linksDataSource.add(message.event as Link);
|
||||
}
|
||||
if (message.action === 'link.updated') {
|
||||
this.linksDataSource.update(message.event as Link);
|
||||
}
|
||||
if (message.action === 'link.deleted') {
|
||||
this.linksDataSource.remove(message.event as Link);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Server} from "../../../models/server";
|
||||
import {NodeService} from "../../../services/node.service";
|
||||
import {Node} from "../../../../cartography/shared/models/node.model";
|
||||
import {Node} from "../../../../cartography/shared/models/node";
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Server} from "../../../models/server";
|
||||
import {NodeService} from "../../../services/node.service";
|
||||
import {Node} from "../../../../cartography/shared/models/node.model";
|
||||
import {Node} from "../../../../cartography/shared/models/node";
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {ChangeDetectorRef, Component, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {MatMenuTrigger} from "@angular/material";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {Node} from "../../cartography/shared/models/node.model";
|
||||
import {Node} from "../../cartography/shared/models/node";
|
||||
import {Server} from "../models/server";
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||
import {MatMenuTrigger} from "@angular/material";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {Node} from "../../cartography/shared/models/node.model";
|
||||
import {Node} from "../../cartography/shared/models/node";
|
||||
import {Port} from "../models/port";
|
||||
|
||||
|
||||
|
@ -1,15 +1,47 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { TestBed, } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
||||
import { ApplianceService } from './appliance.service';
|
||||
import { Server } from '../models/server';
|
||||
import { HttpServer } from './http-server.service';
|
||||
|
||||
|
||||
|
||||
describe('ApplianceService', () => {
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
let service: ApplianceService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ApplianceService]
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [
|
||||
ApplianceService,
|
||||
HttpServer
|
||||
]
|
||||
});
|
||||
|
||||
httpClient = TestBed.get(HttpClient);
|
||||
httpTestingController = TestBed.get(HttpTestingController);
|
||||
service = TestBed.get(ApplianceService);
|
||||
});
|
||||
|
||||
// it('should be created', inject([ApplianceService], (service: ApplianceService) => {
|
||||
// expect(service).toBeTruthy();
|
||||
// }));
|
||||
afterEach(() => {
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
it('should ask for the list from server', () => {
|
||||
const server = new Server();
|
||||
server.ip = "127.0.0.1";
|
||||
server.port = 3080;
|
||||
server.authorization = "none";
|
||||
|
||||
service.list(server).subscribe();
|
||||
|
||||
httpTestingController.expectOne('http://127.0.0.1:3080/v2/appliances');
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
import {Appliance} from "../models/appliance";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
|
||||
@Injectable()
|
||||
export class ApplianceService {
|
||||
@ -13,8 +13,7 @@ export class ApplianceService {
|
||||
|
||||
list(server: Server): Observable<Appliance[]> {
|
||||
return this.httpServer
|
||||
.get(server, '/appliances')
|
||||
.map(response => response.json() as Appliance[]);
|
||||
.get<Appliance[]>(server, '/appliances') as Observable<Appliance[]>;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,15 +1,146 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { TestBed, } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
||||
import { Server } from '../models/server';
|
||||
import { HttpServer } from './http-server.service';
|
||||
|
||||
|
||||
describe('HttpServer', () => {
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
let service: HttpServer;
|
||||
let server: Server;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [HttpServer]
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [
|
||||
HttpServer
|
||||
]
|
||||
});
|
||||
|
||||
httpClient = TestBed.get(HttpClient);
|
||||
httpTestingController = TestBed.get(HttpTestingController);
|
||||
service = TestBed.get(HttpServer);
|
||||
|
||||
server = new Server();
|
||||
server.ip = "127.0.0.1";
|
||||
server.port = 3080;
|
||||
server.authorization = "none";
|
||||
});
|
||||
|
||||
// it('should be created', inject([HttpServer], (service: HttpServer) => {
|
||||
// expect(service).toBeTruthy();
|
||||
// }));
|
||||
afterEach(() => {
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
it('should make GET query for get method', () => {
|
||||
service.get(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("GET");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make GET query for getText method', () => {
|
||||
service.getText(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("GET");
|
||||
expect(req.request.responseType).toEqual("text");
|
||||
});
|
||||
|
||||
it('should make GET query for getText method and preserve options', () => {
|
||||
service.getText(server, '/test', {
|
||||
headers: {
|
||||
'CustomHeader': 'value'
|
||||
},
|
||||
responseType: 'text'
|
||||
}).subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("GET");
|
||||
expect(req.request.responseType).toEqual("text");
|
||||
});
|
||||
|
||||
it('should make POST query for post method', () => {
|
||||
service.post(server, '/test', {test: "1"}).subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("POST");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make PUT query for put method', () => {
|
||||
service.put(server, '/test', {test: "1"}).subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("PUT");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make DELETE query for delete method', () => {
|
||||
service.delete(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("DELETE");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make PATCH query for patch method', () => {
|
||||
service.patch(server, '/test', {test: "1"}).subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("PATCH");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make HEAD query for head method', () => {
|
||||
service.head(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("HEAD");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should make OPTIONS query for options method', () => {
|
||||
service.options(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("OPTIONS");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
});
|
||||
|
||||
it('should add headers for `basic` authorization', () => {
|
||||
server.authorization = "basic";
|
||||
server.login = "login";
|
||||
server.password = "password";
|
||||
|
||||
service.get(server, '/test').subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("GET");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
expect(req.request.headers.get('Authorization')).toEqual('Basic bG9naW46cGFzc3dvcmQ=');
|
||||
});
|
||||
|
||||
it('should add headers for `basic` authorization and preserve headers', () => {
|
||||
server.authorization = "basic";
|
||||
server.login = "login";
|
||||
server.password = "password";
|
||||
|
||||
service.get(server, '/test', {
|
||||
headers: {
|
||||
'CustomHeader': 'value'
|
||||
}
|
||||
}).subscribe();
|
||||
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||
expect(req.request.method).toEqual("GET");
|
||||
expect(req.request.responseType).toEqual("json");
|
||||
expect(req.request.headers.get('Authorization')).toEqual('Basic bG9naW46cGFzc3dvcmQ=');
|
||||
expect(req.request.headers.get('CustomHeader')).toEqual('value');
|
||||
});
|
||||
});
|
||||
|
@ -1,64 +1,132 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpHeaders, HttpClient, HttpParams} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import {Headers, Http, RequestOptions, RequestOptionsArgs, Response} from "@angular/http";
|
||||
|
||||
import {Server} from "../models/server";
|
||||
|
||||
|
||||
/* tslint:disable:interface-over-type-literal */
|
||||
export type JsonOptions = {
|
||||
headers?: HttpHeaders | {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
observe?: 'body';
|
||||
params?: HttpParams | {
|
||||
[param: string]: string | string[];
|
||||
};
|
||||
reportProgress?: boolean;
|
||||
responseType?: 'json';
|
||||
withCredentials?: boolean;
|
||||
};
|
||||
|
||||
export type TextOptions = {
|
||||
headers?: HttpHeaders | {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
observe?: 'body';
|
||||
params?: HttpParams | {
|
||||
[param: string]: string | string[];
|
||||
};
|
||||
reportProgress?: boolean;
|
||||
responseType: 'text';
|
||||
withCredentials?: boolean;
|
||||
};
|
||||
|
||||
export type HeadersOptions = {
|
||||
headers?: HttpHeaders | {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
};
|
||||
/* tslint:enable:interface-over-type-literal */
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class HttpServer {
|
||||
|
||||
constructor(private http: Http) { }
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
get(server: Server, url: string, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.get(url, options);
|
||||
get<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer<JsonOptions>(server, url, options);
|
||||
return this.http.get<T>(intercepted.url, intercepted.options as JsonOptions);
|
||||
}
|
||||
|
||||
post(server: Server, url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.post(url, body, options);
|
||||
getText(server: Server, url: string, options?: TextOptions): Observable<string> {
|
||||
options = this.getTextOptions(options);
|
||||
const intercepted = this.getOptionsForServer<TextOptions>(server, url, options);
|
||||
return this.http.get(intercepted.url, intercepted.options as TextOptions);
|
||||
}
|
||||
|
||||
put(server: Server, url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.put(url, body, options);
|
||||
post<T>(server: Server, url: string, body: any | null, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.post<T>(intercepted.url, body, intercepted.options);
|
||||
}
|
||||
|
||||
delete(server: Server, url: string, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.delete(url, options);
|
||||
put<T>(server: Server, url: string, body: any, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.put<T>(intercepted.url, body, intercepted.options);
|
||||
}
|
||||
|
||||
patch(server: Server, url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.patch(url, body, options);
|
||||
delete<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.delete<T>(intercepted.url, intercepted.options);
|
||||
}
|
||||
|
||||
head(server: Server, url: string, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.patch(url, options);
|
||||
patch<T>(server: Server, url: string, body: any, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.patch<T>(intercepted.url, body, intercepted.options);
|
||||
}
|
||||
|
||||
options(server: Server, url: string, options?: RequestOptionsArgs): Observable<Response> {
|
||||
options = this.getOptionsForServer(server, url, options);
|
||||
return this.http.options(url, options);
|
||||
head<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.head<T>(intercepted.url, intercepted.options);
|
||||
}
|
||||
|
||||
private getOptionsForServer(server: Server, url: string, options) {
|
||||
if (options === undefined) {
|
||||
options = new RequestOptions();
|
||||
options<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||
options = this.getJsonOptions(options);
|
||||
const intercepted = this.getOptionsForServer(server, url, options);
|
||||
return this.http.options<T>(intercepted.url, intercepted.options);
|
||||
}
|
||||
|
||||
private getJsonOptions(options: JsonOptions): JsonOptions {
|
||||
if (!options) {
|
||||
return {
|
||||
responseType: "json"
|
||||
};
|
||||
}
|
||||
options.url = `http://${server.ip}:${server.port}/v2${url}`;
|
||||
return options;
|
||||
}
|
||||
|
||||
if (options.headers === null) {
|
||||
options.headers = new Headers();
|
||||
private getTextOptions(options: TextOptions): TextOptions {
|
||||
if (!options) {
|
||||
return {
|
||||
responseType: "text"
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private getOptionsForServer<T extends HeadersOptions>(server: Server, url: string, options: T) {
|
||||
url = `http://${server.ip}:${server.port}/v2${url}`;
|
||||
|
||||
if (!options.headers) {
|
||||
options.headers = {};
|
||||
}
|
||||
|
||||
if (server.authorization === "basic") {
|
||||
const credentials = btoa(`${server.login}:${server.password}`);
|
||||
options.headers.append('Authorization', `Basic ${credentials}`);
|
||||
options.headers['Authorization'] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
return options;
|
||||
return {
|
||||
url: url,
|
||||
options: options
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Node } from '../../cartography/shared/models/node.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Node } from '../../cartography/shared/models/node';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
import {Response} from "@angular/http";
|
||||
import {Port} from "../models/port";
|
||||
|
||||
@Injectable()
|
||||
@ -15,7 +13,7 @@ export class LinkService {
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
createLink(
|
||||
server: Server, source_node: Node, source_port: Port, target_node: Node, target_port: Port): Observable<Response> {
|
||||
server: Server, source_node: Node, source_port: Port, target_node: Node, target_port: Port) {
|
||||
return this.httpServer
|
||||
.post(
|
||||
server,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Project } from '../models/project';
|
||||
import { Node } from '../../cartography/shared/models/node.model';
|
||||
import { Node } from '../../cartography/shared/models/node';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
import {Appliance} from "../models/appliance";
|
||||
import {Response} from "@angular/http";
|
||||
|
||||
|
||||
@Injectable()
|
||||
@ -15,21 +14,19 @@ export class NodeService {
|
||||
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
start(server: Server, node: Node): Observable<Node> {
|
||||
start(server: Server, node: Node) {
|
||||
return this.httpServer
|
||||
.post(server, `/projects/${node.project_id}/nodes/${node.node_id}/start`, {})
|
||||
.map(response => response.json() as Node);
|
||||
.post<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}/start`, {});
|
||||
}
|
||||
|
||||
stop(server: Server, node: Node): Observable<Node> {
|
||||
stop(server: Server, node: Node) {
|
||||
return this.httpServer
|
||||
.post(server, `/projects/${node.project_id}/nodes/${node.node_id}/stop`, {})
|
||||
.map(response => response.json() as Node);
|
||||
.post<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}/stop`, {});
|
||||
}
|
||||
|
||||
createFromAppliance(
|
||||
server: Server, project: Project, appliance: Appliance,
|
||||
x: number, y: number, compute_id: string): Observable<Response> {
|
||||
x: number, y: number, compute_id: string) {
|
||||
return this.httpServer
|
||||
.post(
|
||||
server,
|
||||
@ -39,10 +36,9 @@ export class NodeService {
|
||||
|
||||
updatePosition(server: Server, node: Node, x: number, y: number): Observable<Node> {
|
||||
return this.httpServer
|
||||
.put(server, `/projects/${node.project_id}/nodes/${node.node_id}`, {
|
||||
.put<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}`, {
|
||||
'x': x,
|
||||
'y': y
|
||||
})
|
||||
.map(response => response.json() as Node);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,47 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Project } from '../models/project';
|
||||
import { Node } from '../../cartography/shared/models/node.model';
|
||||
import { Node } from '../../cartography/shared/models/node';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Link } from "../../cartography/shared/models/link.model";
|
||||
import { Link } from "../../cartography/shared/models/link";
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
import {Drawing} from "../../cartography/shared/models/drawing.model";
|
||||
import {Drawing} from "../../cartography/shared/models/drawing";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService {
|
||||
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
get(server: Server, project_id: string): Observable<Project> {
|
||||
get(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.get(server, `/projects/${project_id}`)
|
||||
.map(response => response.json() as Project);
|
||||
.get<Project>(server, `/projects/${project_id}`);
|
||||
}
|
||||
|
||||
open(server: Server, project_id: string): Observable<Project> {
|
||||
open(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.post(server, `/projects/${project_id}/open`, {})
|
||||
.map(response => response.json() as Project);
|
||||
.post<Project>(server, `/projects/${project_id}/open`, {});
|
||||
}
|
||||
|
||||
list(server: Server): Observable<Project[]> {
|
||||
list(server: Server) {
|
||||
return this.httpServer
|
||||
.get(server, '/projects')
|
||||
.map(response => response.json() as Project[]);
|
||||
.get<Project[]>(server, '/projects');
|
||||
}
|
||||
|
||||
nodes(server: Server, project_id: string): Observable<Node[]> {
|
||||
nodes(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.get(server, `/projects/${project_id}/nodes`)
|
||||
.map(response => response.json() as Node[]);
|
||||
.get<Node[]>(server, `/projects/${project_id}/nodes`);
|
||||
}
|
||||
|
||||
links(server: Server, project_id: string): Observable<Link[]> {
|
||||
links(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.get(server, `/projects/${project_id}/links`)
|
||||
.map(response => response.json() as Link[]);
|
||||
.get<Link[]>(server, `/projects/${project_id}/links`);
|
||||
}
|
||||
|
||||
drawings(server: Server, project_id: string): Observable<Drawing[]> {
|
||||
drawings(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.get(server, `/projects/${project_id}/drawings`)
|
||||
.map(response => response.json() as Drawing[]);
|
||||
.get<Drawing[]>(server, `/projects/${project_id}/drawings`);
|
||||
}
|
||||
|
||||
delete(server: Server, project_id: string): Observable<any> {
|
||||
|
@ -10,16 +10,14 @@ export class SnapshotService {
|
||||
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
create(server: Server, project_id: string, snapshot: Snapshot): Observable<Snapshot> {
|
||||
create(server: Server, project_id: string, snapshot: Snapshot) {
|
||||
return this.httpServer
|
||||
.post(server, `/projects/${project_id}/snapshots`, snapshot)
|
||||
.map(response => response.json() as Snapshot);
|
||||
.post<Snapshot>(server, `/projects/${project_id}/snapshots`, snapshot);
|
||||
}
|
||||
|
||||
list(server: Server, project_id: string): Observable<Snapshot[]> {
|
||||
list(server: Server, project_id: string) {
|
||||
return this.httpServer
|
||||
.get(server, `/projects/${project_id}/snapshots`)
|
||||
.map(response => response.json() as Snapshot[]);
|
||||
.get<Snapshot[]>(server, `/projects/${project_id}/snapshots`);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/forkJoin';
|
||||
import 'rxjs/add/observable/of';
|
||||
|
||||
import { Symbol } from '../models/symbol';
|
||||
import { Symbol } from '../../cartography/shared/models/symbol';
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
|
||||
@ -26,7 +26,7 @@ export class SymbolService {
|
||||
load(server: Server): Observable<Symbol[]> {
|
||||
this.list(server).subscribe((symbols: Symbol[]) => {
|
||||
const streams = symbols.map(symbol => this.raw(server, symbol.symbol_id));
|
||||
Observable.forkJoin(streams).subscribe((results: string[]) => {
|
||||
Observable.forkJoin(streams).subscribe((results) => {
|
||||
symbols.forEach((symbol: Symbol, i: number) => {
|
||||
symbol.raw = results[i];
|
||||
});
|
||||
@ -36,16 +36,14 @@ export class SymbolService {
|
||||
return this.symbols.asObservable();
|
||||
}
|
||||
|
||||
list(server: Server): Observable<Symbol[]> {
|
||||
list(server: Server) {
|
||||
return this.httpServer
|
||||
.get(server, '/symbols')
|
||||
.map(response => response.json() as Symbol[]);
|
||||
.get<Symbol[]>(server, '/symbols');
|
||||
}
|
||||
|
||||
raw(server: Server, symbol_id: string): Observable<string> {
|
||||
raw(server: Server, symbol_id: string) {
|
||||
const encoded_uri = encodeURI(symbol_id);
|
||||
return this.httpServer
|
||||
.get(server, `/symbols/${encoded_uri}/raw`)
|
||||
.map(response => response.text() as string);
|
||||
.getText(server, `/symbols/${encoded_uri}/raw`);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
@ -13,9 +12,8 @@ export class VersionService {
|
||||
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
get(server: Server): Observable<Version> {
|
||||
get(server: Server) {
|
||||
return this.httpServer
|
||||
.get(server, '/version')
|
||||
.map(response => response.json() as Version);
|
||||
.get<Version>(server, '/version');
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user