GUI2 added in details panel, updated docs
Change-Id: I49a874deeb4de1082f190ea5d0d985c986a978f8
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.css b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.css
index 9a35cb3..9ddfe17 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.css
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-present Open Networking Foundation
+ * Copyright 2019-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,18 +19,18 @@
padding: 16px;
top: 370px;
}
-html[data-platform='iPad'] #topo2-p-detail {
+html[data-platform='iPad'] {
top: 386px;
}
-#topo2-p-detail .actionBtns .actionBtn {
+.actionBtns .actionBtn {
display: inline-block;
}
-#topo2-p-detail .actionBtns .actionBtn svg {
+.actionBtns .actionBtn svg {
width: 28px;
height: 28px;
}
-#topo2-p-detail div.actionBtns {
+div.actionBtns {
padding-top: 6px;
}
\ No newline at end of file
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 d79954b..405fdfc 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
@@ -1,5 +1,5 @@
<!--
-~ Copyright 2018-present Open Networking Foundation
+~ Copyright 2019-present Open Networking Foundation
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,112 +15,52 @@
-->
<div id="topo2-p-detail" class="floatpanel topo2-p"
style="opacity: 1; right: 20px; width: 260px; top: 350px;" [@detailsPanelState]="on">
+ <!-- Template explanation - Create a HTML header which has an SVG icon along
+ side title text. -->
<div class="header">
<div class="icon clickable">
- <svg>
- <use width="26" height="26" class="glyph" xlink:href="#m_switch"></use>
- </svg>
+ <onos-icon
+ [iconSize]="26"
+ [iconId]="showDetails?.glyphId">
+ </onos-icon>
</div>
- <h2 class="clickable">{{ selectedNode?.id }}</h2>
+ <h2 class="clickable">{{ showDetails?.title }}</h2>
</div>
<div class="body">
<table>
<tbody>
- <tr>
- <td class="label">URI :</td>
- <td class="value">{{ selectedNode?.id }}</td>
- </tr>
- <tr>
- <td class="label">Vendor :</td>
- <td class="value">ONF</td>
- </tr>
- <tr>
- <td class="label">H/W Version :</td>
- <td class="value">0.1</td>
- </tr>
- <tr>
- <td class="label">S/W Version :</td>
- <td class="value">0.1</td>
- </tr>
- <tr>
- <td class="label">Serial # :</td>
- <td class="value">1234</td>
- </tr>
- <tr>
- <td class="label">Protocol :</td>
- <td class="value"></td>
- </tr>
- <tr>
- <td colspan="2">
- <hr>
- </td>
- </tr>
- <tr>
- <td class="label">Ports :</td>
- <td class="value">4</td>
- </tr>
- <tr>
- <td class="label">Flows :</td>
- <td class="value">4</td>
- </tr>
- <tr>
- <td class="label">Tunnels :</td>
- <td class="value">0</td>
- </tr>
+ <!-- Template explanation - Inside a HTML table, create a row per
+ item in the propOrder array returned through the WSS showDetails.
+ If the row name contains only '-' then draw a horiz rule otherwise
+ create a cell for the name and another for the value -->
+ <tr *ngFor="let p of showDetails?.propOrder">
+ <td *ngIf="showDetails?.propLabels[p] !== '-'"
+ class="label">{{showDetails?.propLabels[p]}} :</td>
+ <td *ngIf="showDetails?.propLabels[p] !== '-'"
+ class="value">{{ showDetails?.propValues[p]}}</td>
+ <!-- If the label is '-' then insert a horiz line -->
+ <td *ngIf="showDetails?.propLabels[p] === '-'"
+ colspan="2"><hr></td>
+ </tr>
</tbody>
</table>
</div>
<div class="footer">
<hr>
<div class="actionBtns">
- <div class="actionBtn">
- <div class="button" id="topo2-p-detail-core-showDeviceView">
- <svg class="embeddedIcon" width="25" height="25" viewBox="0 0 50 50">
- <g class="icon">
- <rect width="50" height="50" rx="5"></rect>
- <use width="50" height="50" class="glyph" xlink:href="#switch"></use>
- </g>
- </svg>
- </div>
- </div>
- <div class="actionBtn">
- <div class="button" id="topo2-p-detail-core-showFlowView">
- <svg class="embeddedIcon" width="25" height="25" viewBox="0 0 50 50">
- <g class="icon">
- <rect width="50" height="50" rx="5"></rect>
- <use width="50" height="50" class="glyph" xlink:href="#flowTable"></use>
- </g>
- </svg>
- </div>
- </div>
- <div class="actionBtn">
- <div class="button" id="topo2-p-detail-core-showPortView">
- <svg class="embeddedIcon" width="25" height="25" viewBox="0 0 50 50">
- <g class="icon">
- <rect width="50" height="50" rx="5"></rect>
- <use width="50" height="50" class="glyph" xlink:href="#portTable"></use>
- </g>
- </svg>
- </div>
- </div>
- <div class="actionBtn">
- <div class="button" id="topo2-p-detail-core-showGroupView">
- <svg class="embeddedIcon" width="25" height="25" viewBox="0 0 50 50">
- <g class="icon">
- <rect width="50" height="50" rx="5"></rect>
- <use width="50" height="50" class="glyph" xlink:href="#groupTable"></use>
- </g>
- </svg>
- </div>
- </div>
- <div class="actionBtn">
- <div class="button" id="topo2-p-detail-core-showMeterView">
- <svg class="embeddedIcon" width="25" height="25" viewBox="0 0 50 50">
- <g class="icon">
- <rect width="50" height="50" rx="5"></rect>
- <use width="50" height="50" class="glyph" xlink:href="#meterTable"></use>
- </g>
- </svg>
+ <!-- Template explanation - Inside the panel footer, create an SVG icon
+ per entry in the buttons array returned from the WSS showDetails
+ The icons used here are loaded in the ForceSvgComponent
+ -->
+ <div *ngFor="let btn of showDetails?.buttons" class="actionBtn">
+ <div class="button" id="topo2-p-detail-core-{{ btn }}"
+ (click)="navto(buttonAttribs(btn).path, showDetails.navPath, showDetails.id)">
+ <onos-icon
+ [iconSize]="25"
+ [iconId]="buttonAttribs(btn).gid"
+ [toolTip]="lionFn(buttonAttribs(btn).tt)"
+ classes="icon">
+ </onos-icon>
</div>
</div>
</div>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.spec.ts
index 0c0c269..620d931 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.spec.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-present Open Networking Foundation
+ * Copyright 2019-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,9 +20,10 @@
import { DetailsComponent } from './details.component';
import {
- FnService,
- LogService
+ FnService, LionService,
+ LogService, IconComponent
} from 'gui2-fw-lib';
+import {RouterTestingModule} from '@angular/router/testing';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
@@ -42,6 +43,15 @@
let component: DetailsComponent;
let fixture: ComponentFixture<DetailsComponent>;
+ const bundleObj = {
+ 'core.view.Flow': {
+ test: 'test1'
+ }
+ };
+ const mockLion = (key) => {
+ return bundleObj[key] || '%' + key + '%';
+ };
+
beforeEach(async(() => {
const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
ar = new MockActivatedRoute({ 'debug': 'txrx' });
@@ -60,11 +70,20 @@
fs = new FnService(ar, logSpy, windowMock);
TestBed.configureTestingModule({
- imports: [ BrowserAnimationsModule ],
- declarations: [ DetailsComponent ],
+ imports: [ BrowserAnimationsModule, RouterTestingModule ],
+ declarations: [ DetailsComponent, IconComponent ],
providers: [
{ provide: FnService, useValue: fs },
{ provide: LogService, useValue: logSpy },
+ {
+ provide: LionService, useFactory: (() => {
+ return {
+ bundle: ((bundleId) => mockLion),
+ ubercache: new Array(),
+ loadCbs: new Map<string, () => void>([])
+ };
+ })
+ },
{ provide: 'Window', useValue: windowMock },
]
})
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 935b2ce..f63d99a 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-present Open Networking Foundation
+ * Copyright 2019-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,77 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Component, Input, OnInit} from '@angular/core';
-import { animate, state, style, transition, trigger } from '@angular/animations';
import {
- LogService,
- LoadingService,
- FnService,
+ Component,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ SimpleChanges
+} from '@angular/core';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+import {
DetailsPanelBaseImpl,
+ FnService, LionService,
+ LoadingService,
+ LogService,
WebSocketService
} from 'gui2-fw-lib';
-import {Node} from '../../layer/forcesvg/models';
+import {Host, Link, LinkType, UiElement} from '../../layer/forcesvg/models';
+import {Params, Router} from '@angular/router';
-/*
- ONOS GUI -- Topology Details Panel.
- Displays details of selected device.
+
+interface ButtonAttrs {
+ gid: string;
+ tt: string;
+ path: string;
+}
+
+const SHOWDEVICEVIEW: ButtonAttrs = {
+ gid: 'deviceTable',
+ tt: 'tt_ctl_show_device',
+ path: 'device',
+};
+const SHOWFLOWVIEW: ButtonAttrs = {
+ gid: 'flowTable',
+ tt: 'title_flows',
+ path: 'flow',
+};
+const SHOWPORTVIEW: ButtonAttrs = {
+ gid: 'portTable',
+ tt: 'tt_ctl_show_port',
+ path: 'port',
+};
+const SHOWGROUPVIEW: ButtonAttrs = {
+ gid: 'groupTable',
+ tt: 'tt_ctl_show_group',
+ path: 'group',
+};
+const SHOWMETERVIEW: ButtonAttrs = {
+ gid: 'meterTable',
+ tt: 'tt_ctl_show_meter',
+ path: 'meter',
+};
+const SHOWPIPECONFVIEW: ButtonAttrs = {
+ gid: 'pipeconfTable',
+ tt: 'tt_ctl_show_pipeconf',
+ path: 'pipeconf',
+};
+
+interface ShowDetails {
+ buttons: string[];
+ glyphId: string;
+ id: string;
+ navPath: string;
+ propLabels: Object;
+ propOrder: string[];
+ propValues: Object;
+ title: string;
+}
+/**
+ * ONOS GUI -- Topology Details Panel.
+ * Displays details of selected device. When no device is selected the panel slides
+ * off to the side and disappears
*/
@Component({
selector: 'onos-details',
@@ -40,7 +97,7 @@
trigger('detailsPanelState', [
state('true', style({
transform: 'translateX(0%)',
- opacity: '100'
+ opacity: '1.0'
})),
state('false', style({
transform: 'translateX(100%)',
@@ -51,21 +108,169 @@
])
]
})
-export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit {
- @Input() selectedNode: Node = undefined;
+export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
+ @Input() selectedNode: UiElement = undefined; // Populated when user selects node or link
+
+ // deferred localization strings
+ lionFn; // Function
+ showDetails: ShowDetails; // Will be populated on callback. Cleared if nothing is selected
constructor(
protected fs: FnService,
protected log: LogService,
protected ls: LoadingService,
+ protected router: Router,
protected wss: WebSocketService,
+ private lion: LionService
) {
super(fs, ls, log, wss, 'topo');
- this.log.debug('InstanceComponent constructed');
+
+ if (this.lion.ubercache.length === 0) {
+ this.lionFn = this.dummyLion;
+ this.lion.loadCbs.set('flow', () => this.doLion());
+ } else {
+ this.doLion();
+ }
+
+ this.log.debug('Topo DetailsComponent constructed');
}
- ngOnInit() {
+ /**
+ * When the component is initializing set up the handler for callbacks of
+ * ShowDetails from the WSS. Set the variable showDetails when ever a callback
+ * is made
+ */
+ ngOnInit(): void {
this.on = false;
+ this.wss.bindHandlers(new Map<string, (data) => void>([
+ ['showDetails', (data) => {
+ this.showDetails = data;
+ // this.log.debug('showDetails received', data);
+ }
+ ]
+ ]));
+ this.log.debug('Topo DetailsComponent initialized');
+ }
+
+ /**
+ * When the component is being unloaded then unbind the WSS handler.
+ */
+ ngOnDestroy(): void {
+ this.wss.unbindHandlers(['showDetails']);
+ this.log.debug('Topo DetailsComponent destroyed');
+ }
+
+ /**
+ * If changes are detected on the Input param selectedNode, call on WSS sendEvent
+ * Note the difference in call to the WSS with requestDetails between a node
+ * and a link - the handling is done in TopologyViewMessageHandler#RequestDetails.process()
+ *
+ * The WSS will call back asynchronously (see fn in ngOnInit())
+ *
+ * @param changes Simple Changes set of updates
+ */
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['selectedNode']) {
+ this.selectedNode = changes['selectedNode'].currentValue;
+ let type: any;
+
+ if (this.selectedNode === undefined) {
+ // Selection has been cleared
+ this.showDetails = <ShowDetails>{};
+ return;
+ }
+
+ if (this.selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion
+ type = (<Host>this.selectedNode).nodeType;
+ this.wss.sendEvent('requestDetails', {
+ id: this.selectedNode.id,
+ class: type,
+ });
+ } else if (this.selectedNode.hasOwnProperty('type')) { // Must be link
+ const link: Link = <Link>this.selectedNode;
+ if (<LinkType><unknown>LinkType[link.type] === LinkType.UiEdgeLink) { // Number based enum
+ this.wss.sendEvent('requestDetails', {
+ key: link.id,
+ class: 'link',
+ sourceId: link.epA,
+ targetId: Link.deviceNameFromEp(link.epB),
+ targetPort: link.portB,
+ isEdgeLink: true
+ });
+ } else {
+ this.wss.sendEvent('requestDetails', {
+ key: link.id,
+ class: 'link',
+ sourceId: Link.deviceNameFromEp(link.epA),
+ sourcePort: link.portA,
+ targetId: Link.deviceNameFromEp(link.epB),
+ targetPort: link.portB,
+ isEdgeLink: false
+ });
+ }
+ } else {
+ this.log.warn('Unexpected type for selected element', this.selectedNode);
+ }
+ } else {
+ this.log.warn('Unexpected change in Topo DetailsComponent');
+ }
+ }
+
+ /**
+ * Table of core button attributes to return per button icon
+ * @param btnName The name of the button
+ * @returns A structure with the button attributes
+ */
+ buttonAttribs(btnName: string): ButtonAttrs {
+ switch (btnName) {
+ case 'showDeviceView':
+ return SHOWDEVICEVIEW;
+ case 'showFlowView':
+ return SHOWFLOWVIEW;
+ case 'showPortView':
+ return SHOWPORTVIEW;
+ case 'showGroupView':
+ return SHOWGROUPVIEW;
+ case 'showMeterView':
+ return SHOWMETERVIEW;
+ case 'showPipeConfView':
+ return SHOWPIPECONFVIEW;
+ default:
+ return <ButtonAttrs>{
+ gid: btnName,
+ path: btnName
+ };
+ }
+ }
+
+ /**
+ * Navigate using Angular Routing. Combines the parameters to generate a relative URL
+ * e.g. if params are 'meter', 'device' and 'null:0000000000001' then the
+ * navigation URL will become "http://localhost:4200/#/meter?devId=null:0000000000000002"
+ *
+ * @param path The path to navigate to
+ * @param navPath The parameter name to use
+ * @param selId the parameter value to use
+ */
+ navto(path: string, navPath: string, selId: string): void {
+ this.log.debug('navigate to', path, 'for', navPath, '=', selId);
+ // Special case until it's fixed
+ if (selId) {
+ if (navPath === 'device') {
+ navPath = 'devId';
+ }
+ const queryPar: Params = {};
+ queryPar[navPath] = selId;
+ this.router.navigate([path], { queryParams: queryPar });
+ }
+ }
+
+ /**
+ * Read the LION bundle for Details panel and set up the lionFn
+ */
+ doLion() {
+ this.lionFn = this.lion.bundle('core.view.Flow');
+
}
}