Added d3 force graph to GUI2 topology
Change-Id: I6860472efaf51ea27fad74e630e687f0c6abad3d
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
index 69392cf..6a900fb 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
@@ -41,6 +41,7 @@
['close', 'xClose'],
['m_ports', 'm_ports'],
+ ['m_switch', 'm_switch'],
['topo', 'topo'],
['bird', 'bird'],
diff --git a/web/gui2/BUILD b/web/gui2/BUILD
index 7ce7cf7..fb6a2c5 100644
--- a/web/gui2/BUILD
+++ b/web/gui2/BUILD
@@ -17,9 +17,9 @@
"""
Rules to build the ONOS GUI 2
- The GUI2 Angular 6 elements are built here with Angular CLI 'ng'
+ The GUI2 Angular 7 elements are built here with Angular CLI 'ng'
Some work is being done in the Bazel community to integrate Bazel and
- Angular 6, (Angular Buildtools Convergence -
+ Angular 7, (Angular Buildtools Convergence -
https://docs.google.com/document/d/1OlyiUnoTirUj4gecGxJeZBcjHcFr36RvLsvpBl2mxA8/preview)
but it is in the very early stages (Aug'18) and not yet fit
for production and at present it works as a replacement for Angular CLI
@@ -103,7 +103,7 @@
See bazel-genfiles/web/gui2/onos-gui2-ng-build-prod.log for details of the Angular CLI output
To avoid the overhead of having several "npm install" invocations, we just do
- it once in the //web/gui2-fw-lib which is really the core for the whole Angular 6
+ it once in the //web/gui2-fw-lib which is really the core for the whole Angular 7
structure in ONOS. This copies files in to node_modules, but because the gui2-fw-lib
has not been generated at that time we copy it in separately below with the 'tar' cmd
and then 'mv'
@@ -151,9 +151,8 @@
" export PATH=$$ROOT/$$(dirname $${NODE}):$$ROOT/web/gui2/node_modules/@angular/cli/bin:$$PATH &&" +
" node -v > ../../$(location onos-gui2-ng-build-prod.log) &&" +
" npm -v >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
- " ng -v >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
- # Build it in production mode - optimization is turned off because of Angular CLI 6.0.x bug https://github.com/angular/angular-cli/issues/7799
- " ng build --extract-css --prod --optimization=false --preserve-symlinks" +
+ " ng version >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
+ " ng build --extract-css --prod --preserve-symlinks" +
" --base-href /onos/ui2/ --deploy-url /onos/ui2/ >> $$ROOT/$(location onos-gui2-ng-build-prod.log) 2>&1 ||" +
" if [ $$? -eq 0 ]; then echo 'Successfully ran build';" +
" else " +
@@ -163,7 +162,7 @@
" exit 1;" +
" fi;" +
" cd src/main/webapp/dist && jar Mcf $$ROOT/$(location onos-gui2-ng-build.jar) .",
- message = "Angular CLI 6 build",
+ message = "Angular CLI 7 build",
)
"""
@@ -213,7 +212,7 @@
" export PATH=$$ROOT/$$(dirname $${NODE}):$$ROOT/web/gui2/node_modules/@angular/cli/bin:$$PATH &&" +
" node -v > ../../$(location onos-gui2-ng-ver.log) &&" +
" npm -v >> ../../$(location onos-gui2-ng-ver.log) &&" +
- " ng -v >> ../../$(location onos-gui2-ng-ver.log) &&" +
+ " ng version >> ../../$(location onos-gui2-ng-ver.log) &&" +
" ng lint > ../../$(location onos-gui2-ng-lint.log);" +
" if [ -f /usr/bin/chromium-browser ]; then " + # Add to this for Mac and Chrome
" export CHROME_BIN=/usr/bin/chromium-browser; " +
@@ -236,7 +235,7 @@
#" tail -n 100 ../../$(location onos-gui2-ng-test.log) >&2;" +
" exit 1;" +
" fi;",
- message = "Angular CLI 6 lint and test",
+ message = "Angular CLI 7 lint and test",
)
"""
diff --git a/web/gui2/README.md b/web/gui2/README.md
index c3cbed3..8f97312 100644
--- a/web/gui2/README.md
+++ b/web/gui2/README.md
@@ -73,9 +73,9 @@
If you make any changes here or are using it for the first time it will need to be built
```text
-cd ~/onos/web/gui2-fw-lib && \
+pushd ~/onos/web/gui2-fw-lib && \
ng build gui2-fw-lib && \
-pushd dist/gui2-fw-lib && \
+cd dist/gui2-fw-lib && \
npm pack && \
popd
```
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index 604c7e4..7cf1766 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -4704,8 +4704,8 @@
}
},
"fm-gui2-lib": {
- "version": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-1.15.0.tgz",
- "integrity": "sha512-5qRtSAZDtFeafTTaJ9UHwyP+E7Sj6nx5RrAORCzt1DswqI4YFZqW1nSLa9co0hWqi9lem5sKspp+BH787TiXwQ==",
+ "version": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-2.0.0.tgz",
+ "integrity": "sha512-AYRLoCF5jSYYAvi6fYJCepKtCm9Woregjte79YTQvgzL/+2AvTtXf9m2ezj/wmUnZOfFH6wTqVdW1OLzK/TQiQ==",
"requires": {
"tslib": "1.9.3"
}
@@ -5559,8 +5559,8 @@
"dev": true
},
"gui2-fw-lib": {
- "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
- "integrity": "sha512-8uT9TL5Ye4uRSkseDyI6fsGHb1d5/SwjSYeTVhftmyKIgF4O2HywOa0gdFopx6Ft+JjhhP5k1n+WAPvoEjnXKA==",
+ "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
+ "integrity": "sha512-wAhrApE0c7FWqRDgB/8itVppmsd1Z4T+j8woxmDzkYwIkEqSvc7pvF3+QIOF0IQDkRCU2P31Ms1AtZ5cQliXTA==",
"requires": {
"tslib": "1.9.3"
}
diff --git a/web/gui2/src/main/webapp/app/onos-routing.module.ts b/web/gui2/src/main/webapp/app/onos-routing.module.ts
index 962f5a3..77bce5a 100644
--- a/web/gui2/src/main/webapp/app/onos-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/onos-routing.module.ts
@@ -89,7 +89,7 @@
},
{
path: '',
- redirectTo: 'device', // Default to devices view - change to topo in future
+ redirectTo: 'topo2', // Default to Topology view
pathMatch: 'full'
}
];
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
index 389ec9c..d745ce8 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
@@ -13,8 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g onos-mapsvg />
-<svg:g>
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg />
+<svg:g xmlns:svg="http://www.w3.org/2000/svg">
<svg:text>Layout: {{ layoutData.id }} {{ layoutData.bgDesc }}</svg:text>
<svg:text>Region: {{ layoutData.region }} {{ layoutData.regionName }}</svg:text>
<svg:text>Parent {{ layoutData.parent }}</svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
index ff6f99a..e07f304 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
@@ -44,6 +44,7 @@
* Enum of the topo2CurrentRegion location type from Location below
*/
export enum LocationType {
+ NONE = 'none',
GEO = 'geo',
GRID = 'grid'
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts
new file mode 100644
index 0000000..bb6b4d0
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-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 { DraggableDirective } from './draggable.directive';
+import {inject, TestBed} from '@angular/core/testing';
+import {ElementRef} from '@angular/core';
+
+export class MockElementRef extends ElementRef {
+ nativeElement = {};
+}
+
+describe('DraggableDirective', () => {
+ let mockWindow: Window;
+
+ beforeEach(() => {
+ mockWindow = <any>{
+ navigator: {
+ userAgent: 'HeadlessChrome',
+ vendor: 'Google Inc.'
+ }
+ };
+
+ TestBed.configureTestingModule({
+ providers: [DraggableDirective,
+ { provide: 'Window', useFactory: (() => mockWindow ) },
+ { provide: ElementRef, useValue: mockWindow }
+ ]
+ });
+ });
+
+ it('should create an instance', inject([DraggableDirective], (directive: DraggableDirective) => {
+
+ expect(directive).toBeTruthy();
+ }));
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
new file mode 100644
index 0000000..88faa37
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-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 { Directive, ElementRef, Input, OnChanges } from '@angular/core';
+import { ForceDirectedGraph, Node } from '../models';
+import * as d3 from 'd3';
+
+@Directive({
+ selector: '[onosDraggableNode]'
+})
+export class DraggableDirective implements OnChanges {
+ @Input() draggableNode: Node;
+ @Input() draggableInGraph: ForceDirectedGraph;
+
+ constructor(
+ private _element: ElementRef
+ ) {
+ }
+
+ ngOnChanges() {
+ this.applyDraggableBehaviour(
+ this._element.nativeElement,
+ this.draggableNode,
+ this.draggableInGraph);
+ }
+
+ /**
+ * A method to bind a draggable behaviour to an svg element
+ */
+ applyDraggableBehaviour(element, node: Node, graph: ForceDirectedGraph) {
+ const d3element = d3.select(element);
+
+ function started() {
+ /** Preventing propagation of dragstart to parent elements */
+ d3.event.sourceEvent.stopPropagation();
+
+ if (!d3.event.active) {
+ graph.simulation.alphaTarget(0.3).restart();
+ }
+
+ d3.event.on('drag', dragged).on('end', ended);
+
+ function dragged() {
+ node.fx = d3.event.x;
+ node.fy = d3.event.y;
+ }
+
+ function ended() {
+ if (!d3.event.active) {
+ graph.simulation.alphaTarget(0);
+ }
+
+ node.fx = null;
+ node.fy = null;
+ }
+ }
+
+ d3element.call(d3.drag()
+ .on('start', started));
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
index 0c2f0aa..f02b3a1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
@@ -13,10 +13,40 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g class="topo2-force">
- <svg:g class="topo2-links" />
- <svg:g class="topo2-linkLabels" />
- <svg:g class="topo2-numLinkLabels" />
- <svg:g class="topo2-nodes" />
- <svg:g class="topo2-portLabels" />
+<svg:g class="topo2-force" xmlns:svg="http://www.w3.org/2000/svg">
+ <svg:g id="new-zoom-layer">
+ <svg:g class="topo2-links">
+ <svg:g onos-linkvisual [link]="link" *ngFor="let link of regionData.links">
+ </svg:g>
+ </svg:g>
+ <svg:g class="topo2-linkLabels" />
+ <svg:g class="topo2-numLinkLabels" />
+ <svg:g class="topo2-nodes">
+ <svg:g onos-devicenodesvg [device]="device"
+ *ngFor="let device of regionData.devices[visibleLayerIdx()]"
+ onosDraggableNode [draggableNode]="device" [draggableInGraph]="graph"
+ (selectedEvent)="updateSelected($event)">
+ </svg:g>
+ <svg:g onos-hostnodesvg [host]="host"
+ *ngFor="let host of regionData.hosts[visibleLayerIdx()]"
+ onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph">
+ </svg:g>
+ <svg:g onos-subregionnodesvg [subRegion]="subRegion"
+ *ngFor="let subRegion of regionData.subregions"
+ onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph">
+ </svg:g>
+ </svg:g>
+ </svg:g>
+ <!--<svg:g class="topo2-portLabels">-->
+ <!--<!–TODO make each of these in to a component that can be inserted –>-->
+ <!--<svg:g *ngIf="selectedLink !== null" id="topo-port-src" class="portLabel" opacity="1">-->
+ <!--<rect x="0" y="0" width="1" height="1" [ngStyle]="{'transform': 'scale(1)'}"></rect>-->
+ <!--<text dy="0.3em" [ngStyle]="{'transform': 'scale(1)'}">{{ selectedLink.portA }}</text>-->
+ <!--</svg:g>-->
+
+ <!--<svg:g *ngIf="selectedLink !== null" id="topo-port-tgt" class="portLabel" opacity="1">-->
+ <!--<rect x="0" y="0" width="1" height="1" [ngStyle]="{'transform': 'scale(1)'}"></rect>-->
+ <!--<text dy="0.3em" [ngStyle]="{'transform': 'scale(1)'}">{{ selectedLink.portA }}</text>-->
+ <!--</svg:g>-->
+ <!--</svg:g>-->
</svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
index 505c528..ffbe667 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
@@ -16,21 +16,71 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ForceSvgComponent } from './forcesvg.component';
+import {IconService, LogService} from 'gui2-fw-lib';
+import {
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent, LinkVisualComponent,
+ SubRegionNodeSvgComponent
+} from './visuals';
+import {DraggableDirective} from './draggable/draggable.directive';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+class MockIconService {
+ loadIconDef() { }
+}
describe('ForceSvgComponent', () => {
+ let ar: MockActivatedRoute;
+ let windowMock: Window;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
let component: ForceSvgComponent;
let fixture: ComponentFixture<ForceSvgComponent>;
beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+ windowMock = <any>{
+ 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'
+ }
+ };
+
TestBed.configureTestingModule({
- declarations: [ ForceSvgComponent ]
+ declarations: [
+ ForceSvgComponent,
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent,
+ SubRegionNodeSvgComponent,
+ LinkVisualComponent,
+ DraggableDirective
+ ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: IconService, useClass: MockIconService },
+ ]
})
.compileComponents();
+ logServiceSpy = TestBed.get(LogService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ForceSvgComponent);
- component = fixture.componentInstance;
+ component = fixture.debugElement.componentInstance;
fixture.detectChanges();
});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
index d159f06..97c9e1e 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
@@ -13,143 +13,189 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Component, Input, OnInit} from '@angular/core';
-import { LocationType } from '../backgroundsvg/backgroundsvg.component';
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ HostListener,
+ Input,
+ OnChanges,
+ OnInit,
+ Output, QueryList, SimpleChange,
+ SimpleChanges, ViewChildren
+} from '@angular/core';
+import {IconService, LogService} from 'gui2-fw-lib';
+import {
+ Device,
+ ForceDirectedGraph,
+ Host,
+ LabelToggle,
+ LayerType,
+ Region,
+ RegionLink,
+ SubRegion
+} from './models';
+import {DeviceNodeSvgComponent} from './visuals';
-/**
- * Enum of the topo2CurrentRegion node type from SubRegion below
- */
-export enum NodeType {
- REGION = 'region',
- DEVICE = 'device'
-}
-
-/**
- * Enum of the topo2CurrentRegion layerOrder from Region below
- */
-export enum LayerOrder {
- LAYER_OPTICAL = 'opt',
- LAYER_PACKET = 'pkt',
- LAYER_DEFAULT = 'def'
-}
-
-/**
- * model of the topo2CurrentRegion location from SubRegion below
- */
-export interface Location {
- locType: LocationType;
- latOrY: number;
- longOrX: number;
-}
-
-/**
- * model of the topo2CurrentRegion props from SubRegion below
- */
-export interface RegionProps {
- latitude: number;
- longitude: number;
- name: string;
- peerLocations: string;
-}
-
-/**
- * model of the topo2CurrentRegion subregion from Region below
- */
-export interface SubRegion {
- id: string;
- location: Location;
- nDevs: number;
- nHosts: number;
- name: string;
- nodeType: NodeType;
- props: RegionProps;
-}
-
-export enum LinkType {
- UiRegionLink,
- UiDeviceLink
-}
-
-/**
- * model of the topo2CurrentRegion region rollup from Region below
- */
-export interface RegionRollup {
- id: string;
- epA: string;
- epB: string;
- portA: string;
- portB: string;
- type: LinkType;
-}
-
-/**
- * model of the topo2CurrentRegion region link from Region below
- */
-export interface RegionLink {
- id: string;
- epA: string;
- epB: string;
- rollup: RegionRollup[];
- type: LinkType;
-}
-
-/**
- * model of the topo2CurrentRegion device props from Device below
- */
-export interface DeviceProps {
- latitude: number;
- longitude: number;
- name: string;
- locType: LocationType;
-}
-
-export interface Device {
- id: string;
- layer: LayerOrder;
- location: LocationType;
- master: string;
- nodeType: NodeType;
- online: boolean;
- props: DeviceProps;
- type: string;
-}
-
-/**
- * model of the topo2CurrentRegion WebSocket response
- */
-export interface Region {
- note?: string;
- id: string;
- devices: Device[][];
- hosts: Object[];
- links: RegionLink[];
- layerOrder: LayerOrder[];
- peerLocations?: Location[];
- subregions: SubRegion[];
-}
-
-/**
- * model of the topo2PeerRegions WebSocket response
- */
-export interface Peer {
- peers: SubRegion[];
-}
/**
* ONOS GUI -- Topology Forces Graph Layer View.
+ *
+ * The regionData is set by Topology Service on WebSocket topo2CurrentRegion callback
+ * This drives the whole Force graph
*/
@Component({
selector: '[onos-forcesvg]',
templateUrl: './forcesvg.component.html',
- styleUrls: ['./forcesvg.component.css']
+ styleUrls: ['./forcesvg.component.css'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class ForceSvgComponent implements OnInit {
+export class ForceSvgComponent implements OnInit, OnChanges {
@Input() onosInstMastership: string = '';
- regionData: Region;
+ @Input() visibleLayer: LayerType = LayerType.LAYER_DEFAULT;
+ @Output() linkSelected = new EventEmitter<RegionLink>();
+ @Output() selectedNodeEvent = new EventEmitter<Device>();
+ @Input() selectedLink: RegionLink = null;
+ private graph: ForceDirectedGraph;
- constructor() { }
+ @Input() regionData: Region = <Region>{devices: [ [], [], [] ], hosts: [ [], [], [] ], links: []};
+ private _options: { width, height } = { width: 800, height: 600 };
- ngOnInit() {
+ @ViewChildren(DeviceNodeSvgComponent) devices: QueryList<DeviceNodeSvgComponent>;
+
+ @HostListener('window:resize', ['$event'])
+ onResize(event) {
+ this.graph.initSimulation(this.options);
+ this.log.debug('Simulation reinit after resize', event);
}
+ constructor(
+ protected log: LogService,
+ protected is: IconService,
+ private ref: ChangeDetectorRef
+ ) {
+ this.selectedLink = null;
+ this.log.debug('ForceSvgComponent constructed');
+ }
+
+ /**
+ * After the component is initialized create the Force simulation
+ */
+ ngOnInit() {
+ // Receiving an initialized simulated graph from our custom d3 service
+ this.graph = new ForceDirectedGraph(this.options);
+
+ /** Binding change detection check on each tick
+ * This along with an onPush change detection strategy should enforce checking only when relevant!
+ * This improves scripting computation duration in a couple of tests I've made, consistently.
+ * Also, it makes sense to avoid unnecessary checks when we are dealing only with simulations data binding.
+ */
+ this.graph.ticker.subscribe((simulation) => {
+ // this.log.debug("Force simulation has ticked", simulation);
+ this.ref.markForCheck();
+ });
+ this.log.debug('ForceSvgComponent initialized - waiting for nodes and links');
+
+ this.is.loadIconDef('m_switch');
+ }
+
+ /**
+ * When any one of the inputs get changed by a containing component, this gets called automatically
+ * In addition this is called manually by topology.service when a response
+ * is received from the WebSocket from the server
+ *
+ * The Devices, Hosts and SubRegions are all added to the Node list for the simulation
+ * The Links are added to the Link list of the simulation.
+ * Before they are added the Links are associated with Nodes based on their endPt
+ *
+ * @param changes - a list of changed @Input(s)
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['regionData']) {
+ const devices: Device[] =
+ changes['regionData'].currentValue.devices[this.visibleLayerIdx()];
+ const hosts: Host[] =
+ changes['regionData'].currentValue.hosts[this.visibleLayerIdx()];
+ const subRegions: SubRegion[] = changes['regionData'].currentValue.subRegion;
+ this.graph.nodes = [];
+ if (devices) {
+ this.graph.nodes = devices;
+ }
+ if (hosts) {
+ this.graph.nodes = this.graph.nodes.concat(hosts);
+ }
+ if (subRegions) {
+ this.graph.nodes = this.graph.nodes.concat(subRegions);
+ }
+
+ // Associate the endpoints of each link with a real node
+ this.graph.links = [];
+ for (const linkIdx of Object.keys(this.regionData.links)) {
+ this.regionData.links[linkIdx].source =
+ this.graph.nodes.find((node) =>
+ node.id === this.regionData.links[linkIdx].epA);
+ this.regionData.links[linkIdx].target =
+ this.graph.nodes.find((node) =>
+ node.id === this.regionData.links[linkIdx].epB);
+ this.regionData.links[linkIdx].index = Number(linkIdx);
+ }
+
+ this.graph.links = this.regionData.links;
+
+ this.graph.initSimulation(this.options);
+ this.graph.initNodes();
+ this.log.debug('ForceSvgComponent input changed',
+ this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
+ }
+ }
+
+ /**
+ * Get the index of LayerType so it can drive the visibility of nodes and
+ * hosts on layers
+ */
+ visibleLayerIdx(): number {
+ const layerKeys: string[] = Object.keys(LayerType);
+ for (const idx in layerKeys) {
+ if (LayerType[layerKeys[idx]] === this.visibleLayer) {
+ return Number(idx);
+ }
+ }
+ return -1;
+ }
+
+ selectLink(link: RegionLink): void {
+ this.selectedLink = link;
+ this.linkSelected.emit(link);
+ }
+
+ get options() {
+ return this._options = {
+ width: window.innerWidth,
+ height: window.innerHeight
+ };
+ }
+
+ updateDeviceLabelToggle() {
+ this.devices.forEach((d) => {
+ const old: LabelToggle = d.labelToggle;
+ const next = LabelToggle.next(old);
+ d.ngOnChanges({'labelToggle': new SimpleChange(old, next, false)});
+ });
+ }
+
+ updateSelected(selectedNodeId: string): void {
+ this.log.debug('Device selected', selectedNodeId);
+ this.devices.filter((d) => d.device.id !== selectedNodeId).forEach((d) => {
+ d.deselect();
+ });
+ const selectedDevice: DeviceNodeSvgComponent =
+ (this.devices.find((d) => d.device.id === selectedNodeId));
+ if (selectedDevice) {
+ this.selectedNodeEvent.emit(selectedDevice.device);
+ } else {
+ this.selectedNodeEvent.emit();
+ }
+ }
}
+
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
new file mode 100644
index 0000000..767e094
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018-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 {ForceDirectedGraph, Options} from './force-directed-graph';
+import {Node} from './node';
+import {Link} from './link';
+
+export class TestNode extends Node {
+ constructor(id: string) {
+ super(id);
+ }
+}
+
+export class TestLink extends Link {
+ constructor(source: Node, target: Node) {
+ super(source, target);
+ }
+}
+
+/**
+ * ONOS GUI -- ForceDirectedGraph - Unit Tests
+ */
+describe('ForceDirectedGraph', () => {
+ let fdg: ForceDirectedGraph;
+ const options: Options = {width: 1000, height: 1000};
+
+ beforeEach(() => {
+ const nodes: Node[] = [];
+ const links: Link[] = [];
+ fdg = new ForceDirectedGraph(options);
+
+ for (let i = 0; i < 10; i++) {
+ const newNode: TestNode = new TestNode('id' + i);
+ nodes.push(newNode);
+ }
+ for (let j = 1; j < 10; j++) {
+ const newLink = new TestLink(nodes[0], nodes[j]);
+ links.push(newLink);
+ }
+ fdg.nodes = nodes;
+ fdg.links = links;
+ fdg.initSimulation(options);
+ fdg.initNodes();
+
+ });
+
+ afterEach(() => {
+ fdg.stopSimulation();
+ fdg.nodes = [];
+ fdg.links = [];
+ fdg.initSimulation(options);
+ });
+
+ it('should be created', () => {
+ expect(fdg).toBeTruthy();
+ });
+
+ it('should have simulation', () => {
+ expect(fdg.simulation).toBeTruthy();
+ });
+
+ it('should have 10 nodes', () => {
+ expect(fdg.nodes.length).toEqual(10);
+ });
+
+ it('should have 10 links', () => {
+ expect(fdg.links.length).toEqual(9);
+ });
+
+ // TODO fix these up to listen for tick
+ // it('nodes should not be at zero', () => {
+ // expect(nodes[0].x).toBeGreaterThan(0);
+ // });
+ // it('ticker should emit', () => {
+ // let tickMe = jasmine.createSpy("tickMe() spy");
+ // fdg.ticker.subscribe((simulation) => tickMe());
+ // expect(tickMe).toHaveBeenCalled();
+ // });
+
+ // it('init links chould be called ', () => {
+ // spyOn(fdg, 'initLinks');
+ // // expect(fdg).toBeTruthy();
+ // fdg.initSimulation(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/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
new file mode 100644
index 0000000..3db25ae
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018-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 { EventEmitter } from '@angular/core';
+import { Link } from './link';
+import { Node } from './node';
+import * as d3 from 'd3';
+
+const FORCES = {
+ LINKS: 1 / 50,
+ COLLISION: 1,
+ CHARGE: -1
+};
+
+export interface Options {
+ width: number;
+ height: number;
+}
+
+export class ForceDirectedGraph {
+ public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
+ public simulation: d3.Simulation<any, any>;
+
+ public nodes: Node[] = [];
+ public links: Link[] = [];
+
+ constructor(options: Options) {
+ this.initSimulation(options);
+ }
+
+ initNodes() {
+ if (!this.simulation) {
+ throw new Error('simulation was not initialized yet');
+ }
+
+ 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',
+ d3.forceLink(this.links)
+ .strength(FORCES.LINKS)
+ );
+ }
+
+ 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(FORCES.CHARGE)
+ );
+
+ // 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();
+ }
+
+ stopSimulation() {
+ this.simulation.stop();
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts
new file mode 100644
index 0000000..36fd2e7
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+export * from './node';
+export * from './link';
+export * from './regions';
+
+export * from './force-directed-graph';
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
new file mode 100644
index 0000000..e4d7768
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018-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 { Node } from './node';
+import * as d3 from 'd3';
+
+export enum LinkType {
+ UiRegionLink,
+ UiDeviceLink,
+ UiEdgeLink
+}
+
+/**
+ * model of the topo2CurrentRegion region rollup from Region below
+ *
+ */
+export interface RegionRollup {
+ id: string;
+ epA: string;
+ epB: string;
+ portA: string;
+ portB: string;
+ type: LinkType;
+}
+
+/**
+ * Implementing SimulationLinkDatum interface into our custom Link class
+ */
+export class Link implements d3.SimulationLinkDatum<Node> {
+ // Optional - defining optional implementation properties - required for relevant typing assistance
+ index?: number;
+
+ // Must - defining enforced implementation properties
+ source: Node;
+ target: Node;
+
+ constructor(source, target) {
+ this.source = source;
+ this.target = target;
+ }
+}
+
+/**
+ * model of the topo2CurrentRegion region link from Region below
+ */
+export class RegionLink extends Link {
+ id: string; // The id of the link in the format epA/portA~epB/portB
+ epA: string; // The name of the device or host at one end
+ epB: string; // The name of the device or host at the other end
+ portA: string; // The number of the port at one end
+ portB: string; // The number of the port at the other end
+ rollup: RegionRollup[]; // Links in sub regions represented by this one link
+ type: LinkType;
+
+ constructor(type: LinkType, nodeA: Node, nodeB: Node) {
+ super(nodeA, nodeB);
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
new file mode 100644
index 0000000..d19de88
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018-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 * as d3 from 'd3';
+import {LocationType} from '../../backgroundsvg/backgroundsvg.component';
+import {
+ LayerType,
+ Location,
+ NodeType,
+ RegionProps
+} from './regions';
+
+/**
+ * Toggle state for how labels should be displayed
+ */
+export enum LabelToggle {
+ NONE,
+ ID,
+ NAME
+}
+
+export namespace LabelToggle {
+ export function next(current: LabelToggle) {
+ if (current === LabelToggle.NONE) {
+ return LabelToggle.ID;
+ } else if (current === LabelToggle.ID) {
+ return LabelToggle.NAME;
+ } else if (current === LabelToggle.NAME) {
+ return LabelToggle.NONE;
+ }
+ }
+}
+
+/**
+ * model of the topo2CurrentRegion device props from Device below
+ */
+export interface DeviceProps {
+ latitude: number;
+ longitude: number;
+ name: string;
+ locType: LocationType;
+}
+
+/**
+ * model of the topo2CurrentRegion Loc part of the MetaUi below
+ */
+export interface LocMeta {
+ lng: number;
+ lat: number;
+}
+
+/**
+ * model of the topo2CurrentRegion MetaUi from Device below
+ */
+export interface MetaUi {
+ equivLoc: LocMeta;
+ x: number;
+ y: number;
+}
+
+export interface HostProps {
+ gridX: number;
+ gridY: number;
+ latitude: number;
+ longitude: number;
+ locType: LocationType;
+ name: string;
+}
+
+/**
+ * Implementing SimulationNodeDatum interface into our custom Node class
+ */
+export abstract class Node implements d3.SimulationNodeDatum {
+ // Optional - defining optional implementation properties - required for relevant typing assistance
+ index?: number;
+ x: number;
+ y: number;
+ vx?: number;
+ vy?: number;
+ fx?: number | null;
+ fy?: number | null;
+
+ id: string;
+
+ protected constructor(id) {
+ this.id = id;
+ this.x = 0;
+ this.y = 0;
+ }
+}
+
+/**
+ * model of the topo2CurrentRegion device from Region below
+ */
+export class Device extends Node {
+ id: string;
+ layer: LayerType;
+ location: LocationType;
+ metaUi: MetaUi;
+ master: string;
+ nodeType: NodeType;
+ online: boolean;
+ props: DeviceProps;
+ type: string;
+
+ constructor(id: string) {
+ super(id);
+ }
+}
+
+export class Host extends Node {
+ configured: boolean;
+ id: string;
+ ips: string[];
+ layer: LayerType;
+ nodeType: NodeType;
+ props: HostProps;
+
+ constructor(id: string) {
+ super(id);
+ }
+}
+
+
+/**
+ * model of the topo2CurrentRegion subregion from Region below
+ */
+export class SubRegion extends Node {
+ id: string;
+ location: Location;
+ nDevs: number;
+ nHosts: number;
+ name: string;
+ nodeType: NodeType;
+ props: RegionProps;
+
+ constructor(id: string) {
+ super(id);
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts
new file mode 100644
index 0000000..3c1894b
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-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.
+ */
+/**
+ * Enum of the topo2CurrentRegion node type from SubRegion below
+ */
+import {LocationType} from '../../backgroundsvg/backgroundsvg.component';
+import {Device, Host, SubRegion} from './node';
+import {RegionLink} from './link';
+
+export enum NodeType {
+ REGION = 'region',
+ DEVICE = 'device',
+ HOST = 'host',
+}
+
+/**
+ * Enum of the topo2CurrentRegion layerOrder from Region below
+ */
+export enum LayerType {
+ LAYER_OPTICAL = 'opt',
+ LAYER_PACKET = 'pkt',
+ LAYER_DEFAULT = 'def'
+}
+
+/**
+ * model of the topo2CurrentRegion location from SubRegion below
+ */
+export interface Location {
+ locType: LocationType;
+ latOrY: number;
+ longOrX: number;
+}
+
+/**
+ * model of the topo2CurrentRegion props from SubRegion below
+ */
+export interface RegionProps {
+ latitude: number;
+ longitude: number;
+ name: string;
+ peerLocations: string;
+}
+
+/**
+ * model of the topo2CurrentRegion WebSocket response
+ *
+ * The Devices are in a 2D array - 1st order is layer type, 2nd order is
+ * devices in that layer
+ */
+export interface Region {
+ note?: string;
+ id: string;
+ devices: Device[][];
+ hosts: Host[][];
+ links: RegionLink[];
+ layerOrder: LayerType[];
+ peerLocations?: Location[];
+ subregions: SubRegion[];
+}
+
+/**
+ * model of the topo2PeerRegions WebSocket response
+ */
+export interface Peer {
+ peers: SubRegion[];
+}
+
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
new file mode 100644
index 0000000..cf965ac
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces device visual) -- CSS file
+ */
+g.node.device rect {
+ fill: #f0f0f0;
+}
+g.node.device text {
+ fill: #bbb;
+}
+g.node.device use {
+ fill: #777;
+}
+
+
+g.node.device.online rect {
+ fill: #ffffff;
+}
+g.node.device.online text {
+ fill: #3c3a3a;
+}
+g.node.device.online use {
+ /* NOTE: this gets overridden programatically */
+ fill: #454545;
+}
+
+g.node.selected .node-container {
+ stroke-width: 2.0;
+ stroke: #009fdb;
+}
+
+g.node.hovered .node-container {
+ stroke-width: 2.0;
+ stroke: #454545;
+}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
new file mode 100644
index 0000000..d00e906
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -0,0 +1,30 @@
+<!--
+~ Copyright 2018-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.
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" #devSvg
+ [attr.transform]="'translate(' + device?.x + ',' + device?.y + '), scale(' + scale + ')'"
+ [ngClass]="['node', 'device', device.online?'online':'', selected?'selected':'']"
+ (click)="toggleSelected()">
+ <svg:rect class="node-container" x="-18" y="-18" width="36" height="36">
+ <!--[attr.width]="devText.getComputedTextLength()+36" -->
+ <!--[@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: devSvg.getBBox().width+'px' }}">-->
+ </svg:rect>
+ <svg:rect x="-16" y="-16" width="32" height="32" [ngStyle]="{'fill': 'rgb(91, 153, 210)'}">
+ </svg:rect>
+ <svg:text #devText text-anchor="start" y="0.3em" x="22" [ngStyle]="{'transform': 'scale(' + scale + ')'}">
+ {{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
+ </svg:text>
+ <svg:use xlink:href="#m_switch" width="36" height="36" x="-18" y="-18"></svg:use>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
new file mode 100644
index 0000000..0f4feee
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeviceNodeSvgComponent } from './devicenodesvg.component';
+import {LogService} from 'gui2-fw-lib';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {ChangeDetectorRef} from '@angular/core';
+import {Device} from '../../models';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+describe('DeviceNodeSvgComponent', () => {
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let component: DeviceNodeSvgComponent;
+ let fixture: ComponentFixture<DeviceNodeSvgComponent>;
+ let ar: MockActivatedRoute;
+ let testDevice: Device;
+
+
+ beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({ 'debug': 'txrx' });
+ testDevice = new Device('test:1');
+ testDevice.online = true;
+
+ TestBed.configureTestingModule({
+ declarations: [ DeviceNodeSvgComponent ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: ChangeDetectorRef, useClass: ChangeDetectorRef }
+ ]
+ })
+ .compileComponents();
+ logServiceSpy = TestBed.get(LogService);
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DeviceNodeSvgComponent);
+ component = fixture.componentInstance;
+ component.device = testDevice;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
new file mode 100644
index 0000000..bec4529
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018-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 {
+ ChangeDetectorRef,
+ Component,
+ ElementRef, EventEmitter,
+ Input,
+ OnChanges, Output,
+ SimpleChanges,
+ ViewChild
+} from '@angular/core';
+import {Device, LabelToggle} from '../../models';
+import {LogService} from 'gui2-fw-lib';
+
+/**
+ * The Device node in the force graph
+ *
+ * Note: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ */
+@Component({
+ selector: '[onos-devicenodesvg]',
+ templateUrl: './devicenodesvg.component.html',
+ styleUrls: ['./devicenodesvg.component.css'],
+ // changeDetection: ChangeDetectionStrategy.Default,
+ // animations: [
+ // trigger('deviceLabelToggle', [
+ // state('0', style({ // none
+ // width: '36px',
+ // })),
+ // state('1, 2', // id
+ // style({ width: '{{ txtWidth }}'}),
+ // { params: {'txtWidth': '36px'}}
+ // ), // default
+ // transition('0 => *', animate('1000ms ease-in')),
+ // transition('* => 0', animate('1000ms ease-out'))
+ // ])
+ // ]
+})
+export class DeviceNodeSvgComponent implements OnChanges {
+ @Input() device: Device;
+ @Input() scale: number = 1.0;
+ @Input() labelToggle: LabelToggle = LabelToggle.NONE;
+ selected: boolean;
+ @Output() selectedEvent = new EventEmitter<string>();
+ textWidth: number = 36;
+ @ViewChild('idTxt') idTxt: ElementRef;
+
+ constructor(
+ protected log: LogService,
+ private ref: ChangeDetectorRef
+ ) {
+ }
+
+ /**
+ * Called by parent (forcesvg) when a change happens
+ *
+ * There is a difficulty in passing the SVG text object to the animation
+ * directly, to get its width, so we capture it here and update textWidth
+ * local variable here and use it in the animation
+ */
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['device']) {
+ if (!this.device.x) {
+ this.device.x = 0;
+ this.device.y = 0;
+ }
+ }
+ if (changes['labelToggle']) {
+ this.labelToggle = changes['labelToggle'].currentValue;
+ }
+ this.ref.markForCheck();
+ }
+
+ toggleSelected() {
+ this.selected = !this.selected;
+ if (this.selected) {
+ this.selectedEvent.emit(this.device.id);
+ } else {
+ this.selectedEvent.emit();
+ }
+ }
+
+ deselect() {
+ this.selected = false;
+ }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
new file mode 100644
index 0000000..60cedab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces host visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
new file mode 100644
index 0000000..8626cf5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
@@ -0,0 +1,23 @@
+<!--
+~ Copyright 2018-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.
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" [attr.transform]="'translate(' + host?.x + ',' + host?.y + ')'">>
+ <svg:circle
+ cx="0"
+ cy="0"
+ r="5">
+ </svg:circle>
+ <svg:text>{{host?.id}}</svg:text>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts
new file mode 100644
index 0000000..bb791b9
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HostNodeSvgComponent } from './hostnodesvg.component';
+
+describe('HostNodeSvgComponent', () => {
+ let component: HostNodeSvgComponent;
+ let fixture: ComponentFixture<HostNodeSvgComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HostNodeSvgComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HostNodeSvgComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
new file mode 100644
index 0000000..4b1f4ab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-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 {
+ Component,
+ Input,
+ OnChanges,
+ SimpleChanges
+} from '@angular/core';
+import { Host } from '../../models';
+import {LogService} from 'gui2-fw-lib';
+
+/**
+ * The Host node in the force graph
+ *
+ * Note 1: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ * Note 2: the selector is exactly the same as the @Input alias to make this
+ * directive trick work
+ */
+@Component({
+ selector: '[onos-hostnodesvg]',
+ templateUrl: './hostnodesvg.component.html',
+ styleUrls: ['./hostnodesvg.component.css']
+})
+export class HostNodeSvgComponent implements OnChanges {
+ @Input() host: Host;
+
+ constructor(
+ protected log: LogService
+ ) {
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (!this.host.x) {
+ this.host.x = 0;
+ this.host.y = 0;
+ }
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts
new file mode 100644
index 0000000..0723986
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018-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.
+ */
+export * from './linkvisual.component';
+export * from './devicenodesvg/devicenodesvg.component';
+export * from './hostnodesvg/hostnodesvg.component';
+export * from './subregionnodesvg/subregionnodesvg.component';
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css
new file mode 100644
index 0000000..52e5df3
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces link visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html
new file mode 100644
index 0000000..6c4a6bd
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html
@@ -0,0 +1,28 @@
+<!--
+~ Copyright 2018-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.
+-->
+<svg:line xmlns:svg="http://www.w3.org/2000/svg"
+ [attr.x1]="(link && link.source) ? link.source.x : 0"
+ [attr.y1]="(link && link.source) ? link.source.y : 0"
+ [attr.x2]="(link && link.target) ? link.target.x : 0"
+ [attr.y2]="(link && link.target) ? link.target.y : 0"
+ [ngStyle]="{'stroke':'black'}"
+>
+ <!--<svg:line *ngFor="let link of regionData.links"-->
+ <!--x1="0" y1="0" x2="0" y2="0"-->
+ <!--stroke="#939598" stroke-width="1"-->
+ <!--[ngClass]="['link', 'direct']"-->
+ <!--[ngStyle]="{'stroke-width': 1+'px'}" [attr.click()]="selectLink(link)" />-->
+</svg:line>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts
new file mode 100644
index 0000000..c060aab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LinkVisualComponent } from './linkvisual.component';
+
+describe('LinkVisualComponent', () => {
+ let component: LinkVisualComponent;
+ let fixture: ComponentFixture<LinkVisualComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ LinkVisualComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LinkVisualComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts
new file mode 100644
index 0000000..edde2aa
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-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 {
+ Component,
+ Input,
+} from '@angular/core';
+import { Link } from '../models';
+import {LogService} from 'gui2-fw-lib';
+
+@Component({
+ selector: '[onos-linkvisual]',
+ templateUrl: './linkvisual.component.html',
+ styleUrls: ['./linkvisual.component.css']
+})
+export class LinkVisualComponent {
+ @Input() link: Link;
+
+ constructor(
+ protected log: LogService
+ ) {
+ }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css
new file mode 100644
index 0000000..87c23bd
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces subRegion visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html
new file mode 100644
index 0000000..5760634
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html
@@ -0,0 +1,23 @@
+<!--
+~ Copyright 2018-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.
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" [attr.transform]="'translate(' + subRegion?.x + ',' + subRegion?.y + ')'">>
+ <svg:circle
+ cx="0"
+ cy="0"
+ r="5">
+ </svg:circle>
+ <svg:text>{{subRegion?.id}}</svg:text>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts
new file mode 100644
index 0000000..d6f6446
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SubRegionNodeSvgComponent } from './subregionnodesvg.component';
+import {SubRegion} from '../../models';
+
+describe('SubRegionNodeSvgComponent', () => {
+ let component: SubRegionNodeSvgComponent;
+ let fixture: ComponentFixture<SubRegionNodeSvgComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ SubRegionNodeSvgComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SubRegionNodeSvgComponent);
+ component = fixture.debugElement.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should create with an input', () => {
+ component.subRegion = new SubRegion('testId');
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts
new file mode 100644
index 0000000..5dd4736
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-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 {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
+import {SubRegion} from '../../models';
+
+/**
+ * The SubRegion node in the force graph
+ *
+ * Note 1: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ * Note 2: the selector is exactly the same as the @Input alias to make this
+ * directive trick work
+ */
+@Component({
+ selector: '[onos-subregionnodesvg]',
+ templateUrl: './subregionnodesvg.component.html',
+ styleUrls: ['./subregionnodesvg.component.css']
+})
+export class SubRegionNodeSvgComponent implements OnChanges {
+ @Input() subRegion: SubRegion;
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (!this.subRegion.x) {
+ this.subRegion.x = 0;
+ this.subRegion.y = 0;
+ }
+ }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
index 4f3b14f..157a87b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
@@ -13,5 +13,5 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:text>Map of Croatia</svg:text>
-<svg:path d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
+<svg:text xmlns:svg="http://www.w3.org/2000/svg">Map of Croatia</svg:text>
+<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
index d201c8a..9233713 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g id="topo-noDevsLayer" transform="translate(500,500)" style="visibility: visible;">
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" id="topo-noDevsLayer" transform="translate(500,500)" style="visibility: visible;">
<svg:g id="reposition" #reposition [attr.transform]="centre(reposition.getBBox())">
<svg:use width="100" height="100" class="noDevsBird" href="#bird"></svg:use>
<svg:text x="120" y="80">No Devices Are Connected</svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
new file mode 100644
index 0000000..f298bf5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-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 { ZoomableDirective } from './zoomable.directive';
+import {inject, TestBed} from '@angular/core/testing';
+import {LogService, ConsoleLoggerService} from 'gui2-fw-lib';
+import {ElementRef} from '@angular/core';
+
+describe('ZoomableDirective', () => {
+ let log: LogService;
+ let mockWindow: Window;
+
+ beforeEach(() => {
+ log = new ConsoleLoggerService();
+
+ mockWindow = <any>{
+ navigator: {
+ userAgent: 'HeadlessChrome',
+ vendor: 'Google Inc.'
+ }
+ };
+
+ TestBed.configureTestingModule({
+ providers: [ZoomableDirective,
+ {provide: LogService, useValue: log},
+ { provide: ElementRef, useValue: mockWindow }
+ ]
+ });
+ });
+
+ it('should create an instance', inject([ZoomableDirective], (directive: ZoomableDirective) => {
+
+ expect(directive).toBeTruthy();
+ }));
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
new file mode 100644
index 0000000..c956627
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-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 {Directive, ElementRef, Input, OnChanges} from '@angular/core';
+import {LogService} from 'gui2-fw-lib';
+import * as d3 from 'd3';
+
+@Directive({
+ selector: '[onosZoomableOf]'
+})
+export class ZoomableDirective implements OnChanges {
+ @Input() zoomableOf: ElementRef;
+
+ constructor(
+ private _element: ElementRef,
+ private log: LogService,
+ ) {}
+
+ ngOnChanges() {
+ let zoomed, zoom;
+
+ const svg = d3.select(this.zoomableOf);
+ const container = d3.select(this._element.nativeElement);
+
+ zoomed = () => {
+ const transform = d3.event.transform;
+ container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
+ };
+
+ zoom = d3.zoom().on('zoom', zoomed);
+ svg.call(zoom);
+ this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+ }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
index af8f6a0..d79954b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
@@ -21,14 +21,14 @@
<use width="26" height="26" class="glyph" xlink:href="#m_switch"></use>
</svg>
</div>
- <h2 class="clickable">rest:10.1.2.2:443</h2>
+ <h2 class="clickable">{{ selectedNode?.id }}</h2>
</div>
<div class="body">
<table>
<tbody>
<tr>
<td class="label">URI :</td>
- <td class="value">null:0000000000000002</td>
+ <td class="value">{{ selectedNode?.id }}</td>
</tr>
<tr>
<td class="label">Vendor :</td>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
index 0fcf179..935b2ce 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import {
LogService,
@@ -22,6 +22,7 @@
DetailsPanelBaseImpl,
WebSocketService
} from 'gui2-fw-lib';
+import {Node} from '../../layer/forcesvg/models';
/*
ONOS GUI -- Topology Details Panel.
@@ -51,6 +52,7 @@
]
})
export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit {
+ @Input() selectedNode: Node = undefined;
constructor(
protected fs: FnService,
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
index f856213..0cb4a0b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
@@ -17,7 +17,7 @@
<div *ngFor="let inst of onosInstances | keyvalue ; let i=index"
[ngClass]="['onosInst', inst.value.online?'online':'', inst.value.ready? 'ready': '', mastership?'mastership':'', 'affinity']"
(click)="chooseMastership(inst.value.id)">
- <svg width="170" height="85" viewBox="0 0 170 85">
+ <svg xmlns="http://www.w3.org/2000/svg" width="170" height="85" viewBox="0 0 170 85">
<!-- The following blue-glow effect is applied (through CSS) when mastership style is activated on a rectangle -->
<filter x="-50%" y="-50%" width="200%" height="200%" id="blue-glow">
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0 0 0 1 0 "></feColorMatrix>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
index 56f0cc9..894b815 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
@@ -20,6 +20,7 @@
*/
#topo2-p-summary {
padding: 16px;
+ top: 100px;
}
#topo2-p-summary td.label {
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
index a954827..d4d2bbf 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
@@ -28,6 +28,14 @@
import { ForceSvgComponent } from './layer/forcesvg/forcesvg.component';
import { MapSvgComponent } from './layer/mapsvg/mapsvg.component';
import { TopologyService } from './topology.service';
+import { DraggableDirective } from './layer/forcesvg/draggable/draggable.directive';
+import { ZoomableDirective } from './layer/zoomable.directive';
+import {
+ LinkVisualComponent,
+ SubRegionNodeSvgComponent,
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent,
+} from './layer/forcesvg/visuals';
/**
* ONOS GUI -- Topology View Module
@@ -51,7 +59,13 @@
DetailsComponent,
BackgroundSvgComponent,
ForceSvgComponent,
- MapSvgComponent
+ MapSvgComponent,
+ ZoomableDirective,
+ DraggableDirective,
+ LinkVisualComponent,
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent,
+ SubRegionNodeSvgComponent
],
providers: [
TopologyService
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.service.ts b/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
index 1d85a08..3572b7c 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Injectable } from '@angular/core';
+import {Injectable, SimpleChanges, SimpleChange} from '@angular/core';
import {
LogService, WebSocketService,
} from 'gui2-fw-lib';
import { InstanceComponent } from './panel/instance/instance.component';
import { BackgroundSvgComponent } from './layer/backgroundsvg/backgroundsvg.component';
import { ForceSvgComponent } from './layer/forcesvg/forcesvg.component';
+import {Region} from './layer/forcesvg/models';
/**
* ONOS GUI -- Topology Service Module.
@@ -54,8 +55,11 @@
}
],
['topo2CurrentRegion', (data) => {
- this.log.warn('Add fn for topo2CurrentRegion callback', data);
force.regionData = data;
+ force.ngOnChanges({
+ 'regionData' : new SimpleChange(<Region>{}, data, true)
+ });
+ this.log.warn('Add fn for topo2CurrentRegion callback', force.regionData);
}
],
['topo2PeerRegions', (data) => { this.log.warn('Add fn for topo2PeerRegions callback', data); } ],
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
index d74991c..d72a2e0 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
@@ -21,13 +21,13 @@
<onos-details #details></onos-details>
<div id="ov-topo2">
- <svg viewBox="0 0 1000 1000" id="topo2">
+ <svg:svg #svgZoom xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" id="topo2">
<svg:g onos-nodeviceconnected />
- <svg:g id="topo-zoomlayer">
+ <svg:g id="topo-zoomlayer" onosZoomableOf [zoomableOf]="svgZoom">
<svg:g #background onos-backgroundsvg/>
- <svg:g #force onos-forcesvg/>
+ <svg:g #force onos-forcesvg (selectedNodeEvent)="nodeSelected($event)"/>
</svg:g>
- </svg>
+ </svg:svg>
</div>
<div id="breadcrumbs"></div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
index d98a55c..a9c6194 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
@@ -34,6 +34,7 @@
FnService,
LogService
} from 'gui2-fw-lib';
+import {ZoomableDirective} from '../layer/zoomable.directive';
class MockActivatedRoute extends ActivatedRoute {
@@ -107,14 +108,15 @@
SummaryComponent,
ToolbarComponent,
DetailsComponent,
- FlashComponent
+ FlashComponent,
+ ZoomableDirective
],
providers: [
{ provide: FnService, useValue: fs },
{ provide: LogService, useValue: logSpy },
{ provide: 'Window', useValue: windowMock },
{ provide: HttpClient, useClass: MockHttpClient },
- { provide: TopologyService, useClass: MockTopologyService }
+ { provide: TopologyService, useClass: MockTopologyService },
]
}).compileComponents();
logServiceSpy = TestBed.get(LogService);
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
index 728347d..d4decb0 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
@@ -13,20 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {
+ Component,
+ OnDestroy,
+ OnInit,
+ ViewChild
+} from '@angular/core';
import * as d3 from 'd3';
import {
FnService,
- KeysService, KeysToken,
- LogService, PrefsService,
- SvgUtilService, WebSocketService, Zoomer, ZoomOpts, ZoomService
+ KeysService,
+ KeysToken,
+ LogService,
+ PrefsService,
+ SvgUtilService,
+ WebSocketService,
+ Zoomer,
+ ZoomOpts,
+ ZoomService
} from 'gui2-fw-lib';
-import { InstanceComponent } from '../panel/instance/instance.component';
-import { SummaryComponent } from '../panel/summary/summary.component';
-import { DetailsComponent } from '../panel/details/details.component';
-import { BackgroundSvgComponent } from '../layer/backgroundsvg/backgroundsvg.component';
-import { ForceSvgComponent } from '../layer/forcesvg/forcesvg.component';
-import { TopologyService } from '../topology.service';
+import {InstanceComponent} from '../panel/instance/instance.component';
+import {SummaryComponent} from '../panel/summary/summary.component';
+import {DetailsComponent} from '../panel/details/details.component';
+import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
+import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
+import {TopologyService} from '../topology.service';
+import {Node} from '../layer/forcesvg/models';
/**
* ONOS GUI Topology View
@@ -236,6 +248,7 @@
protected cycleDeviceLabels() {
this.log.debug('Cycling device labels');
// TODO: Reinstate with components
+ this.force.updateDeviceLabelToggle();
// let deviceLabelIndex = t2ps.get('dlbls') + 1;
// let newDeviceLabelIndex = deviceLabelIndex % 3;
//
@@ -262,9 +275,12 @@
}
protected toggleDetails(token: KeysToken) {
- this.flashMsg = 'Toggling details';
- this.details.togglePanel(() => {});
- this.log.debug('Toggling details', token);
+ if (this.details.selectedNode) {
+ this.flashMsg = 'Toggling details';
+ this.details.togglePanel(() => {
+ });
+ this.log.debug('Toggling details', token);
+ }
}
protected toggleInstancePanel(token: KeysToken) {
@@ -426,4 +442,9 @@
this.zoomer.panZoom(translate, scale, transition);
}
+ nodeSelected(node: Node) {
+ this.details.selectedNode = node;
+ this.details.on = Boolean(node);
+ }
+
}