FM GUI as an NPM library for GUI 2

* added dynamic loading of external modules
* new commands on Alarm to allow create/updating/delete
* new fields in alarm gui

Change-Id: I9a7f4d665618a7949bb02039374974dabf6e5363
diff --git a/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/NavResource.java b/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/NavResource.java
new file mode 100644
index 0000000..48c6c89
--- /dev/null
+++ b/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/NavResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018-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.
+ */
+package org.onosproject.ui.impl.gui2;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.onlab.rest.BaseResource;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiView;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * Resource for serving the list of UIExtensions.
+ */
+@Path("nav")
+public class NavResource extends BaseResource {
+
+    @GET
+    @Path("uiextensions")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getNavigation() throws JsonProcessingException {
+        UiExtensionService service = get(UiExtensionService.class);
+        UiViewSerializer serializer = new UiViewSerializer(UiView.class);
+        ObjectMapper mapper = new ObjectMapper();
+
+        SimpleModule module =
+                new SimpleModule("UiViewSerializer");
+        module.addSerializer(serializer);
+        mapper.registerModule(module);
+        StringBuilder sb = new StringBuilder("[");
+        boolean first = true;
+        for (UiExtension uiExt : service.getExtensions()) {
+            for (UiView view : uiExt.views()) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",");
+                }
+                sb.append(mapper.writeValueAsString(view));
+            }
+        }
+        sb.append("]");
+
+        return ok(sb.toString()).build();
+    }
+
+}
diff --git a/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/UiViewSerializer.java b/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/UiViewSerializer.java
new file mode 100644
index 0000000..92cbf4c
--- /dev/null
+++ b/web/gui2/src/main/java/org/onosproject/ui/impl/gui2/UiViewSerializer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018-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.
+ */
+
+package org.onosproject.ui.impl.gui2;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiView;
+
+import java.io.IOException;
+
+public class UiViewSerializer extends StdSerializer<UiView> {
+
+    public UiViewSerializer(Class<UiView> t) {
+        super(t);
+    }
+
+    @Override
+    public void serialize(UiView view,
+                          JsonGenerator jsonGenerator,
+                          SerializerProvider serializerProvider)
+            throws IOException {
+        jsonGenerator.writeStartObject();
+        jsonGenerator.writeStringField("id", view.id());
+        jsonGenerator.writeStringField("icon", view.iconId());
+        jsonGenerator.writeStringField("cat", view.category().toString());
+        jsonGenerator.writeStringField("label", view.label());
+        jsonGenerator.writeEndObject();
+    }
+}
diff --git a/web/gui2/src/main/webapp/WEB-INF/web.xml b/web/gui2/src/main/webapp/WEB-INF/web.xml
index 60c222b..3eda814 100644
--- a/web/gui2/src/main/webapp/WEB-INF/web.xml
+++ b/web/gui2/src/main/webapp/WEB-INF/web.xml
@@ -36,6 +36,10 @@
             <web-resource-name>Secured API</web-resource-name>
             <url-pattern>/rs/applications/*</url-pattern>
         </web-resource-collection>
+        <web-resource-collection>
+            <web-resource-name>Secured Nav</web-resource-name>
+            <url-pattern>/rs/nav/*</url-pattern>
+        </web-resource-collection>
         <auth-constraint>
             <role-name>admin</role-name>
         </auth-constraint>
@@ -144,7 +148,8 @@
                 org.glassfish.jersey.media.multipart.MultiPartFeature,
                 org.onosproject.ui.impl.gui2.LogoutResource,
                 <!--org.onosproject.ui.impl.TopologyResource,-->
-                org.onosproject.ui.impl.ApplicationResource
+                org.onosproject.ui.impl.ApplicationResource,
+                org.onosproject.ui.impl.gui2.NavResource
             </param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
diff --git a/web/gui2/src/main/webapp/app/nav/nav.component.html b/web/gui2/src/main/webapp/app/nav/nav.component.html
index 505dc46..04c60b3 100644
--- a/web/gui2/src/main/webapp/app/nav/nav.component.html
+++ b/web/gui2/src/main/webapp/app/nav/nav.component.html
@@ -1,5 +1,5 @@
 <!--
-~ Copyright 2014-present Open Networking Foundation
+~ Copyright 2018-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,38 +15,21 @@
 -->
 <nav id="nav" [@navState]="ns.showNav">
     <div id="platform" class="nav-hdr">{{ lionFn('cat_platform') }}</div>
-
-    <a id="app" (click)="ns.hideNav()" routerLink="/app" routerLinkActive="active">
-        <onos-icon iconId="nav_apps"></onos-icon> Applications</a>
-
-    <a id="settings" (click)="ns.hideNav()" routerLink="/settings" routerLinkActive="active">
-        <onos-icon iconId="nav_settings"></onos-icon> Settings</a>
-
-    <a id="cluster" (click)="ns.hideNav()" routerLink="/cluster" routerLinkActive="active">
-        <onos-icon iconId="nav_cluster"></onos-icon> Cluster Nodes</a>
-
-    <a id="processor" (click)="ns.hideNav()" routerLink="/processor" routerLinkActive="active">
-        <onos-icon iconId="nav_processors"></onos-icon> Packet Processors</a>
-
-    <a id="partition" (click)="ns.hideNav()" routerLink="/partition" routerLinkActive="active">
-        <onos-icon iconId="nav_partitions"></onos-icon> Partitions</a>
+    <div *ngFor="let uiView of ns.uiPlatformViews">
+        <a id="{{uiView.id}}" (click)="ns.hideNav()" routerLink="/{{uiView.id}}">
+        <onos-icon iconId="{{ uiView.icon }}"></onos-icon> {{ uiView.label }}</a>
+    </div>
 
     <div id="network" class="nav-hdr">{{ lionFn('cat_network') }}</div>
+    <div *ngFor="let uiView of ns.uiNetworkViews">
+        <a id="{{uiView.id}}" (click)="ns.hideNav()" routerLink="/{{uiView.id}}">
+            <onos-icon iconId="{{ uiView.icon }}"></onos-icon> {{ uiView.label }}</a>
+    </div>
 
-    <a id="device" (click)="ns.hideNav()" routerLink="/device" routerLinkActive="active">
-        <onos-icon iconId="nav_devs"></onos-icon> Devices</a>
+    <div *ngIf="ns.uiOtherViews.length > 0" id="other" class="nav-hdr">{{ lionFn('cat_other') }}</div>
+    <div *ngFor="let uiView of ns.uiOtherViews">
+        <a id="{{uiView.id}}" (click)="ns.hideNav()" routerLink="/{{uiView.id}}">
+            <onos-icon iconId="{{ uiView.icon }}"></onos-icon> {{ uiView.label }}</a>
+    </div>
 
-    <a id="link" (click)="ns.hideNav()" routerLink="/link" routerLinkActive="active">
-        <onos-icon iconId="nav_links"></onos-icon> Links</a>
-
-    <a id="host" (click)="ns.hideNav()" routerLink="/host" routerLinkActive="active">
-        <onos-icon iconId="nav_hosts"></onos-icon> Hosts</a>
-
-    <a id="intent" (click)="ns.hideNav()" routerLink="/intent" routerLinkActive="active">
-        <onos-icon iconId="nav_intents"></onos-icon> Intents</a>
-
-    <a id="tunnel" (click)="ns.hideNav()" routerLink="/tunnel" routerLinkActive="active">
-        <onos-icon iconId="nav_tunnels"></onos-icon> Tunnels</a>
-
-    <div id="other" class="nav-hdr">{{ lionFn('cat_other') }}</div>
 </nav>
diff --git a/web/gui2/src/main/webapp/app/nav/nav.component.spec.ts b/web/gui2/src/main/webapp/app/nav/nav.component.spec.ts
index c5e8c35..acc400f 100644
--- a/web/gui2/src/main/webapp/app/nav/nav.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/nav/nav.component.spec.ts
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { ActivatedRoute, Params } from '@angular/router';
+import { RouterModule, ActivatedRoute, Params } from '@angular/router';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { DebugElement } from '@angular/core';
 import { By } from '@angular/platform-browser';
-import { of } from 'rxjs';
+import { of, from } from 'rxjs';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
 
 import {
     ConsoleLoggerService,
@@ -37,7 +38,23 @@
     }
 }
 
-class MockNavService {}
+class MockHttpClient {
+    get() {
+        return from(['{"id":"app","icon":"nav_apps","cat":"PLATFORM","label":"Applications"},' +
+            '{"id":"settings","icon":"nav_settings","cat":"PLATFORM","label":"Settings"}']);
+    }
+
+    subscribe() {}
+}
+
+class MockNavService {
+    uiPlatformViews = [];
+    uiNetworkViews = [];
+    uiOtherViews = [];
+    uiHiddenViews = [];
+
+    getUiViews() {}
+}
 
 class MockIconService {
     loadIconDef() {}
@@ -80,11 +97,12 @@
         fs = new FnService(ar, log, windowMock);
 
         TestBed.configureTestingModule({
-            imports: [ BrowserAnimationsModule ],
+            imports: [ BrowserAnimationsModule, RouterModule ],
             declarations: [ NavComponent, IconComponent ],
             providers: [
                 { provide: FnService, useValue: fs },
                 { provide: IconService, useClass: MockIconService },
+                { provide: HttpClient, useClass: MockHttpClient },
                 { provide: LionService, useFactory: (() => {
                         return {
                             bundle: ((bundleId) => mockLion),
@@ -124,41 +142,6 @@
         expect(div.textContent).toEqual('%cat_platform%');
     });
 
-    it('should have an app view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#app'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Applications');
-    });
-
-    it('should have an cluster view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#cluster'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Cluster Nodes');
-    });
-
-    it('should have an processor view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#processor'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Packet Processors');
-    });
-
-    it('should have a settings view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#settings'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Settings');
-    });
-
-    it('should have a partition view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#partition'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Partitions');
-    });
-
     it('should have a network div.nav-hdr inside a nav#nav', () => {
         const appDe: DebugElement = fixture.debugElement;
         const divDe = appDe.query(By.css('nav#nav div#network.nav-hdr'));
@@ -166,38 +149,4 @@
         expect(div.textContent).toEqual('%cat_network%');
     });
 
-    it('should have a device view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#device'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Devices');
-    });
-
-    it('should have a link view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#link'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Links');
-    });
-
-    it('should have a host view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#host'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Hosts');
-    });
-
-    it('should have a intent view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#intent'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Intents');
-    });
-
-    it('should have a tunnel view link inside a nav#nav', () => {
-        const appDe: DebugElement = fixture.debugElement;
-        const divDe = appDe.query(By.css('nav#nav a#tunnel'));
-        const div: HTMLElement = divDe.nativeElement;
-        expect(div.textContent).toEqual(' Tunnels');
-    });
 });
diff --git a/web/gui2/src/main/webapp/app/nav/nav.component.ts b/web/gui2/src/main/webapp/app/nav/nav.component.ts
index b543049..058f514 100644
--- a/web/gui2/src/main/webapp/app/nav/nav.component.ts
+++ b/web/gui2/src/main/webapp/app/nav/nav.component.ts
@@ -52,7 +52,7 @@
     constructor(
         private log: LogService,
         private lion: LionService,
-        public ns: NavService
+        public ns: NavService,
     ) {
         this.log.debug('NavComponent constructed');
     }
@@ -70,6 +70,7 @@
         } else {
             this.doLion();
         }
+        this.ns.getUiViews();
     }
 
     /**
@@ -94,5 +95,4 @@
     dummyLion(key: string): string {
         return '%' + key + '%';
     }
-
 }
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 327f17e..344e6d9 100644
--- a/web/gui2/src/main/webapp/app/onos-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/onos-routing.module.ts
@@ -15,6 +15,7 @@
  */
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
+import { FmGui2LibModule } from 'fm-gui2-lib';
 
 /**
  * The set of Routes in the application - can be chosen from nav menu or
@@ -77,6 +78,11 @@
         path: 'meter',
         loadChildren: 'app/view/meter/meter.module#MeterModule'
     },
+/*  Comment out below section for running locally with 'ng serve' when developing */
+    {
+        path: 'alarmTable',
+        loadChildren: 'fm-gui2-lib#FmGui2LibModule'
+    },
     {
         path: '',
         redirectTo: 'device', // Default to devices view - change to topo in future
@@ -92,7 +98,7 @@
  */
 @NgModule({
     imports: [
-        RouterModule.forRoot(onosRoutes)
+        RouterModule.forRoot(onosRoutes, { useHash : true })
     ],
     exports: [RouterModule],
     providers: []
diff --git a/web/gui2/src/main/webapp/app/onos.component.html b/web/gui2/src/main/webapp/app/onos.component.html
index 2012a17..0fedcca 100644
--- a/web/gui2/src/main/webapp/app/onos.component.html
+++ b/web/gui2/src/main/webapp/app/onos.component.html
@@ -1,5 +1,5 @@
 <!--
-~ Copyright 2014-present Open Networking Foundation
+~ Copyright 2018-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.
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html
index 230263d..4a07787 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html
@@ -16,7 +16,7 @@
 <div id="ov-app" (dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)" (drop)="onDrop($event)">
     <div class="tabular-header">
         <onos-flash id="appMsgFlash" message="{{ alertMsg }}" dwell="5000" warning="true" (closed)="alertMsg = ''"></onos-flash>
-        <onos-confirm message="{{ confirmMsg }}" warning="{{ strongWarning }}" (chosen)="dOk($event)"></onos-confirm>
+        <onos-confirm title="{{ lionFn('dlg_confirm_action') }}" message="{{ confirmMsg }}" warning="{{ strongWarning }}" (chosen)="dOk($event)"></onos-confirm>
         <h2>
             {{lionFn('title_apps')}}
             ({{ tableData.length }}
diff --git a/web/gui2/src/main/webapp/app/view/device/devicedetails/devicedetails.component.ts b/web/gui2/src/main/webapp/app/view/device/devicedetails/devicedetails.component.ts
index b81b68f..0d0eedb 100644
--- a/web/gui2/src/main/webapp/app/view/device/devicedetails/devicedetails.component.ts
+++ b/web/gui2/src/main/webapp/app/view/device/devicedetails/devicedetails.component.ts
@@ -74,7 +74,7 @@
 
     ngOnInit() {
         this.init();
-        this.log.debug('App Details Component initialized:', this.id);
+        this.log.debug('Device Details Component initialized:', this.id);
     }
 
     /**
@@ -82,7 +82,7 @@
      */
     ngOnDestroy() {
         this.destroy();
-        this.log.debug('App Details Component destroyed');
+        this.log.debug('Device Details Component destroyed');
     }
 
     /**
diff --git a/web/gui2/src/main/webapp/index.html b/web/gui2/src/main/webapp/index.html
index 9ca6a02..780634c 100644
--- a/web/gui2/src/main/webapp/index.html
+++ b/web/gui2/src/main/webapp/index.html
@@ -24,6 +24,7 @@
     <meta name="apple-mobile-web-app-status-bar-style" content="black">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
+    <!-- Needs investigation - should not have any external dependencies -->
     <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,700'
           rel='stylesheet' type='text/css'>
     <link href="onos.theme.css" type='text/css'>
diff --git a/web/gui2/src/test/java/org/onosproject/ui/impl/gui2/UiViewSerializerTest.java b/web/gui2/src/test/java/org/onosproject/ui/impl/gui2/UiViewSerializerTest.java
new file mode 100644
index 0000000..095ae83
--- /dev/null
+++ b/web/gui2/src/test/java/org/onosproject/ui/impl/gui2/UiViewSerializerTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018-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.
+ */
+
+package org.onosproject.ui.impl.gui2;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class UiViewSerializerTest {
+
+    UiView uiView1 = new UiView(UiView.Category.NETWORK, "uiView1", "uiView1", "icon1");
+    UiView uiView2 = new UiView(UiView.Category.OTHER, "uiView2", "uiView2", "icon2");
+
+    @Before
+    public void init() {
+    }
+
+    @Test
+    public void uiViewSerializerTest() throws JsonProcessingException {
+        UiViewSerializer serializer = new UiViewSerializer(UiView.class);
+        ObjectMapper mapper = new ObjectMapper();
+
+        SimpleModule module =
+                new SimpleModule("UiViewSerializer");
+        module.addSerializer(serializer);
+        mapper.registerModule(module);
+        String ext = mapper.writeValueAsString(uiView1);
+        assertEquals("{\"id\":\"uiView1\",\"icon\":\"icon1\",\"cat\":\"NETWORK\",\"label\":\"uiView1\"}", ext);
+    }
+}