Implemented Instance View of Topo in GUI2

Change-Id: If603481e729ebc19a6f91db2739f1b422cc762d0
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