GUI2 added in the layout topo overlay
Change-Id: I9960f95ae726a5af9950771ed67bcfc9d172e267
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/gui2-topo-lib.module.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/gui2-topo-lib.module.ts
index 643c3d7..dcb6321 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/gui2-topo-lib.module.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/gui2-topo-lib.module.ts
@@ -37,6 +37,7 @@
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import { GridsvgComponent } from './layer/gridsvg/gridsvg.component';
import {TrafficService} from './traffic.service';
+import {LayoutService} from './layout.service';
/**
* ONOS GUI -- Topology View Module
@@ -75,7 +76,8 @@
],
providers: [
TopologyService,
- TrafficService
+ TrafficService,
+ LayoutService
],
exports: [
BackgroundSvgComponent,
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
index 6910353..843c19c 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
@@ -36,7 +36,7 @@
ZoomUtils
} from 'gui2-fw-lib';
import {
- Device,
+ Device, DeviceProps,
ForceDirectedGraph,
Host,
HostLabelToggle,
@@ -47,6 +47,7 @@
Location,
ModelEventMemo,
ModelEventType,
+ Node,
Region,
RegionLink,
SubRegion,
@@ -56,6 +57,7 @@
import {DeviceNodeSvgComponent} from './visuals/devicenodesvg/devicenodesvg.component';
import { HostNodeSvgComponent} from './visuals/hostnodesvg/hostnodesvg.component';
import { LinkSvgComponent} from './visuals/linksvg/linksvg.component';
+import { Options } from './models/force-directed-graph';
interface UpdateMeta {
id: string;
@@ -63,6 +65,16 @@
memento: MetaUi;
}
+const SVGCANVAS = <Options>{
+ width: 1000,
+ height: 1000
+};
+
+interface ChangeSummary {
+ numChanges: number;
+ locationChanged: boolean;
+}
+
/**
* ONOS GUI -- Topology Forces Graph Layer View.
*
@@ -88,7 +100,6 @@
@Output() linkSelected = new EventEmitter<RegionLink>();
@Output() selectedNodeEvent = new EventEmitter<UiElement>();
public graph: ForceDirectedGraph;
- private _options: { width, height } = { width: 800, height: 600 };
// References to the children of this component - these are created in the
// template view with the *ngFor and we get them by a query here
@@ -131,16 +142,26 @@
* @param existingNode 1st object
* @param updatedNode 2nd object
*/
- private static updateObject(existingNode: Object, updatedNode: Object): number {
- let changed: number = 0;
+ private static updateObject(existingNode: Object, updatedNode: Object): ChangeSummary {
+ const changed = <ChangeSummary>{numChanges: 0, locationChanged: false};
for (const key of Object.keys(updatedNode)) {
const o = updatedNode[key];
- if (key === 'id') {
+ if (['id', 'x', 'y', 'fx', 'fy', 'vx', 'vy', 'index'].some(k => k === key)) {
continue;
} else if (o && typeof o === 'object' && o.constructor === Object) {
- changed += ForceSvgComponent.updateObject(existingNode[key], updatedNode[key]);
+ const subChanged = ForceSvgComponent.updateObject(existingNode[key], updatedNode[key]);
+ changed.numChanges += subChanged.numChanges;
+ changed.locationChanged = subChanged.locationChanged ? true : changed.locationChanged;
+ } else if (existingNode === undefined) {
+ // Copy the whole object
+ existingNode = updatedNode;
+ changed.locationChanged = true;
+ changed.numChanges++;
} else if (existingNode[key] !== updatedNode[key]) {
- changed++;
+ if (['locType', 'latOrY', 'longOrX', 'latitude', 'longitude', 'gridX', 'gridY'].some(k => k === key)) {
+ changed.locationChanged = true;
+ }
+ changed.numChanges++;
existingNode[key] = updatedNode[key];
}
}
@@ -149,8 +170,8 @@
@HostListener('window:resize', ['$event'])
onResize(event) {
- this.graph.initSimulation(this.options);
- this.log.debug('Simulation reinit after resize', event);
+ this.graph.restartSimulation();
+ this.log.debug('Simulation restart after resize', event);
}
/**
@@ -161,7 +182,7 @@
*/
ngOnInit() {
// Receiving an initialized simulated graph from our custom d3 service
- this.graph = new ForceDirectedGraph(this.options, this.log);
+ this.graph = new ForceDirectedGraph(SVGCANVAS, this.log);
/** Binding change detection check on each tick
* This along with an onPush change detection strategy should enforce
@@ -171,9 +192,11 @@
* simulations data binding.
*/
this.graph.ticker.subscribe((simulation) => {
- // this.log.debug("Force simulation has ticked", simulation);
+ // this.log.debug("Force simulation has ticked. Alpha",
+ // Math.round(simulation.alpha() * 1000) / 1000);
this.ref.markForCheck();
});
+
this.log.debug('ForceSvgComponent initialized - waiting for nodes and links');
}
@@ -208,20 +231,7 @@
this.graph.nodes = this.graph.nodes.concat(subRegions);
}
- // If a node has a fixed location then assign it to fx and fy so
- // that it doesn't get affected by forces
- this.graph.nodes
- .forEach((n) => {
- const loc: Location = <Location>n['location'];
- if (loc && loc.locType === LocationType.GEO) {
- const position: MetaUi =
- ZoomUtils.convertGeoToCanvas(
- <LocMeta>{lng: loc.longOrX, lat: loc.latOrY});
- n.fx = position.x;
- n.fy = position.y;
- this.log.debug('Found node', n.id, 'with', loc.locType);
- }
- });
+ this.graph.nodes.forEach((n) => this.fixPosition(n));
// Associate the endpoints of each link with a real node
this.graph.links = [];
@@ -240,15 +250,42 @@
}
this.graph.links = this.regionData.links;
-
- this.graph.initSimulation(this.options);
- this.graph.initNodes();
- this.graph.initLinks();
+ if (this.graph.nodes.length > 0) {
+ this.graph.reinitSimulation();
+ }
this.log.debug('ForceSvgComponent input changed',
this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
}
+ }
- this.ref.markForCheck();
+ /**
+ * If a node has a fixed location then assign it to fx and fy so
+ * that it doesn't get affected by forces
+ * @param graphNode The node whose location should be processed
+ */
+ private fixPosition(graphNode: Node): void {
+ const loc: Location = <Location>graphNode['location'];
+ const props: DeviceProps = <DeviceProps>graphNode['props'];
+ const metaUi = <MetaUi>graphNode['metaUi'];
+ if (loc && loc.locType === LocationType.GEO) {
+ const position: MetaUi =
+ ZoomUtils.convertGeoToCanvas(
+ <LocMeta>{lng: loc.longOrX, lat: loc.latOrY});
+ graphNode.fx = position.x;
+ graphNode.fy = position.y;
+ this.log.debug('Found node', graphNode.id, 'with', loc.locType);
+ } else if (loc && loc.locType === LocationType.GRID) {
+ graphNode.fx = loc.longOrX;
+ graphNode.fy = loc.latOrY;
+ this.log.debug('Found node', graphNode.id, 'with', loc.locType);
+ } else if (props && props.locType === LocationType.NONE && metaUi) {
+ graphNode.fx = metaUi.x;
+ graphNode.fy = metaUi.y;
+ this.log.debug('Found node', graphNode.id, 'with locType=none and metaUi');
+ } else {
+ graphNode.fx = null;
+ graphNode.fy = null;
+ }
}
/**
@@ -270,13 +307,6 @@
this.linkSelected.emit(link);
}
- get options() {
- return this._options = {
- width: window.innerWidth,
- height: window.innerHeight
- };
- }
-
/**
* Iterate through all hosts and devices to deselect the previously selected
* node. The emit an event to the parent that lets it know the selection has
@@ -326,23 +356,7 @@
switch (type) {
case ModelEventType.DEVICE_ADDED_OR_UPDATED:
if (memo === ModelEventMemo.ADDED) {
- const loc = (<Device>data).location;
- if (loc && loc.locType === LocationType.GEO) {
- const position =
- ZoomUtils.convertGeoToCanvas(<LocMeta>{ lng: loc.longOrX, lat: loc.latOrY});
- (<Device>data).fx = position.x;
- (<Device>data).fy = position.y;
- this.log.debug('Using long', loc.longOrX, 'lat', loc.latOrY, '(', position.x, position.y, ')');
- } else if (loc && loc.locType === LocationType.GRID) {
- (<Device>data).fx = loc.longOrX;
- (<Device>data).fy = loc.latOrY;
- this.log.debug('Using grid', loc.longOrX, loc.latOrY);
- } else {
- (<Device>data).fx = null;
- (<Device>data).fy = null;
- // (<Device>data).x = 500;
- // (<Device>data).y = 500;
- }
+ this.fixPosition(<Device>data);
this.graph.nodes.push(<Device>data);
this.regionData.devices[this.visibleLayerIdx()].push(<Device>data);
this.log.debug('Device added', (<Device>data).id);
@@ -351,8 +365,11 @@
this.regionData.devices[this.visibleLayerIdx()]
.find((d) => d.id === subject);
const changes = ForceSvgComponent.updateObject(oldDevice, <Device>data);
- if (changes > 0) {
+ if (changes.numChanges > 0) {
this.log.debug('Device ', oldDevice.id, memo, ' - ', changes, 'changes');
+ if (changes.locationChanged) {
+ this.fixPosition(oldDevice);
+ }
}
} else {
this.log.warn('Device ', memo, ' - not yet implemented', data);
@@ -360,15 +377,19 @@
break;
case ModelEventType.HOST_ADDED_OR_UPDATED:
if (memo === ModelEventMemo.ADDED) {
- this.regionData.hosts[this.visibleLayerIdx()].push(<Host>data);
+ this.fixPosition(<Host>data);
this.graph.nodes.push(<Host>data);
+ this.regionData.hosts[this.visibleLayerIdx()].push(<Host>data);
this.log.debug('Host added', (<Host>data).id);
} else if (memo === ModelEventMemo.UPDATED) {
const oldHost: Host = this.regionData.hosts[this.visibleLayerIdx()]
.find((h) => h.id === subject);
const changes = ForceSvgComponent.updateObject(oldHost, <Host>data);
- if (changes > 0) {
+ if (changes.numChanges > 0) {
this.log.debug('Host ', oldHost.id, memo, ' - ', changes, 'changes');
+ if (changes.locationChanged) {
+ this.fixPosition(oldHost);
+ }
}
} else {
this.log.warn('Host change', memo, ' - unexpected');
@@ -424,10 +445,8 @@
default:
this.log.error('Unexpected model event', type, 'for', subject);
}
- this.ref.markForCheck();
- this.graph.initSimulation(this.options);
- this.graph.initNodes();
- this.graph.initLinks();
+ this.graph.links = this.regionData.links;
+ this.graph.reinitSimulation();
}
private removeRelatedLinks(subject: string) {
@@ -500,7 +519,7 @@
* @param newLocation - the new Location of the node
*/
nodeMoved(klass: string, id: string, newLocation: MetaUi) {
- this.wss.sendEvent('updateMeta', <UpdateMeta>{
+ this.wss.sendEvent('updateMeta2', <UpdateMeta>{
id: id,
class: klass,
memento: newLocation
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.spec.ts
index bdcd5c5..90c12e1 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.spec.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.spec.ts
@@ -55,8 +55,7 @@
}
fdg.nodes = nodes;
fdg.links = links;
- fdg.initSimulation(options);
- fdg.initNodes();
+ fdg.reinitSimulation();
logServiceSpy = TestBed.get(LogService);
});
@@ -64,7 +63,7 @@
fdg.stopSimulation();
fdg.nodes = [];
fdg.links = [];
- fdg.initSimulation(options);
+ fdg.reinitSimulation();
});
it('should be created', () => {
@@ -96,14 +95,7 @@
// it('init links chould be called ', () => {
// spyOn(fdg, 'initLinks');
// // expect(fdg).toBeTruthy();
- // fdg.initSimulation(options);
+ // fdg.reinitSimulation(options);
// expect(fdg.initLinks).toHaveBeenCalled();
// });
-
- it ('throws error on no options', () => {
- expect(fdg.initSimulation).toThrowError('missing options when initializing simulation');
- });
-
-
-
});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.ts
index 24dd029..b4de906 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/force-directed-graph.ts
@@ -20,17 +20,16 @@
import {LogService} from 'gui2-fw-lib';
const FORCES = {
- LINKS: 1 / 50,
COLLISION: 1,
GRAVITY: 0.4,
FRICTION: 0.7
};
const CHARGES = {
- device: -80,
- host: -200,
- region: -80,
- _def_: -120
+ device: -800,
+ host: -2000,
+ region: -800,
+ _def_: -1200
};
const LINK_DISTANCE = {
@@ -41,10 +40,12 @@
_def_: 50,
};
+/**
+ * note: key is link.type
+ * range: {0.0 ... 1.0}
+ */
const LINK_STRENGTH = {
- // note: key is link.type
- // range: {0.0 ... 1.0}
- _def_: 0.1
+ _def_: 0.5
};
export interface Options {
@@ -55,87 +56,68 @@
/**
* The inspiration for this approach comes from
* https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
+ *
+ * Do yourself a favour and read https://d3indepth.com/force-layout/
*/
export class ForceDirectedGraph {
public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
public simulation: d3.Simulation<any, any>;
-
+ public canvasOptions: Options;
public nodes: Node[] = [];
public links: Link[] = [];
constructor(options: Options, public log: LogService) {
- this.initSimulation(options);
+ this.canvasOptions = options;
+ const ticker = this.ticker;
+
+ // Creating the force simulation and defining the charges
+ this.simulation = d3.forceSimulation()
+ .force('charge',
+ d3.forceManyBody().strength(this.charges.bind(this)))
+ // .distanceMin(100).distanceMax(500))
+ .force('gravity',
+ d3.forceManyBody().strength(FORCES.GRAVITY))
+ .force('friction',
+ d3.forceManyBody().strength(FORCES.FRICTION))
+ .force('center',
+ d3.forceCenter(this.canvasOptions.width / 2, this.canvasOptions.height / 2))
+ .force('x', d3.forceX())
+ .force('y', d3.forceY())
+ .on('tick', () => {
+ ticker.emit(this.simulation); // ForceSvgComponent.ngOnInit listens
+ });
+
}
- initNodes() {
- if (!this.simulation) {
- throw new Error('simulation was not initialized yet');
- }
-
+ /**
+ * Assigning updated node and restarting the simulation
+ * Setting alpha to 0.3 and it will count down to alphaTarget=0
+ */
+ public reinitSimulation() {
this.simulation.nodes(this.nodes);
- }
-
- initLinks() {
- if (!this.simulation) {
- throw new Error('simulation was not initialized yet');
- }
-
- // Initializing the links force simulation
- this.simulation.force('links',
+ this.simulation.force('link',
d3.forceLink(this.links)
.strength(this.strength.bind(this))
.distance(this.distance.bind(this))
);
+ this.simulation.alpha(0.3).restart();
}
- charges(node) {
+ charges(node: Node) {
const nodeType = node.nodeType;
return CHARGES[nodeType] || CHARGES._def_;
}
- distance(node) {
- const nodeType = node.nodeType;
- return LINK_DISTANCE[nodeType] || LINK_DISTANCE._def_;
+ distance(link: Link) {
+ const linkType = link.type;
+ this.log.debug('Link type', linkType, LINK_DISTANCE[linkType]);
+ return LINK_DISTANCE[linkType] || LINK_DISTANCE._def_;
}
- strength(node) {
- const nodeType = node.nodeType;
- return LINK_STRENGTH[nodeType] || LINK_STRENGTH._def_;
- }
-
- initSimulation(options: Options) {
- if (!options || !options.width || !options.height) {
- throw new Error('missing options when initializing simulation');
- }
-
- /** Creating the simulation */
- if (!this.simulation) {
- const ticker = this.ticker;
-
- // Creating the force simulation and defining the charges
- this.simulation = d3.forceSimulation()
- .force('charge',
- d3.forceManyBody().strength(this.charges.bind(this)))
- // .distanceMin(100).distanceMax(500))
- .force('gravity',
- d3.forceManyBody().strength(FORCES.GRAVITY))
- .force('friction',
- d3.forceManyBody().strength(FORCES.FRICTION));
-
- // Connecting the d3 ticker to an angular event emitter
- this.simulation.on('tick', function () {
- ticker.emit(this);
- });
-
- this.initNodes();
- // this.initLinks();
- }
-
- /** Updating the central force of the simulation */
- this.simulation.force('centers', d3.forceCenter(options.width / 2, options.height / 2));
-
- /** Restarting the simulation internal timer */
- this.simulation.restart();
+ strength(link: Link) {
+ const linkType = link.type;
+ this.log.debug('Link type', linkType, LINK_STRENGTH[linkType]);
+ return LINK_STRENGTH[linkType] || LINK_STRENGTH._def_;
}
stopSimulation() {
@@ -143,8 +125,8 @@
this.log.debug('Simulation stopped');
}
- restartSimulation() {
- this.simulation.restart();
- this.log.debug('Simulation restarted');
+ public restartSimulation(alpha: number = 0.3) {
+ this.simulation.alpha(alpha).restart();
+ this.log.debug('Simulation restarted. Alpha:', alpha);
}
}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
index ce22e99..b4e62cc 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
@@ -107,6 +107,8 @@
export interface DeviceProps {
latitude: number;
longitude: number;
+ gridX: number;
+ gridY: number;
name: string;
locType: LocationType;
uiType: string;
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/zoomable.directive.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/zoomable.directive.ts
index 9564444..8c3707b 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/zoomable.directive.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/zoomable.directive.ts
@@ -54,8 +54,10 @@
const zoomed = () => {
const transform = d3.event.transform;
- container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
- this.updateZoomState(<TopoZoomPrefs>{tx: transform.x, ty: transform.y, sc: transform.k});
+ if (transform) {
+ container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
+ this.updateZoomState(<TopoZoomPrefs>{tx: transform.x, ty: transform.y, sc: transform.k});
+ }
};
this.zoom = d3.zoom().on('zoom', zoomed);
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.spec.ts
new file mode 100644
index 0000000..d970993
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.spec.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { LayoutService } from './layout.service';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {FnService, LogService} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+describe('LayoutService', () => {
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let ar: ActivatedRoute;
+ let fs: FnService;
+ let mockWindow: Window;
+
+ beforeEach(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'info']);
+ ar = new MockActivatedRoute({'debug': 'TestService'});
+ mockWindow = <any>{
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ },
+ location: <any>{
+ hostname: 'foo',
+ host: 'foo',
+ port: '80',
+ protocol: 'http',
+ search: { debug: 'true' },
+ href: 'ws://foo:123/onos/ui/websock/path',
+ absUrl: 'ws://foo:123/onos/ui/websock/path'
+ }
+ };
+ fs = new FnService(ar, logSpy, mockWindow);
+
+ TestBed.configureTestingModule({
+ providers: [LayoutService,
+ { provide: FnService, useValue: fs},
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: 'Window', useFactory: (() => mockWindow ) }
+ ]
+ });
+ logServiceSpy = TestBed.get(LogService);
+ });
+
+ it('should be created', () => {
+ const service: LayoutService = TestBed.get(LayoutService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.ts
new file mode 100644
index 0000000..2ed795a
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layout.service.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Injectable } from '@angular/core';
+import {LogService, WebSocketService} from 'gui2-fw-lib';
+
+export enum LayoutType {
+ LAYOUT_DEFAULT = 'default',
+ LAYOUT_ACCESS = 'access'
+}
+
+/**
+ * ONOS GUI - Layout service - connects to the Layout UI Extension app
+ */
+@Injectable()
+export class LayoutService {
+
+ constructor(
+ protected log: LogService,
+ protected wss: WebSocketService
+ ) {
+ this.log.debug('LayoutService constructed');
+ }
+
+ /**
+ * tell the server we want a new layout
+ * @param type The type of layout we want
+ */
+ changeLayout(type: LayoutType): void {
+ this.wss.sendEvent('doLayout', {
+ type: type
+ });
+ this.log.debug('Layout changed to', type);
+ }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html
index 623c425..69d4557 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html
@@ -74,4 +74,12 @@
<onos-icon [iconSize]="25" iconId="m_cycleGridDisplay" [toolTip]="lionFn('tbtt_cyc_grid_display')" classes="button"></onos-icon>
</div>
</div>
-</div>
\ No newline at end of file
+ <div class="tbar-row">
+ <div class="button" id="toolbar-topo2-toolbar-topo2-layout-default" (click)="buttonClicked('layout-default-btn')">
+ <onos-icon iconSize="25" iconId="m_fiberSwitch" toolTip="Default (force-based) layout" classes="button"></onos-icon>
+ </div>
+ <div class="button" id="toolbar-topo2-toolbar-topo2-layout-access" (click)="buttonClicked('layout-access-btn')">
+ <onos-icon iconSize="25" iconId="m_disjointPaths" toolTip="Access layout - separate service leafs" classes="button"></onos-icon>
+ </div>
+ </div>
+</div>
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts
index 0615530..ce7b90b 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts
@@ -39,6 +39,8 @@
export const CANCEL_TRAFFIC = 'cancel-traffic';
export const ALL_TRAFFIC = 'all-traffic';
export const QUICKHELP_BTN = 'quickhelp-btn';
+export const LAYOUT_DEFAULT_BTN = 'layout-default-btn';
+export const LAYOUT_ACCESS_BTN = 'layout-access-btn';
/*
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology.service.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology.service.ts
index 2c5d777..509a468 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology.service.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology.service.ts
@@ -75,7 +75,7 @@
<ModelEventType><unknown>(ModelEventType[event.type]), // Number based enum
<ModelEventMemo>(event.memo), // String based enum
event.subject, event.data);
- this.log.debug('Region Data updated from WSS as topo2UiModelEvent', force.regionData);
+ this.log.debug('Region Data updated from WSS as topo2UiModelEvent', event.subject, event.data);
}
],
// topo2Highlights is handled by TrafficService
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
index b4d579d..c7b6c15 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
@@ -50,6 +50,7 @@
import {DeviceNodeSvgComponent} from '../layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component';
import {SubRegionNodeSvgComponent} from '../layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component';
import {HostNodeSvgComponent} from '../layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component';
+import {LayoutService} from '../layout.service';
class MockActivatedRoute extends ActivatedRoute {
@@ -105,6 +106,8 @@
class MockTrafficService {}
+class MockLayoutService {}
+
class MockPrefsService {
listeners: ((data) => void)[] = [];
@@ -200,6 +203,7 @@
{ provide: HttpClient, useClass: MockHttpClient },
{ provide: TopologyService, useClass: MockTopologyService },
{ provide: TrafficService, useClass: MockTrafficService },
+ { provide: LayoutService, useClass: MockLayoutService },
{ provide: IconService, useClass: MockIconService },
{ provide: PrefsService, useClass: MockPrefsService },
{ provide: KeysService, useClass: MockKeysService },
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts
index d7055a4..9f2a08b 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.ts
@@ -22,14 +22,17 @@
} from '@angular/core';
import * as d3 from 'd3';
import {
- FnService, IconService,
+ FnService,
+ IconService,
KeysService,
- KeysToken, LionService,
+ KeysToken,
+ LionService,
LogService,
PrefsService,
SvgUtilService,
+ TopoZoomPrefs,
WebSocketService,
- TopoZoomPrefs, ZoomUtils
+ ZoomUtils
} from 'gui2-fw-lib';
import {InstanceComponent} from '../panel/instance/instance.component';
import {DetailsComponent} from '../panel/details/details.component';
@@ -43,15 +46,29 @@
UiElement
} from '../layer/forcesvg/models';
import {
- INSTANCE_TOGGLE, SUMMARY_TOGGLE, DETAILS_TOGGLE,
- HOSTS_TOGGLE, OFFLINE_TOGGLE, PORTS_TOGGLE,
- BKGRND_TOGGLE, CYCLELABELS_BTN, CYCLEHOSTLABEL_BTN,
- CYCLEGRIDDISPLAY_BTN, RESETZOOM_BTN, EQMASTER_BTN,
- CANCEL_TRAFFIC, ALL_TRAFFIC, QUICKHELP_BTN, BKGRND_SELECT
+ ALL_TRAFFIC,
+ BKGRND_SELECT,
+ BKGRND_TOGGLE,
+ CANCEL_TRAFFIC,
+ CYCLEGRIDDISPLAY_BTN,
+ CYCLEHOSTLABEL_BTN,
+ CYCLELABELS_BTN,
+ DETAILS_TOGGLE,
+ EQMASTER_BTN,
+ HOSTS_TOGGLE,
+ INSTANCE_TOGGLE,
+ LAYOUT_ACCESS_BTN,
+ LAYOUT_DEFAULT_BTN,
+ OFFLINE_TOGGLE,
+ PORTS_TOGGLE,
+ QUICKHELP_BTN,
+ RESETZOOM_BTN,
+ SUMMARY_TOGGLE
} from '../panel/toolbar/toolbar.component';
import {TrafficService} from '../traffic.service';
import {ZoomableDirective} from '../layer/zoomable.directive';
import {MapObject} from '../layer/maputils';
+import {LayoutService, LayoutType} from '../layout.service';
const TOPO2_PREFS = 'topo2_prefs';
const TOPO_MAPID_PREFS = 'topo_mapid';
@@ -165,6 +182,7 @@
protected trs: TrafficService,
protected is: IconService,
private lion: LionService,
+ private layout: LayoutService,
@Inject('Window') public window: any,
) {
if (this.lion.ubercache.length === 0) {
@@ -199,6 +217,8 @@
this.is.loadIconDef('groupTable');
this.is.loadIconDef('meterTable');
this.is.loadIconDef('triangleUp');
+ this.is.loadIconDef('m_disjointPaths');
+ this.is.loadIconDef('m_fiberSwitch');
this.log.debug('Topology component constructed');
}
@@ -350,6 +370,12 @@
case QUICKHELP_BTN:
this.ks.quickHelpShown = true;
break;
+ case LAYOUT_DEFAULT_BTN:
+ this.layout.changeLayout(LayoutType.LAYOUT_DEFAULT);
+ break;
+ case LAYOUT_ACCESS_BTN:
+ this.layout.changeLayout(LayoutType.LAYOUT_ACCESS);
+ break;
default:
this.log.warn('Unhandled Toolbar action', name);
}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.spec.ts
index 8b2a736..6029cca 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.spec.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.spec.ts
@@ -19,7 +19,6 @@
import {FnService, LogService} from 'gui2-fw-lib';
import {ActivatedRoute, Params} from '@angular/router';
import {of} from 'rxjs';
-import {TopologyService} from './topology.service';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
@@ -56,7 +55,7 @@
fs = new FnService(ar, logSpy, mockWindow);
TestBed.configureTestingModule({
- providers: [TopologyService,
+ providers: [TrafficService,
{ provide: FnService, useValue: fs},
{ provide: LogService, useValue: logSpy },
{ provide: ActivatedRoute, useValue: ar },
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.ts
index b6d2b85..9aff0c7 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/traffic.service.ts
@@ -39,9 +39,7 @@
/**
* ONOS GUI -- Traffic Service Module.
*/
-@Injectable({
- providedIn: 'root'
-})
+@Injectable()
export class TrafficService {
private handlers: string[] = [];
private openListener: any;