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');
+
     }
 
 }