Added native Bazel build to GUI2. Reduced a lot of the unused Angular CLI structures

Reviewers should look at the changes in WORKSPACE, BUILD, BUILD.bazel, README.md files
This is only possible now as rules_nodejs went to 1.0.0 on December 20
gui2 has now been made the entry point (rather than gui2-fw-lib)
No tests or linting are functional yet for Typescript
Each NgModule now has its own BUILD.bazel file with ng_module
gui2-fw-lib is all one module and has been refactored to simplify the directory structure
gui2-topo-lib is also all one module - its directory structure has had 3 layers removed
The big bash script in web/gui2/BUILD has been removed - all is done through ng_module rules
in web/gui2/src/main/webapp/BUILD.bazel and web/gui2/src/main/webapp/app/BUILD.bazel

Change-Id: Ifcfcc23a87be39fe6d6c8324046cc8ebadb90551
diff --git a/web/gui2-topo-lib/lib/panel/details/details.component.ts b/web/gui2-topo-lib/lib/panel/details/details.component.ts
new file mode 100644
index 0000000..a0a2837
--- /dev/null
+++ b/web/gui2-topo-lib/lib/panel/details/details.component.ts
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+import {DetailsPanelBaseImpl, FnService, LionService, LogService, WebSocketService} from '../../../../gui2-fw-lib/public_api';
+import {Host, Link, LinkType, NodeType, UiElement} from '../../layer/forcesvg/models';
+import {Params, Router} from '@angular/router';
+
+
+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',
+};
+const RELATEDINTENTS: ButtonAttrs = {
+    gid: 'm_relatedIntents',
+    tt: 'tr_btn_show_related_traffic',
+    path: 'relatedIntents',
+};
+const CREATEHOSTTOHOSTFLOW: ButtonAttrs = {
+    gid: 'm_endstation',
+    tt: 'tr_btn_create_h2h_flow',
+    path: 'create_h2h_flow',
+};
+const CREATEMULTISOURCEFLOW: ButtonAttrs = {
+    gid: 'm_flows',
+    tt: 'tr_btn_create_msrc_flow',
+    path: 'create_msrc_flow',
+};
+
+
+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
+ *
+ * This Panel is a child of the Topology component and it gets the 'selectedNodes'
+ * from there as an input component. See TopologyComponent.nodeSelected()
+ * The topology component gets these by listening to events from ForceSvgComponent
+ * which gets them in turn from Device, Host, SubRegion and Link components. This
+ * is so that each component respects the hierarchy
+ */
+@Component({
+    selector: 'onos-details',
+    templateUrl: './details.component.html',
+    styleUrls: [
+        './details.component.css', './details.theme.css',
+        '../../topology.common.css',
+        '../../../../gui2-fw-lib/lib/widget/panel.css',
+        '../../../../gui2-fw-lib/lib/widget/panel-theme.css'
+    ],
+    animations: [
+        trigger('detailsPanelState', [
+            state('true', style({
+                transform: 'translateX(0%)',
+                opacity: '1.0'
+            })),
+            state('false', style({
+                transform: 'translateX(100%)',
+                opacity: '0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
+    @Input() selectedNodes: UiElement[] = []; // Populated when user selects node or link
+    @Input() on: boolean = false; // Override the parent class attribute
+
+    // deferred localization strings
+    lionFnTopo; // Function
+    lionFnFlow; // Function for flow bundle
+    showDetails: ShowDetails; // Will be populated on callback. Cleared if nothing is selected
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected router: Router,
+        protected wss: WebSocketService,
+        private lion: LionService
+    ) {
+        super(fs, log, wss, 'topo');
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFnTopo = this.dummyLion;
+            this.lionFnFlow = this.dummyLion;
+            this.lion.loadCbs.set('detailscore', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
+        this.log.debug('Topo DetailsComponent constructed');
+    }
+
+    /**
+     * 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.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
+     * and expect ShowDetails to be updated from data sent back from server.
+     *
+     * Note the difference in call to the WSS with requestDetails between a node
+     * and a link - the handling is done in TopologyViewMessageHandler#RequestDetails.process()
+     *
+     * When multiple items are selected fabricate the ShowDetails here, and
+     * present buttons that allow custom actions
+     *
+     * The WSS will call back asynchronously (see fn in ngOnInit())
+     *
+     * @param changes Simple Changes set of updates
+     */
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes['selectedNodes']) {
+            this.selectedNodes = changes['selectedNodes'].currentValue;
+            let type: any;
+            if (this.selectedNodes.length === 0) {
+                // Selection has been cleared
+                this.showDetails = <ShowDetails>{};
+                return;
+            } else if (this.selectedNodes.length > 1) {
+                // Don't send message to WSS just form dialog here
+                const propOrder: string[] = [];
+                const propValues: Object = {};
+                const propLabels: Object = {};
+                let numHosts: number = 0;
+                for (let i = 0; i < this.selectedNodes.length; i++) {
+                    propOrder.push(i.toString());
+                    propLabels[i.toString()] = i.toString();
+                    propValues[i.toString()] = this.selectedNodes[i].id;
+                    if (this.selectedNodes[i].hasOwnProperty('nodeType') &&
+                        (<Host>this.selectedNodes[i]).nodeType === NodeType.HOST) {
+                        numHosts++;
+                    } else {
+                        numHosts = -128; // Negate the whole thing so other buttons will not be shown
+                    }
+                }
+                const buttons: string[] = [];
+                if (numHosts === 2) {
+                    buttons.push('createHostToHostFlow');
+                } else if (numHosts > 2) {
+                    buttons.push('createMultiSourceFlow');
+                }
+                buttons.push('relatedIntents');
+
+                this.showDetails = <ShowDetails>{
+                    buttons: buttons,
+                    glyphId: undefined,
+                    id: 'multiple',
+                    navPath: undefined,
+                    propLabels: propLabels,
+                    propOrder: propOrder,
+                    propValues: propValues,
+                    title: this.lionFnTopo('title_selected_items')
+                };
+                this.log.debug('Details panel generated from multiple devices', this.showDetails);
+                return;
+            }
+
+            // If only one thing has been selected then request details of that from the server
+            const selectedNode = this.selectedNodes[0];
+            if (selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion
+                type = (<Host>selectedNode).nodeType;
+                this.wss.sendEvent('requestDetails', {
+                    id: selectedNode.id,
+                    class: type,
+                });
+            } else if (selectedNode.hasOwnProperty('type')) { // Must be link
+                const link: Link = <Link>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', selectedNode);
+            }
+        }
+    }
+
+    /**
+     * 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;
+            case 'relatedIntents':
+                return RELATEDINTENTS;
+            case 'createHostToHostFlow':
+                return CREATEHOSTTOHOSTFLOW;
+            case 'createMultiSourceFlow':
+                return CREATEMULTISOURCEFLOW;
+            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"
+     *
+     * When multiple hosts are selected other actions have to be accommodated
+     *
+     * @param path The path to navigate to
+     * @param navPath The parameter name to use
+     * @param selId the parameter value to use
+     */
+    navto(path: string): void {
+        this.log.debug('navigate to', path, 'for',
+            this.showDetails.navPath, '=', this.showDetails.id);
+
+        const ids: string[] = [];
+        Object.values(this.showDetails.propValues).forEach((v) => ids.push(v));
+        if (path === 'relatedIntents' && this.showDetails.id === 'multiple') {
+            this.wss.sendEvent('topo2RequestRelatedIntents', {
+                'ids': ids,
+                'hover': ''
+            });
+
+        } else if (path === 'create_h2h_flow' && this.showDetails.id === 'multiple') {
+            this.wss.sendEvent('topo2AddHostIntent', {
+                'one': ids[0],
+                'two': ids[1],
+                'ids': ids
+            });
+
+        } else if (path === 'create_msrc_flow' && this.showDetails.id === 'multiple') {
+            // Should only happen when there are 3 or more ids
+            this.wss.sendEvent('topo2AddMultiSourceIntent', {
+                'src': ids.slice(0, ids.length - 1),
+                'dst': ids[ids.length - 1],
+                'ids': ids
+            });
+
+        } else if (this.showDetails.id) {
+            let navPath = this.showDetails.navPath;
+            if (navPath === 'device') {
+                navPath = 'devId';
+            }
+            const queryPar: Params = {};
+            queryPar[navPath] = this.showDetails.id;
+            this.router.navigate([path], { queryParams: queryPar });
+        }
+    }
+
+    /**
+     * Read the LION bundle for Details panel and set up the lionFn
+     */
+    doLion() {
+        this.lionFnTopo = this.lion.bundle('core.view.Topo');
+        this.lionFnFlow = this.lion.bundle('core.view.Flow');
+    }
+
+}