Implemented Instance View of Topo in GUI2
Change-Id: If603481e729ebc19a6f91db2739f1b422cc762d0
diff --git a/apps/faultmanagement/fm-gui2-lib/package.json b/apps/faultmanagement/fm-gui2-lib/package.json
index cb6f6c1..aa34854 100644
--- a/apps/faultmanagement/fm-gui2-lib/package.json
+++ b/apps/faultmanagement/fm-gui2-lib/package.json
@@ -14,7 +14,7 @@
"@angular/router": "^7.0.2",
"core-js": "^2.5.4",
"d3": "^5.2.0",
- "gui2-fw-lib": "file:../../../web/gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
+ "gui2-fw-lib": "file:../../../web/gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
"rxjs": "^6.3.3",
"zone.js": "^0.8.26"
},
diff --git a/apps/faultmanagement/fm-gui2-lib/projects/fm-gui2-lib/package.json b/apps/faultmanagement/fm-gui2-lib/projects/fm-gui2-lib/package.json
index 9c383d4..5073363 100644
--- a/apps/faultmanagement/fm-gui2-lib/projects/fm-gui2-lib/package.json
+++ b/apps/faultmanagement/fm-gui2-lib/projects/fm-gui2-lib/package.json
@@ -1,6 +1,6 @@
{
"name": "fm-gui2-lib",
- "version": "1.15.0",
+ "version": "2.0.0",
"peerDependencies": {
"@angular/common": "^7.0.0 || ^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^7.0.0 || ^6.0.0-rc.0 || ^6.0.0"
diff --git a/tools/build/bazel/modules.bzl b/tools/build/bazel/modules.bzl
index 2c3176d..1f6b771 100644
--- a/tools/build/bazel/modules.bzl
+++ b/tools/build/bazel/modules.bzl
@@ -75,7 +75,7 @@
"//providers/general/device:onos-providers-general-device",
"//providers/p4runtime/packet:onos-providers-p4runtime-packet",
"//web/api:onos-rest",
- # "//web/gui2:onos-gui2",
+ "//web/gui2:onos-gui2",
"//web/gui:onos-gui",
"//incubator/protobuf/models/proto:onos-incubator-protobuf-models-proto",
"//incubator/protobuf/models:onos-incubator-protobuf-models",
@@ -311,7 +311,7 @@
"//tools/package/features:onos-cli",
"//tools/package/features:onos-rest",
"//tools/package/features:onos-gui",
- # "//tools/package/features:onos-gui2",
+ "//tools/package/features:onos-gui2",
# "//tools/package/features:onos-security",
]
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/package.json b/web/gui2-fw-lib/projects/gui2-fw-lib/package.json
index 288b753..289a829 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/package.json
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/package.json
@@ -1,6 +1,6 @@
{
"name": "gui2-fw-lib",
- "version": "0.14.0",
+ "version": "2.0.0",
"peerDependencies": {
"@angular/common": "^7.0.0 || ^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^7.0.0 || ^6.0.0-rc.0 || ^6.0.0"
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
index 6e96309..337b104 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/nav/nav.service.ts
@@ -73,7 +73,9 @@
if (uiView.cat === 'PLATFORM') {
this.uiPlatformViews.push(uiView);
} else if (uiView.cat === 'NETWORK') {
- this.uiNetworkViews.push(uiView);
+ if ( uiView.id !== 'topo') {
+ this.uiNetworkViews.push(uiView);
+ }
} else if (uiView.cat === 'HIDDEN') {
this.uiHiddenViews.push(uiView);
} else {
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 73480ed..69392cf 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
@@ -54,6 +54,7 @@
['downArrow', 'triangleDown'],
['appInactive', 'unknown'],
+ ['uiAttached', 'uiAttached'],
['node', 'node'],
['devIcon_SWITCH', 'switch'],
diff --git a/web/gui2/AngularMigration.md b/web/gui2/AngularMigration.md
index 82e4b29..d706255 100644
--- a/web/gui2/AngularMigration.md
+++ b/web/gui2/AngularMigration.md
@@ -1,21 +1,54 @@
-# Migrating ONOS GUI
-Code written for Angular 1.x can be converted to Angular 5, through a line by line migration process (aka a hard slog)
+# Migrating ONOS GUI to GUI2
-* It is important to know that Angular 5 code is written in TypeScript and all files end in .ts
+# Introduction
+## Why is a migration needed?
+Since the introduction of ES6 version of the EcmaScript language many new language
+features have been introduced that allow a lot of new capabilities that were very
+hard to acheieve with the older version of the ECMAScript language.
+
+Google have taken advantage of this and totally renewed the Angular framework to
+take full advantage of these improvements. The new Angular is so different they
+have renamed old angular to Angular JS and the new one is known as Angular 7.
+
+Along with this Microsoft have introduced TypeScript which is a strongly typed
+superset of ES6. Its big advantage is that type safety can be ensured at compile
+time and the code readability and testability has been greatly improved.
+
+In addition to all of this Google has introduced a new framework tool __Angular
+CLI__ that bundles together several JS frameworks together that make creation,
+building and testing much more consistent and automated.
+
+The reason for this migration of the ONOS GUI code then is to take advantage of
+all of these new technologies. As it was in early 2018 based on Angular JS (1.3.5)
+the development of the GUI code had come to a halt, and it was at risk of becoming
+obsolete and left behind by developers because of its complexity. Anyone wanting
+to develop or extend it had a huge learning curve.
+
+The purpose of this migration therefore is to make the code more up to date and
+accessible to developers, and to ensure that it provides an open framework that
+can be built upon by future developers. The simple goal is that anyone experienced
+in the most recent Angular framework should find it very easy to navigate around
+the ONOS GUI2 code base.
+
+# Technical challenges
+Code written for Angular 1.x can be converted to Angular 7, through a line by line migration process (aka a hard slog)
+
+* It is important to know that Angular 7 code is written in TypeScript and all files end in .ts
* In tsconfig.json this app is set to be compiled as ES6 (ES2015)
* See this [Compatibility Table](http://kangax.github.io/compat-table/es6/) for supported browsers
* All modern browsers are supported
* See https://webapplog.com/es6/ for a list of things that ES6 brings
-* Each item (Service, Component, Directive, Pipe or Module) gets its own file ending with this type e.g. function.service.ts
+* Each item (Angular 'schematic' e.g. Service, Component, Directive, Pipe or Module)
+gets its own file ending with this type e.g. function.service.ts
* Each test file is the name of the item with .spec.ts e.g. function.service.spec.ts
* It is considered best practice to put the Unit test of a component or service
- right next to it in the folder - some new unit tests have been placed in a
- tests folder, but they will have to come back out from there in to the
- individual folders.
+ right next to it in the folder.
* Modules are used to group together services, components, directives etc
* When starting any new component use the `ng generate ..` This will create the associated test too
+* This migration takes advantage of libraries. The 'fw' section has now been
+separated out in to its own library 'gui2-fw-lib'
-
+# Migration approach
Migration the existing ONOS Gui can be done through a process of taking an existing JavaScript file and looking at its
angular.module statement.
Note:
@@ -51,7 +84,7 @@
* The function in the controller becomes the constructor of the component
* The parameters to the function are injected services to the constructor
* It includes a html file and a CSS file through the templateUrl and styleURLs
-* The 'detectBrowser' directive becomes __onos/web/gui2/src/main/webapp/app/detectbrowser.directive.ts__
+* The 'detectBrowser' directive becomes __onos/web/gui2-fw-lib/src/main/webapp/app/detectbrowser.directive.ts__
* It can be referenced in the onos.component.html template by its selector `onosDetectBrowser`
If documentation has been created (using `npm run compodoc`) this module can be inspected at
@@ -69,7 +102,8 @@
`ng serve`
-command and can be left running as code changes are made.
+command and can be left running as code changes are made. Please see README.md for notes
+on how to run this.
Once this is running a browser attached to [http://localhost:4200](http://localhost:4200)
will display the application and any changes made to the code will
be visible immediately as the page will refresh
@@ -78,7 +112,7 @@
bad in your code.
Another place to look is in the browsers own console
-`Ctrl-Shift-I` usually brings this up.
+`Ctrl-Shift-I` or `F12` usually brings this up.
# Migrating the code
This is where things get really interesting. A good place to start is on a
@@ -86,52 +120,66 @@
Two services have been setup in the onos.module.ts that are new to this migration
* LogService - this replaces $log that was inserted in to the old code
-* WindowService - this replaces $window and $location in the old code
+* FunctionService - this replaces fn.js in the old code
There is a fair bit of refactoring has to take place. An important thing to understand
-is that DOM manipulations from inside JavaScript code is not the Angular 6
+is that DOM manipulations from inside JavaScript code is not the Angular 7
way of doing things - there was a lot of this in the old ONOS GUI, using d3.append(..)
and so on.
-The Angular 6 way of doing things is to define DOM objects (elements) in the
+The Angular 7 way of doing things is to define DOM objects (elements) in the
html template of a component, and use the Component Java Script code as a base
for logic that can influence the display of these objects in the template.
What this means is that what were previously defined as services (e.g. VeilService or
-LoadingService) should now become Components in Angular 6 (e.g. VeilComponent or
-LoadingComponent).
+LoadingService) should now become Components in Angular 7 (e.g. VeilComponent or
+LoadingComponent).
Similarly a directive might be trying to do DOM manipulation and have a CSS - this
should be made in to a component instead (see IconComponent)
### How do I know whether a Service or Directive should be made a Component in this new GUI?
The general rule to follow is _"if a service in the old GUI has an associated CSS
-file or two then is should be a component in the new GUI"_.
+file or two then it should be a component in the new GUI2"_.
The implication of this is that all of the d3 DOM Manipulations that happened in
the old service should now be represented in the template of this new component.
If it's not clear to you what the template should look like, then run the old GUI
and inspect the element and its children to see the structure.
+### Scope of components and services
Components (unlike services) have limited scope (that's the magic of them really -
no more DOM is loaded at any time than is necessary). This means that they are
self contained modules, and any CSS associated with them is private to that
component and not accessible globally.
+Services on the other hand have 2 different options for scope - they can be in the
+'root' scope or in the scope of a component that injects them. Most services have
+been given root scope - by using __@Injectable ({providedIn: 'root',})__. There
+are a few exceptions like TopologyService that is only relevant to TopologyComponent
+and so is loaded only in this scope.
+
### Do not inject components in to services
-Components are graphical elements and should not be injected in to Services.
+Components are graphical elements and should not be injected in to Services.
Services should be injected in to components, but not the other way round.
-Components can be added in to other components by putting the selector of
+Components can be added in to other components by putting the selector of
the child component e.g. <onos-icon> in to the html template of the parent.
-Take for instance the WebSocketService - this should remain a service, but I want
-to display the LoadingComponent while it's waiting and the VeilComponent if it
-disconnects. I should not go injecting these in to WebSocketService - instead
-there is a setLoadingDelegate() and a setVeilDelegate() function on WSS that I
-can pass in a reference to these two components. When they need to be displayed
-a method call is made on the delegate and the component gets enabled and displays.
-Also note inside WSS any time we call a method on this LoadingComponent delegate
+If some function on this child component needs to be referred to in the parent
+component code, the child can be given a name tag and then referred to by this
+with a @ViewChild declaration in the parent TS file. See TopologyComponent for
+an example of this, where a reference to the SummaryComponent is given the tag
+__#summary__.
+
+In terms of injecting services, take for instance the WebSocketService - this
+should remain a service, but I want to display the LoadingComponent while it's
+waiting and the VeilComponent if it disconnects.
+I should not go injecting these in to WebSocketService - instead there is a
+setLoadingDelegate() and a setVeilDelegate() function on WSS that I can pass in
+a reference to these two components. When they need to be displayed a method call
+is made on the delegate and the component gets enabled and displays.
+Also note inside WSS any time we call a method on this LoadingComponent delegate
we check that it the delegate had actually been set.
-The WSS was passed in to the LoadingComponent and VeilComponent to set the
+The WSS was passed in to the LoadingComponent and VeilComponent to set the
delegate on it.
Any component that needs to use WSS for data should inject the WSS service __AND__
@@ -148,14 +196,15 @@
directive and they can be merged in to one
## fw/remote/wsock.service
-Taking for a really simple example the fw/remote/WSockService, this was originally defined in
-the __app/fw/remote/wsock.js__ file and is now redefined in
-__onos/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts__.
+Taking for a really simple example the fw/remote/WSockService, this was originally defined in
+the __/onos/web/gui/src/main/webapp/app/fw/remote/wsock.js__ file and is now redefined in
+__onos/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/remote/wsock.service.ts__.
First of all this should remain a Service, since it does not do any DOM
manipulation and does not have an associated CSS.
-This has one method that's called to establish the WebSocketService
+This is a wrapper around the ES6 class WebSocket. It has one method newWebSocket()
+that's called to establish the WebSocketService
```javascript
1 (function () {
@@ -182,21 +231,21 @@
```
-Converting this to TypeScript requires a total change in mindset away
+Converting this to TypeScript requires a total change in mindset away
from functions and towards more object oriented programming. This file is
-located in __onos/web/gui2/src/main/webapp/app/fw/remote/wsock.service.ts__
+located in __onos/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/remote/wsock.service.ts__
```typescript
101 import { Injectable } from '@angular/core';
102 import { LogService } from '../../log.service';
103
-104 @Injectable()
+104 @Injectable({ providedIn: 'root' })
105 export class WSock {
106
107 constructor(
-108 private log: LogService,
+108 private log: LogService,
109 ) {
-110 this.log.debug('WSockService constructed');
+110 this.log.debug('WSockService constructed');
111 }
112
113 newWebSocket(url: string): WebSocket {
@@ -212,11 +261,12 @@
```
There are several things worth noting here:
-* The module is no longer given in the file -
- the corresponding RemoteModule in ./remote.module.ts lists this
- service as one of its providers
+* The module is no longer given in the file - the onosRemote module is now part of
+ the corresponding Gui2FwLibModule in ../gui2-fw-lib.module.ts. It could be
+ listed in this module file as one of its providers, but it doesn't have to
+ since it's already injected as root (line 104)
* factory is replaced by the @Injectable annotation
-* This WSock is expressed as a class rather than a function.
+* This WSock is expressed as a class rather than a function (line 105)
* Note on line 105 - the more usual convention used elsewhere is to call name the class
WSockService, but for the sake of compatibility with the existing file at line 5
we keep the same name.
@@ -227,8 +277,9 @@
* Anything belonging to the class has to be prefixed with 'this.'
* The calling of the 'debug' service (line 110) on the log service is an example
* The function newWebSocket (line 7) is now replaced by the method newWebSocket()
-* Because if TypeScript we can assign types to the method signature.
- e.g. url is a string, and the function returns a WebSocket. This helps
+ - this is automatically public, but could be made protected or private
+* Because it's TypeScript, we can assign types to the method signature.
+ e.g. url is a string, and the function returns a WebSocket object. This helps
a lot with code readability and static checking
* The __let__ keyword (e.g. line 114) is used in TypeScript instead of __var__ (line 8)
@@ -238,15 +289,17 @@
* $timeout can be replaced by setTimeout()
* $timeout.cancel can be replaced by clearTimeout()
* (d3 object).append(..).attr values should be listed individually (see icon.service for example)
-* Please try do avoid d3 DOM manipulations in ONOS GUI 2, as this is not the Angular 6 way of
- doing things
+* Please try do avoid d3 DOM manipulations in ONOS GUI 2, as this is not the
+ Angular way of doing things
* $interval should be replaced by
task = setInterval(() => functionname_or_body, speed);
* To cancel the timer clearInterval(task)
+* If a function is to be called then the format () => {} should be used. This is
+ so that the enclosing context can be passed in to the lambda
-# Progress so far - 18 Jun 2018
-The following services are partially migrated:
+# Progress so far - Nov 2018
+The following services are most migrated:
* fw/util/FnService - full migrated with Unit tests
* fw/svg/GlyphDataService - mostly migrated. Values are stored in maps as constants
* fw/svg/GlyphService - partly implemented - enough to support the icon service
@@ -272,7 +325,7 @@
base for AppsDetailsComponent and DeviceDetailsComponent
-# Devices View
+## Devices View
This is now a Component, whose class extends the TableBase - this is where it gets
most of its functionality. As before all data comes from the WebSocket.
There is still a bit of work to go on this - scrolling of long lists, device details
@@ -289,9 +342,59 @@
embedded in to the repeated row. There are base classes for common details panel
behaviour
-# Apps View
+## Apps View
This is a Component too, again extending TableBase. Apps view has much more functionality
though because it has controls for upload and download of applications.
+## All other tabular views
+About 20 tabular views have now been migrated
-
+## Faultmanagement Alarms view
+Because it is important to be able to integrate external applications in to the
+ONOS GUI2, an approach was needed to make them work in the new world of Angular CLI.
+
+To make this happen this GUI view was made in to a library (fm-gui2-lib), in
+* onos/apps/faultmanagement/fm-gui2-lib/projects/fm-gui2-lib/src/lib
+
+that
+contains 2 components:
+* AlarmTableComponent
+* AlarmDetailsComponent
+
+These depend on some of the functions defined in the gui2-fw-lib - but that's no
+problem this is imported just like any other NPM library.
+
+The fm-gui2-lib then can just be listed as one of the dependencies of the main
+application in onos/web/gui2 and can be navigated to through the Nav menu as 'alarmTable'
+See onos/web/gui2/src/main/webapp/app/onos-routing.module.ts line 87.
+
+When the underlying application is stopped in Karaf (ONOS CLI), the option to
+navigate to this Alarm GUI disappears from the Nav menu.
+
+## Yang GUI
+No migration has been done on this yet. Because it's and external application
+the approach will be similar to FM GUI, where it's created as a library.
+
+## Topology View
+This is one of the main goals of the migration - bringing together the Topology(1)
+and the Topology2 views of the old GUI together in to one new Topology view.
+
+Topology2 project was never really finished and several features from Topology1
+are missing from it. Topology2 introduced the Regions and hierarchial navigation.
+
+In the new GUI2 implementation, care has been taken to separate the structure out
+in to components that will hopefully be reusable elsewhere. Also the components
+have been logically grouped - all of the __panel__ components are together, as
+are the __layout__ components. In addition some are SVG components that are
+designed to extend an SVG element tree, rather than a HTML one.
+
+All of this will ultimately lead to a framework that can support other paradigms
+especially ones like tiles background maps, such as this from Google and other
+providers.
+
+There is still quite a way to go to finish this consolidated Topology view (as of
+Nov '18), as there are updated vesions of the D3 Force library and the D3 Zoom
+library to migrate to.
+
+The Topology view will eventually be broken out in to its own library, to promote
+reuse.
\ No newline at end of file
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index 5f58ec3..604c7e4 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -5560,7 +5560,7 @@
},
"gui2-fw-lib": {
"version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
- "integrity": "sha512-2uUhzQPKJA1hRZP+cY8AXOKYi1QqYQHbpkgXcq8QrZlB7jHBDNyBsmemrnV3kVhFz5+40Z6WGTaXTWd7sbTI+Q==",
+ "integrity": "sha512-8uT9TL5Ye4uRSkseDyI6fsGHb1d5/SwjSYeTVhftmyKIgF4O2HywOa0gdFopx6Ft+JjhhP5k1n+WAPvoEjnXKA==",
"requires": {
"tslib": "1.9.3"
}
diff --git a/web/gui2/package.json b/web/gui2/package.json
index 1aa5216..1ce2565 100644
--- a/web/gui2/package.json
+++ b/web/gui2/package.json
@@ -24,8 +24,8 @@
"@angular/router": "^7.0.2",
"core-js": "^2.5.4",
"d3": "^5.2.0",
- "fm-gui2-lib": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-1.15.0.tgz",
- "gui2-fw-lib": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
+ "fm-gui2-lib": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-2.0.0.tgz",
+ "gui2-fw-lib": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
"rxjs": "^6.0.0",
"zone.js": "^0.8.26"
},
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 60ec9d7..962f5a3 100644
--- a/web/gui2/src/main/webapp/app/onos-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/onos-routing.module.ts
@@ -79,7 +79,7 @@
loadChildren: 'app/view/meter/meter.module#MeterModule'
},
{
- path: 'topo',
+ path: 'topo2',
loadChildren: 'app/view/topology/topology.module#TopologyModule'
},
/* Comment out below section for running locally with 'ng serve' when developing */
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 3f78ebb..389ec9c 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
@@ -14,3 +14,9 @@
~ limitations under the License.
-->
<svg:g onos-mapsvg />
+<svg:g>
+ <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>
+ <svg:text *ngFor="let crumb of layoutData.crumbs">{{ crumb.id }} {{ crumb.name }}</svg:text>
+</svg:g>
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 6a010d1..ff6f99a 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
@@ -16,6 +16,57 @@
import { Component, OnInit } from '@angular/core';
/**
+ * model of the topo2CurrentLayout attrs from BgZoom below
+ */
+export interface BgZoomAttrs {
+ offsetX: number;
+ offsetY: number;
+ scale: number;
+}
+
+/**
+ * model of the topo2CurrentLayout background zoom attrs from Layout below
+ */
+export interface BgZoom {
+ cfg: BgZoomAttrs;
+ usr?: BgZoomAttrs;
+}
+
+/**
+ * model of the topo2CurrentLayout breadcrumb from Layout below
+ */
+export interface LayoutCrumb {
+ id: string;
+ name: string;
+}
+
+/**
+ * Enum of the topo2CurrentRegion location type from Location below
+ */
+export enum LocationType {
+ GEO = 'geo',
+ GRID = 'grid'
+}
+
+/**
+ * model of the topo2CurrentLayout WebSocket response
+ */
+export interface Layout {
+ id: string;
+ bgDefaultScale: number;
+ bgDesc: string;
+ bgFilePath: string;
+ bgId: string;
+ bgType: LocationType;
+ bgWarn: string;
+ bgZoom: BgZoom;
+ crumbs: LayoutCrumb[];
+ parent: string;
+ region: string;
+ regionName: string;
+}
+
+/**
* ONOS GUI -- Topology Background Layer View.
*/
@Component({
@@ -25,9 +76,13 @@
})
export class BackgroundSvgComponent implements OnInit {
+ layoutData: Layout = <Layout>{};
+
constructor() { }
ngOnInit() {
}
+
+
}
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 45fa000..d159f06 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,7 +13,127 @@
* 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 { LocationType } from '../backgroundsvg/backgroundsvg.component';
+
+/**
+ * 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.
@@ -24,6 +144,8 @@
styleUrls: ['./forcesvg.component.css']
})
export class ForceSvgComponent implements OnInit {
+ @Input() onosInstMastership: string = '';
+ regionData: Region;
constructor() { }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/layout/layout.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/layout/layout.component.ts
index 477dd15..122c14b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/layout/layout.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/layout/layout.component.ts
@@ -16,15 +16,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
- selector: 'onos-layout',
- templateUrl: './layout.component.html',
- styleUrls: ['./layout.component.css']
+ selector: 'onos-layout',
+ templateUrl: './layout.component.html',
+ styleUrls: ['./layout.component.css']
})
export class LayoutComponent implements OnInit {
- constructor() { }
+ constructor() { }
- ngOnInit() {
- }
+ ngOnInit() {
+ }
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.css b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.css
index cb78e8d..f335726 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.css
@@ -15,11 +15,6 @@
*/
/* --- Topo Instance Panel --- */
-#topo-p-instance {
- height: 85px;
- padding: 10px;
-}
-
#topo-p-instance div.onosInst {
display: inline-block;
width: 170px;
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 6a1aba9..f856213 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
@@ -13,17 +13,28 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<div id="topo-p-instance" class="floatpanel" style="left: 20px; width: 170px; height: 85px;" [@instancePanelState]="!on">
- <div class="onosInst online ready mastership affinity">
+<div id="topo-p-instance" class="floatpanel" [ngStyle]="{'left': '20px', 'top':divTopPx+'px', 'width': (onosInstances.length * 170)+'px', 'height': '85px'}" [@instancePanelState]="!on">
+ <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">
- <rect x="5" y="5" width="160" height="30" style="fill: rgb(91, 153, 210);"></rect>
+ <!-- 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>
+ <feGaussianBlur stdDeviation="3" result="coloredBlur"></feGaussianBlur>
+ <feMerge>
+ <feMergeNode in="coloredBlur"></feMergeNode>
+ <feMergeNode in="SourceGraphic"></feMergeNode>
+ </feMerge>
+ </filter>
+ <rect x="5" y="5" width="160" height="30" [ngStyle]="{ 'fill': panelColour(i)}"></rect>
+ <text class="instTitle" x="48" y="27">{{ inst.value.id }}</text>
<rect x="5" y="35" width="160" height="45"></rect>
+ <text class="instLabel ip" x="48" y="55">{{ inst.value.ip }}</text>
<use width="20" height="20" class="glyph badgeIcon bird" xlink:href="#bird" transform="translate(15,10)"></use>
- <use width="16" height="16" class="glyph overlay badgeIcon readyBadge" xlink:href="#checkMark" transform="translate(18,40)"></use>
- <text class="instTitle" x="48" y="27">127.0.0.1</text>
- <text class="instLabel ip" x="48" y="55">127.0.0.1</text>
- <text class="instLabel ns" x="48" y="73">Devices: 0</text>
- <use width="24" height="24" class="glyph overlay badgeIcon uiBadge" xlink:href="#uiAttached" transform="translate(14,54)"></use>
+ <use *ngIf="inst.value.ready" width="16" height="16" class="glyph overlay badgeIcon readyBadge" xlink:href="#checkMark" transform="translate(18,40)"></use>
+ <text class="instLabel ns" x="48" y="73">Devices: {{ inst.value.switches }}</text>
+ <use *ngIf="inst.value.uiAttached" width="24" height="24" class="glyph overlay badgeIcon uiBadge" xlink:href="#uiAttached" transform="translate(14,54)"></use>
</svg>
</div>
</div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.ts
index 66b2a05..05e8768 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.ts
@@ -13,18 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit, Input } from '@angular/core';
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter
+} from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import {
LogService,
LoadingService,
FnService,
- PanelBaseImpl
+ PanelBaseImpl,
+ IconService,
+ SvgUtilService
} from 'gui2-fw-lib';
-/*
- ONOS GUI -- Topology Instances Panel.
- Displays ONOS instances.
+/**
+ * A model of instance information that drives each panel
+ */
+export interface Instance {
+ id: string;
+ ip: string;
+ online: boolean;
+ ready: boolean;
+ switches: number;
+ uiAttached: boolean;
+}
+
+/**
+ * ONOS GUI -- Topology Instances Panel.
+ * Displays ONOS instances. The onosInstances Array gets updated by topology.service
+ * whenever a topo2AllInstances update arrives back on the WebSocket
+ *
+ * This emits a mastership event when the user clicks on an instance, to
+ * see the devices that it has mastership of.
*/
@Component({
selector: 'onos-instance',
@@ -38,29 +61,57 @@
trigger('instancePanelState', [
state('true', style({
transform: 'translateX(0%)',
- opacity: '100'
+ opacity: '1.0'
})),
state('false', style({
transform: 'translateX(-100%)',
- opacity: '0'
+ opacity: '0.0'
})),
transition('0 => 1', animate('100ms ease-in')),
transition('1 => 0', animate('100ms ease-out'))
])
]
})
-export class InstanceComponent extends PanelBaseImpl implements OnInit {
+export class InstanceComponent extends PanelBaseImpl {
+ @Input() divTopPx: number = 100;
+ @Output() mastershipEvent = new EventEmitter<string>();
+ public onosInstances: Array<Instance>;
+ protected mastership: string;
constructor(
protected fs: FnService,
protected log: LogService,
protected ls: LoadingService,
+ protected is: IconService,
+ protected sus: SvgUtilService
) {
super(fs, ls, log);
+ this.onosInstances = <Array<Instance>>[];
+ this.is.loadIconDef('active');
+ this.is.loadIconDef('uiAttached');
this.log.debug('InstanceComponent constructed');
}
- ngOnInit() {
+ /**
+ * Get a colour for the banner of the nth panel
+ * @param idx The index of the panel (0-6)
+ */
+ panelColour(idx: number): string {
+ return this.sus.cat7().getColor(idx, false, '');
}
+ /**
+ * Toggle the display of mastership
+ * If the same instance is clicked a second time then cancel display of mastership
+ * @param instId The instance to display mastership for
+ */
+ chooseMastership(instId: string): void {
+ if (this.mastership === instId) {
+ this.mastership = '';
+ } else {
+ this.mastership = instId;
+ }
+ this.mastershipEvent.emit(this.mastership);
+ this.log.debug('Instance', this.mastership, 'chosen on GUI');
+ }
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.theme.css b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.theme.css
index a20711d..3be7bdd 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.theme.css
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.theme.css
@@ -34,4 +34,119 @@
}
.dark #topo-p-instance .online svg .glyph.overlay {
fill: #fff;
+}
+
+/* offline */
+#topo-p-instance svg .badgeIcon {
+ opacity: 0.4;
+ fill: #939598;
+}
+
+/* online */
+#topo-p-instance .online svg .badgeIcon {
+ opacity: 1.0;
+ fill: #939598;
+}
+#topo-p-instance .online svg .badgeIcon.bird {
+ fill: #ffffff;
+}
+
+#topo-p-instance svg .readyBadge {
+ visibility: hidden;
+}
+#topo-p-instance .ready svg .readyBadge {
+ visibility: visible;
+}
+
+#topo-p-instance svg text {
+ text-anchor: start;
+ opacity: 0.5;
+ fill: #3c3a3a;
+}
+
+#topo-p-instance .online svg text {
+ opacity: 1.0;
+ fill: #3c3a3a;
+}
+
+#topo-p-instance .onosInst.mastership {
+ opacity: 0.3;
+}
+#topo-p-instance .onosInst.mastership.affinity {
+ opacity: 1.0;
+}
+#topo-p-instance .onosInst.mastership.affinity svg rect {
+ filter: url(#blue-glow);
+}
+
+.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
+ filter: url(#blue-glow);
+}
+
+.dark #topo-p-instance {
+ background-color: #2f313c;
+ color: #c2c2b7;
+ border: 1px solid #364144;
+
+}
+
+.dark #topo-p-instance svg rect {
+ stroke-width: 0;
+ fill: #525660;
+}
+
+/* body of an instance */
+.dark #topo-p-instance .online svg rect {
+ opacity: 1;
+ fill: #838992;
+}
+
+.dark #topo-p-instance svg .glyph {
+ fill: #ddd;
+}
+.dark #topo-p-instance .online svg .glyph {
+ fill: #fff;
+}
+.dark #topo-p-instance .online svg .glyph.overlay {
+ fill: #c7c7c7;
+}
+
+/* offline */
+.dark #topo-p-instance svg .badgeIcon {
+ opacity: 0.4;
+ fill: #939598;
+}
+
+/* online */
+.dark #topo-p-instance .online svg .badgeIcon {
+ opacity: 1.0;
+ fill: #939598;
+}
+.dark #topo-p-instance .online svg .badgeIcon.bird {
+ fill: #ffffff;
+}
+
+.dark #topo-p-instance svg text {
+ text-anchor: start;
+ opacity: 0.5;
+ fill: #aaa;
+}
+
+.dark #topo-p-instance .online svg text {
+ opacity: 1.0;
+ fill: #fff;
+}
+
+.dark #topo-p-instance .onosInst.mastership {
+ opacity: 0.3;
+}
+.dark #topo-p-instance .onosInst.mastership.affinity {
+ opacity: 1.0;
+}
+.dark #topo-p-instance .onosInst.mastership.affinity svg rect {
+ filter: url(#blue-glow);
+}
+
+.dark.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
+ filter: url(#blue-glow);
}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.ts
index 7de5e89..c1c41f8 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.ts
@@ -94,7 +94,6 @@
handleSummaryData(data: SummaryResponse) {
this.summaryData = data;
this.render();
- this.log.debug('Summary', data);
}
private render() {
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 a26f3ec..a954827 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
@@ -27,6 +27,7 @@
import { BackgroundSvgComponent } from './layer/backgroundsvg/backgroundsvg.component';
import { ForceSvgComponent } from './layer/forcesvg/forcesvg.component';
import { MapSvgComponent } from './layer/mapsvg/mapsvg.component';
+import { TopologyService } from './topology.service';
/**
* ONOS GUI -- Topology View Module
@@ -51,6 +52,9 @@
BackgroundSvgComponent,
ForceSvgComponent,
MapSvgComponent
+ ],
+ providers: [
+ TopologyService
]
})
export class TopologyModule { }
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.service.spec.ts b/web/gui2/src/main/webapp/app/view/topology/topology.service.spec.ts
index ca8711a..29d456f 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.service.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.service.spec.ts
@@ -14,23 +14,61 @@
* limitations under the License.
*/
import { TestBed, inject } from '@angular/core/testing';
-import { LogService, ConsoleLoggerService } from 'gui2-fw-lib';
+import { ActivatedRoute, Params } from '@angular/router';
+import {of} from 'rxjs';
+
import { TopologyService } from './topology.service';
+import {
+ LogService,
+ FnService
+} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
/**
* ONOS GUI -- Topology Service - Unit Tests
*/
describe('TopologyService', () => {
- let log: LogService;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let ar: ActivatedRoute;
+ let fs: FnService;
+ let mockWindow: Window;
beforeEach(() => {
- log = new ConsoleLoggerService();
+ 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: [TopologyService,
- { provide: LogService, useValue: log },
+ { provide: FnService, useValue: fs},
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: 'Window', useFactory: (() => mockWindow ) }
]
});
+ logServiceSpy = TestBed.get(LogService);
});
it('should be created', inject([TopologyService], (service: 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 b75ed07..1d85a08 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
@@ -15,9 +15,11 @@
*/
import { Injectable } from '@angular/core';
import {
- LogService,
+ 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';
/**
* ONOS GUI -- Topology Service Module.
@@ -25,19 +27,72 @@
@Injectable()
export class TopologyService {
+ private handlers: string[] = [];
+ private openListener: any;
+
constructor(
protected log: LogService,
+ protected wss: WebSocketService
) {
- this.log.debug('Initialized TopologyService');
+ this.log.debug('TopologyService constructed');
}
- init() {
+ /**
+ * bind our event handlers to the web socket service, so that our
+ * callbacks get invoked for incoming events
+ */
+ init(instance: InstanceComponent, background: BackgroundSvgComponent, force: ForceSvgComponent) {
+ this.wss.bindHandlers(new Map<string, (data) => void>([
+ ['topo2AllInstances', (data) => {
+ this.log.warn('Add fn for topo2AllInstances callback', data);
+ instance.onosInstances = data.members;
+ }
+ ],
+ ['topo2CurrentLayout', (data) => {
+ this.log.warn('Add fn for topo2CurrentLayout callback', data);
+ background.layoutData = data;
+ }
+ ],
+ ['topo2CurrentRegion', (data) => {
+ this.log.warn('Add fn for topo2CurrentRegion callback', data);
+ force.regionData = data;
+ }
+ ],
+ ['topo2PeerRegions', (data) => { this.log.warn('Add fn for topo2PeerRegions callback', data); } ],
+ ['topo2UiModelEvent', (data) => { this.log.warn('Add fn for topo2UiModelEvent callback', data); } ],
+ ['topo2Highlights', (data) => { this.log.warn('Add fn for topo2Highlights callback', data); } ],
+ ]));
+ this.handlers.push('topo2AllInstances');
+ this.handlers.push('topo2CurrentLayout');
+ this.handlers.push('topo2CurrentRegion');
+ this.handlers.push('topo2PeerRegions');
+ this.handlers.push('topo2UiModelEvent');
+ this.handlers.push('topo2Highlights');
+ // in case we fail over to a new server,
+ // listen for wsock-open events
+ this.openListener = this.wss.addOpenListener(() => this.wsOpen);
+ // tell the server we are ready to receive topology events
+ this.wss.sendEvent('topo2Start', {});
+ this.log.debug('TopologyService initialized');
}
+ /**
+ * tell the server we no longer wish to receive topology events
+ */
destroy() {
+ this.wss.sendEvent('topo2Stop', {});
+ this.wss.unbindHandlers(this.handlers);
+ this.wss.removeOpenListener(this.openListener);
+ this.openListener = null;
+ this.log.debug('TopologyService destroyed');
+ }
+ wsOpen(host: string, url: string) {
+ this.log.debug('topo2Event: WSopen - cluster node:', host, 'URL:', url);
+ // tell the server we are ready to receive topo events
+ this.wss.sendEvent('topo2Start', {});
}
}
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 0db2c82..d74991c 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
@@ -15,7 +15,7 @@
-->
<onos-flash id="topoMsgFlash" message="{{ flashMsg }}" (closed)="flashMsg = ''"></onos-flash>
-<onos-instance #instance></onos-instance>
+<onos-instance #instance [divTopPx]="80" (mastershipEvent)="force.onosInstMastership = $event"></onos-instance>
<onos-summary #summary></onos-summary>
<onos-toolbar #toolbar></onos-toolbar>
<onos-details #details></onos-details>
@@ -24,8 +24,8 @@
<svg viewBox="0 0 1000 1000" id="topo2">
<svg:g onos-nodeviceconnected />
<svg:g id="topo-zoomlayer">
- <svg:g onos-backgroundsvg/>
- <svg:g onos-forcesvg/>
+ <svg:g #background onos-backgroundsvg/>
+ <svg:g #force onos-forcesvg/>
</svg:g>
</svg>
</div>
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 5933d37..d98a55c 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
@@ -20,10 +20,14 @@
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TopologyComponent } from './topology.component';
-import { InstanceComponent } from '../panel/instance/instance.component';
+import {
+ Instance,
+ InstanceComponent
+} from '../panel/instance/instance.component';
import { SummaryComponent } from '../panel/summary/summary.component';
import { ToolbarComponent } from '../panel/toolbar/toolbar.component';
import { DetailsComponent } from '../panel/details/details.component';
+import { TopologyService } from '../topology.service';
import {
FlashComponent,
@@ -41,6 +45,32 @@
class MockHttpClient {}
+class MockTopologyService {
+ init(instance: InstanceComponent) {
+ instance.onosInstances = [
+ <Instance>{
+ 'id': 'inst1',
+ 'ip': '127.0.0.1',
+ 'reachable': true,
+ 'online': true,
+ 'ready': true,
+ 'switches': 4,
+ 'uiAttached': true
+ },
+ <Instance>{
+ 'id': 'inst1',
+ 'ip': '127.0.0.2',
+ 'reachable': true,
+ 'online': true,
+ 'ready': true,
+ 'switches': 3,
+ 'uiAttached': false
+ }
+ ];
+ }
+ destroy() {}
+}
+
/**
* ONOS GUI -- Topology View -- Unit Tests
*/
@@ -84,6 +114,7 @@
{ provide: LogService, useValue: logSpy },
{ provide: 'Window', useValue: windowMock },
{ provide: HttpClient, useClass: MockHttpClient },
+ { 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 bf46637..728347d 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,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, OnInit, ViewChild } from '@angular/core';
+import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import * as d3 from 'd3';
import {
FnService,
@@ -21,9 +21,12 @@
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 { 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';
/**
* ONOS GUI Topology View
@@ -54,10 +57,13 @@
templateUrl: './topology.component.html',
styleUrls: ['./topology.component.css']
})
-export class TopologyComponent implements OnInit {
+export class TopologyComponent implements OnInit, OnDestroy {
+ // These are references to the components inserted in the template
@ViewChild(InstanceComponent) instance: InstanceComponent;
@ViewChild(SummaryComponent) summary: SummaryComponent;
@ViewChild(DetailsComponent) details: DetailsComponent;
+ @ViewChild(BackgroundSvgComponent) background: BackgroundSvgComponent;
+ @ViewChild(ForceSvgComponent) force: ForceSvgComponent;
flashMsg: string = '';
prefsState = {};
@@ -73,7 +79,8 @@
protected sus: SvgUtilService,
protected ps: PrefsService,
protected wss: WebSocketService,
- protected zs: ZoomService
+ protected zs: ZoomService,
+ protected ts: TopologyService,
) {
this.log.debug('Topology component constructed');
@@ -90,9 +97,19 @@
zoomCallback: (() => { return; })
});
this.zoomEventListeners = [];
+ // The components from the template are handed over to TopologyService here
+ // so that WebSocket responses can be passed back in to them
+ // The handling of the WebSocket call is delegated out to the Topology
+ // Service just to compartmentalize things a bit
+ this.ts.init(this.instance, this.background, this.force);
this.log.debug('Topology component initialized');
}
+ ngOnDestroy() {
+ this.ts.destroy();
+ this.log.debug('Topology component destroyed');
+ }
+
actionMap() {
return {
L: [() => {this.cycleDeviceLabels(); }, 'Cycle device labels'],