Added actions to the Apps view of web/gui2

Change-Id: I3d96a324590bee4de0875d4f533cc723c7f6ba52
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
index d8f5a44..0826c52 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
@@ -51,14 +51,22 @@
     public Response upload(@QueryParam("activate") @DefaultValue("false") String activate,
                            @FormDataParam("file") InputStream stream) throws IOException {
         ApplicationAdminService service = get(ApplicationAdminService.class);
-        Application app = service.install(stream);
-        synchronized (LAST_INSTALLED_APP_NAME_LOCK) {
-            lastInstalledAppName = app.id().name();
+        try {
+            Application app = service.install(stream);
+            synchronized (LAST_INSTALLED_APP_NAME_LOCK) {
+                lastInstalledAppName = app.id().name();
+            }
+            if (Objects.equals(activate, "true")) {
+                service.activate(app.id());
+            }
+            return Response.ok().build();
+        } catch (IllegalStateException e) {
+            return Response.status(
+                    Response.Status.PRECONDITION_FAILED.getStatusCode(),
+                    e.getMessage()).build();
+        } catch (Exception e) {
+            throw e;
         }
-        if (Objects.equals(activate, "true")) {
-            service.activate(app.id());
-        }
-        return Response.ok().build();
     }
 
     /**
diff --git a/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.css b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.css
new file mode 100644
index 0000000..80243b0
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.css
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Confirm Component (layout) -- CSS file
+ */
+#app-dialog {
+    top: 140px;
+    padding: 12px;
+}
+
+#app-dialog p {
+    font-size: 12pt;
+}
+
+#app-dialog p.strong {
+    font-weight: bold;
+    padding: 8px;
+    text-align: center;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.html b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.html
new file mode 100644
index 0000000..a3b76f4
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.html
@@ -0,0 +1,21 @@
+<!--
+~ 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.
+-->
+<div id="app-dialog" class="floatpanel dialog" [@confirmDlgState]="message!==''">
+    <p>{{ message }}</p>
+    <p *ngIf="warning" class="warning strong">{{ warning }}</p>
+    <div tabindex="10" class="dialog-button" (click)="choice(true)">OK</div>
+    <div tabindex="11" class="dialog-button" (click)="choice(false)">Cancel</div>
+</div>
diff --git a/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.spec.ts b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.spec.ts
new file mode 100644
index 0000000..9a88dc9
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.spec.ts
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { ConsoleLoggerService } from '../../../consolelogger.service';
+import { LogService } from '../../../log.service';
+import { ConfirmComponent } from './confirm.component';
+
+/**
+ * ONOS GUI -- Layer -- Confirm Component - Unit Tests
+ */
+describe('ConfirmComponent', () => {
+    let log: LogService;
+    let component: ConfirmComponent;
+    let fixture: ComponentFixture<ConfirmComponent>;
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ ConfirmComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+            ]
+        });
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(ConfirmComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have a div.dialog-button inside a div#app-dialog', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#app-dialog div.dialog-button'));
+        const div: HTMLElement = divDe.nativeElement;
+        // It selects the first one
+        expect(div.textContent).toEqual('OK');
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.ts b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.ts
new file mode 100644
index 0000000..cd6aa7a
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.component.ts
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+import { LogService } from '../../../log.service';
+
+/**
+ * ONOS GUI -- Layer -- Confirm Component
+ *
+ * Replaces Flash Service in old GUI.
+ * Provides a mechanism to present a confirm dialog to the screen
+ *
+ * To use add an element to the template like
+ *   <onos-confirm message="Performing something dangerous. Would you like to proceed"></onos-flash>
+ *
+ * An event is raised with either OK or Cancel
+ */
+@Component({
+    selector: 'onos-confirm',
+    templateUrl: './confirm.component.html',
+    styleUrls: [
+        './confirm.component.css',
+        './confirm.theme.css',
+        '../dialog.css',
+        '../dialog.theme.css',
+        '../../widget/panel.css',
+        '../../widget/panel-theme.css'
+    ],
+    animations: [
+        trigger('confirmDlgState', [
+            state('true', style({
+                transform: 'translateX(-100%)',
+                opacity: '100'
+            })),
+            state('false', style({
+                transform: 'translateX(0%)',
+                opacity: '0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class ConfirmComponent {
+    @Input() message: string;
+    @Input() warning: string;
+    @Output() chosen: EventEmitter<boolean> = new EventEmitter();
+
+    constructor(
+        private log: LogService,
+    ) {
+        this.log.debug('ConfirmComponent constructed');
+    }
+
+    /**
+     * When OK or Cancel is pressed, send an event to parent with choice
+     */
+    choice(chosen: boolean): void {
+        this.chosen.emit(chosen);
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.theme.css b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.theme.css
new file mode 100644
index 0000000..db97648
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/confirm/confirm.theme.css
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Confirm Component (theme) -- CSS file
+ */
+/* temporarily removed .light */
+#app-dialog p.strong {
+    color: white;
+    background-color: #ce5b58;
+}
+
+#app-dialog.floatpanel.dialog {
+    background-color: #ffffff;
+}
+
+#app-dialog p.strong {
+    color: white;
+    background-color: #ce5b58;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/dialog.css b/web/gui2/src/main/webapp/app/fw/layer/dialog.css
new file mode 100644
index 0000000..fdb33d1
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/dialog.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/*
+ ONOS GUI -- Dialog Service (layout) -- CSS file
+ */
+
+.dialog h2 {
+    margin: 0;
+    word-wrap: break-word;
+    display: inline-block;
+    width: 210px;
+    vertical-align: middle;
+}
+
+.dialog .dialog-button {
+    display: inline-block;
+    cursor: pointer;
+    height: 20px;
+    padding: 6px 8px 2px 8px;
+    margin: 4px;
+    float: right;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/dialog.theme.css b/web/gui2/src/main/webapp/app/fw/layer/dialog.theme.css
new file mode 100644
index 0000000..dfcc3cc
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/dialog.theme.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/*
+ ONOS GUI -- Dialog Service (theme) -- CSS file
+ */
+
+/* temporarily removed .light */
+.dialog .dialog-button {
+    background-color: #518ecc;
+    color: white;
+}
+
+
+/* ========== DARK Theme ========== */
+
+.dark .dialog .dialog-button {
+    background-color: #345e85;
+    color: #cccccd;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.css b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.css
index 829d5e2..66b8f3b 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.css
+++ b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.css
@@ -15,33 +15,54 @@
  */
 
 /*
- ONOS GUI -- Flash Service (layout) -- CSS file
+ ONOS GUI -- Flash Component (layout) -- CSS file
  */
 
 #flash {
-    z-index: 1400;
-}
-
-#flash svg {
-    /*position: absolute;*/
-    bottom: 0;
-    opacity: 0.8;
-}
-
-#flash svg g.flashItem text {
-    stroke: none;
-    text-anchor: middle;
-    alignment-baseline: middle;
-    font-size: 16pt;
-}
-
-/* Used for temp div */
-.centered {
     position: fixed;
     top: 50%;
     left: 50%;
-    -webkit-transform: translate(-50%, -50%);
     transform: translate(-50%, -50%);
-    margin: 0;
+    z-index: 1400;
+}
+
+#flash.warning div#flashBox {
+    border: 2px solid #222222;
+    border-radius: 10px;
+    background: #FFFFFF;
     padding: 10px;
 }
+
+#flash div#flashBox {
+    background: #CCCCCC;
+    border-radius: 10px;
+    padding: 1px;
+}
+
+#flash div#flashBox div.dialog-button {
+    transform :translateY(-32px);
+}
+
+#flash.warning p#flashText {
+    stroke: #FF0000;
+    color: #FF0000;
+    text-anchor: middle;
+    alignment-baseline: middle;
+    text-align: center;
+    font-size: 16pt;
+    border-radius: 10px;
+    background: #FFFFFF;
+    padding: 10px;
+}
+
+#flash p#flashText {
+    stroke: none;
+    color: #222222;
+    text-anchor: middle;
+    alignment-baseline: middle;
+    text-align: center;
+    font-size: 16pt;
+    border-radius: 10px;
+    background: #CCCCCC;
+    padding: 5px;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.html b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.html
index 5566013..46b6de9 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.html
+++ b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.html
@@ -13,15 +13,9 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<div id="flash">
-    <svg *ngIf="enabled" [attr.width]="width" [attr.height]="height" [attr.viewBox]="vbox" >
-        <g class="flashItem" [@flashState]="enabled?'active':'inactive'">
-            <text #flashtext>{{ message }}</text>
-            <rect [attr.opacity]="0.5" [attr.rx]="rx"
-                      [attr.x]="-flashtext.getBBox().width/2-xpad"
-                      [attr.y]="flashtext.getBBox().y-ypad"
-                      [attr.height]="flashtext.getBBox().height+ypad*2"
-                      [attr.width]="flashtext.getBBox().width+xpad*2"></rect>
-        </g>
-    </svg>
+<div id="flash" class="dialog" [ngClass]="warning?'warning':''" [@flashState]="visible">
+    <div id="flashBox" *ngIf="visible">
+        <p id="flashText">{{ message }}</p>
+        <div class="dialog-button" *ngIf="dwell>1200" (click)="closeNow()">Dismiss</div>
+    </div>
 </div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.spec.ts b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.spec.ts
new file mode 100644
index 0000000..14efa19
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.spec.ts
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { ConsoleLoggerService } from '../../../consolelogger.service';
+import { LogService } from '../../../log.service';
+import { FlashComponent } from './flash.component';
+
+/**
+ * ONOS GUI -- Layer -- Flash Component - Unit Tests
+ */
+describe('FlashComponent', () => {
+    let log: LogService;
+    let component: FlashComponent;
+    let fixture: ComponentFixture<FlashComponent>;
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ FlashComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+            ]
+        });
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(FlashComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+//    it('should have a div#flash', () => {
+//        component.enabled = true;
+//        const appDe: DebugElement = fixture.debugElement;
+//        const divDe = appDe.query(By.css('div#flash'));
+//        expect(divDe).toBeTruthy();
+//    });
+});
diff --git a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.ts b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.ts
index 3a70c35..df08a96 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/flash/flash.component.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component } from '@angular/core';
+import { Component, Input, Output, OnChanges, SimpleChange, EventEmitter } from '@angular/core';
 import { LogService } from '../../../log.service';
 import { trigger, state, style, animate, transition } from '@angular/animations';
 
@@ -24,55 +24,68 @@
  * Provides a mechanism to flash short informational messages to the screen
  * to alert the user of something, e.g. "Hosts visible" or "Hosts hidden".
  *
+ * It can be used in a warning mode, where text will appear in red
+ * The dwell time (milliseconds) can be controlled or the default is 1200ms
+ *
  * To use add an element to the template like
- *   <onos-flash #flashComponent (click)="flashComponent.flash('Hosts visible')"></onos-flash>
- * 1) The (click) can be removed and the call to flash() can be called from anywhere else in the template.
- * 2) This whole element can be disabled until needed with an ngIf
+ *   <onos-flash message="Hosts visible" dwell="2000" warning="true"></onos-flash>
+ * This whole element can be disabled until needed with an ngIf, but if this is done
+ * the animated fade-in and fade-out will not happen
+ * There is also a (closed) event that tells you when the message is closed, or
+ * fades-out
  */
 @Component({
     selector: 'onos-flash',
     templateUrl: './flash.component.html',
-    styleUrls: ['./flash.component.css'],
+    styleUrls: [
+        './flash.component.css',
+        '../dialog.css',
+        '../dialog.theme.css',
+    ],
     animations: [
         trigger('flashState', [
-            state('inactive', style({
+            state('false', style({
+//                transform: 'translateY(-400%)',
                 opacity: '0.0',
             })),
-            state('active', style({
+            state('true', style({
+//                transform: 'translateY(0%)',
                 opacity: '1.0',
             })),
-            transition('inactive => active', animate('200ms ease-in')),
-            transition('active => inactive', animate('200ms ease-out'))
+            transition('0 => 1', animate('200ms ease-in')),
+            transition('1 => 0', animate('200ms ease-out'))
         ])
     ]
 })
-export class FlashComponent {
-    public message: string;
+export class FlashComponent implements OnChanges {
+    @Input() message: string;
+    @Input() dwell: number = 1200; // milliseconds
+    @Input() warning: boolean = false;
+    @Output() closed: EventEmitter<boolean> = new EventEmitter();
 
-    public width: string = '100%';
-    public height: number = 200;
-    public rx: number = 10;
-    public vbox: string = '-200 -' + (this.height / 2) + ' 400 ' + this.height;
-    public xpad: number = 20;
-    public ypad: number = 10;
-    public enabled: boolean = false;
-
-    constructor(
-        private log: LogService,
-    ) {
-        this.log.debug('FlashComponent constructed');
-    }
+    public visible: boolean = false;
 
     /**
      * Flash a message up for 1200ms then disappear again.
      * See animation parameter for the ease in and ease out params
      */
-    flash(message: string): void {
-        this.message = message;
-        this.enabled = true;
+    ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
+        if (changes['message'] && this.message && this.message !== '') {
+            this.visible = true;
 
-        setTimeout(() => {
-            this.enabled = false;
-        }, 1200);
+            setTimeout(() => {
+                this.visible = false;
+                this.closed.emit(false);
+            }, this.dwell);
+        }
+    }
+
+    /**
+     * The message will flash up for 'dwell' milliseconds
+     * If dwell is > 2000ms, then there will be a button that allows it to be dismissed now
+     */
+    closeNow() {
+        this.visible = false;
+        this.closed.emit(false);
     }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
index 4e8abe7..21806c5 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
@@ -16,6 +16,7 @@
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 
+import { ConfirmComponent } from './confirm/confirm.component';
 import { FlashComponent } from './flash/flash.component';
 import { DialogService } from './dialog.service';
 import { EditableTextService } from './editabletext.service';
@@ -28,6 +29,7 @@
  */
 @NgModule({
   exports: [
+    ConfirmComponent,
     FlashComponent,
     VeilComponent
   ],
@@ -35,6 +37,7 @@
     CommonModule
   ],
   declarations: [
+    ConfirmComponent,
     FlashComponent,
     VeilComponent
   ],
diff --git a/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts b/web/gui2/src/main/webapp/app/fw/util/fn.service.spec.ts
similarity index 98%
rename from web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts
rename to web/gui2/src/main/webapp/app/fw/util/fn.service.spec.ts
index e3d3c87..43e4f85 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/util/fn.service.spec.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/fn.service.spec.ts
@@ -15,9 +15,9 @@
  */
 import { TestBed, inject } from '@angular/core/testing';
 
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
+import { LogService } from '../../log.service';
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { FnService } from '../../fw/util/fn.service';
 import { ActivatedRoute, Params } from '@angular/router';
 import { of } from 'rxjs';
 import * as d3 from 'd3';
diff --git a/web/gui2/src/main/webapp/app/fw/widget/table.css b/web/gui2/src/main/webapp/app/fw/widget/table.css
index 3b761f6..9df99ef 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/table.css
+++ b/web/gui2/src/main/webapp/app/fw/widget/table.css
@@ -31,6 +31,7 @@
 
 div.summary-list div.table-body {
     overflow-y: scroll;
+    max-height:70vh;
 }
 
 div.summary-list div.table-body::-webkit-scrollbar {
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tableresize.directive.ts b/web/gui2/src/main/webapp/app/fw/widget/tableresize.directive.ts
index bd25919..fef5123 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/tableresize.directive.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/tableresize.directive.ts
@@ -13,25 +13,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Directive } from '@angular/core';
+import { Directive, ElementRef } from '@angular/core';
 import { FnService } from '../util/fn.service';
 import { LogService } from '../../log.service';
-import { MastService } from '../mast/mast.service';
 
 /**
  * ONOS GUI -- Widget -- Table Resize Directive
  */
 @Directive({
-  selector: '[onosTableResize]'
+    selector: '[onosTableResize]',
 })
 export class TableResizeDirective {
 
     constructor(
         private fs: FnService,
-        private log: LogService,
-        private ms: MastService
+        public log: LogService,
+        private el: ElementRef,
     ) {
+
+        this.windowSize();
         this.log.debug('TableResizeDirective constructed');
     }
 
+    windowSize() {
+        const wsz = this.fs.windowSize(0, 30);
+        this.el.nativeElement.style.width = wsz.width + 'px';
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
index 4a09032..b05b76b 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
@@ -43,7 +43,8 @@
     TableFilterPipe
   ],
   exports: [
-    TableFilterPipe
+    TableFilterPipe,
+    TableResizeDirective
   ],
   providers: [
   ]
diff --git a/web/gui2/src/main/webapp/app/onos.module.ts b/web/gui2/src/main/webapp/app/onos.module.ts
index 78cf112..b8c609d 100644
--- a/web/gui2/src/main/webapp/app/onos.module.ts
+++ b/web/gui2/src/main/webapp/app/onos.module.ts
@@ -15,11 +15,9 @@
  */
 import { BrowserModule } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { HttpClientModule } from '@angular/common/http';
 import { NgModule } from '@angular/core';
 
-import { AppsModule } from './view/apps/apps.module';
-import { DeviceModule } from './view/device/device.module';
-
 import { LayerModule } from './fw/layer/layer.module';
 import { MastModule } from './fw/mast/mast.module';
 import { NavModule } from './fw/nav/nav.module';
@@ -45,6 +43,7 @@
   imports: [
     BrowserModule,
     BrowserAnimationsModule,
+    HttpClientModule,
     LayerModule,
     MastModule,
     NavModule,
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.module.ts b/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
index 0f38dbc..738ee18 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
@@ -19,7 +19,7 @@
 import { AppsRoutingModule } from './apps-routing.module';
 import { AppsComponent } from './apps/apps.component';
 import { AppsDetailsComponent } from './appsdetails/appsdetails.component';
-import { TriggerFormDirective } from './triggerform.directive';
+import { LayerModule } from '../../fw/layer/layer.module';
 import { SvgModule } from '../../fw/svg/svg.module';
 import { WidgetModule } from '../../fw/widget/widget.module';
 
@@ -36,12 +36,12 @@
         AppsRoutingModule,
         SvgModule,
         WidgetModule,
-        FormsModule
+        FormsModule,
+        LayerModule
     ],
     declarations: [
         AppsComponent,
-        AppsDetailsComponent,
-        TriggerFormDirective
+        AppsDetailsComponent
     ]
 })
 export class AppsModule { }
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css
index 5813f30..3096bae 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css
@@ -17,13 +17,15 @@
 /*
  ONOS GUI -- Applications View (layout) -- CSS file
  */
+#ov-app .tabular-header {
+    text-align: left;
+}
 
 #ov-app h2 {
     display: inline-block;
 }
 
 #ov-app div.ctrl-btns {
-    width: 290px;
 }
 
 /* -- Drag-n-Drop oar file upload -- */
@@ -35,19 +37,3 @@
 .dropping {
 
 }
-
-/* -- Confirmation Dialog -- */
-#app-dialog {
-    top: 140px;
-    padding: 12px;
-}
-
-#app-dialog p {
-    font-size: 12pt;
-}
-
-#app-dialog p.strong {
-    font-weight: bold;
-    padding: 8px;
-    text-align: center;
-}
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 1c4a22f..943e26b 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
@@ -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.
@@ -13,41 +13,42 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<div id="ov-app" filedrop on-file-drop="appDropped()">
+<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>
         <h2>
             {{lionFn('title_apps')}}
             ({{ tableData.length }}
             {{ lionFn('total') }})
         </h2>
         <div class="ctrl-btns">
+            <form #inputFileForm="ngForm">
+                <input id="uploadFile" hidden
+                       type="file" size="50" accept=".oar,.jar"
+                       name="appFile" (change)="fileEvent($event)">
+            </form>
+
             <div class="refresh" (click)="toggleRefresh()">
                 <onos-icon classes="{{ autoRefresh?'active refresh':'refresh' }}"
                            iconId="refresh" iconSize="42" toolTip="{{ autoRefreshTip }}"></onos-icon>
             </div>
             <div class="separator"></div>
 
-            <!--<form id="inputFileForm">-->
-                <!--<input id="uploadFile"-->
-                       <!--type="file" size="50" accept=".oar,.jar"-->
-                       <!--file-model="appFile">-->
-            <!--</form>-->
-
-            <div class="active" trigger-form>
+            <div class="active" (click)="triggerForm()">
                 <onos-icon classes="{{ 'active upload' }}"
-                        iconId="upload" iconSize="42" toolTip="{{ uploadTip }}"></onos-icon>
+                           iconId="upload" iconSize="42" toolTip="{{ uploadTip }}"></onos-icon>
             </div>
-            <div (click)="appAction('activate')">
+
+            <div (click)="confirmAction(AppActionEnum.ACTIVATE)">
                 <onos-icon classes="{{ ctrlBtnState.installed?'active play':'play' }}"
                            iconId="play" iconSize="42" toolTip="{{ activateTip }}"></onos-icon>
             </div>
-            <div (click)="appAction('deactivate')">
+            <div (click)="confirmAction(AppActionEnum.DEACTIVATE)">
                 <onos-icon classes="{{ ctrlBtnState.active?'active stop':'stop' }}"
                         iconId="stop" iconSize="42" toolTip="{{ deactivateTip }}"></onos-icon>
             </div>
-            <div (click)="appAction('uninstall')">
-                 <!--[ngClass]="{active: ctrlBtnState.selection}">-->
-                <!--tooltip tt-msg="uninstallTip"-->
+            <div (click)="confirmAction(AppActionEnum.UNINSTALL)">
                 <onos-icon classes="{{ ctrlBtnState.selection?'active garbage':'garbage' }}"
                         iconId="garbage" iconSize="42" toolTip="{{ uninstallTip }}"></onos-icon>
             </div>
@@ -73,57 +74,61 @@
 
     </div>
 
-    <div class="summary-list" onos-table-resize>
-        <table onos-flash-changes id-prop="id" width="100%">
-            <tr class="table-header">
-                <th colId="state" [ngStyle]="{width: '32px'}" class="table-icon" (click)="onSort('state')">
-                    <onos-icon classes="active" [iconId]="sortIcon('state')"></onos-icon>
-                </th>
-                <th colId="icon" [ngStyle]="{width: '32px'}" class="table-icon"></th>
-                <th colId="title"  (click)="onSort('title')">{{lionFn('title')}}
-                    <onos-icon classes="active" [iconId]="sortIcon('title')"></onos-icon>
-                </th>
-                <th colId="id" (click)="onSort('id')">{{lionFn('app_id')}}
-                    <onos-icon classes="active" [iconId]="sortIcon('id')"></onos-icon>
-                </th>
-                <th colId="version" (click)="onSort('version')"> {{lionFn('version')}}
-                    <onos-icon classes="active" [iconId]="sortIcon('version')"></onos-icon>
-                </th>
-                <th colId="category" (click)="onSort('category')"> {{lionFn('category')}}
-                    <onos-icon classes="active" [iconId]="sortIcon('category')"></onos-icon>
-                </th>
-                <th colId="origin" (click)="onSort('origin')"> {{lionFn('origin')}}
-                    <onos-icon classes="active" [iconId]="sortIcon('origin')"></onos-icon>
-                </th>
-            </tr>
-
-            <tr *ngIf="tableData.length === 0" class="no-data">
-                <td colspan="5">
-                    {{annots.no_rows_msg}}
-                </td>
-            </tr>
-            <!-- See https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
-                Angular has dropped the filter and order by pipe that were present in
-                AngularJS - filter and sort the data at source instead -->
-            <tr class="table-body" *ngFor="let app of tableData | filter: tableDataFilter"
-                (click)="selectCallback($event, app)"
-                [ngClass]="{selected: app.id === selId, 'data-change': isChanged(app.id)}">
-                <td class="table-icon">
-                    <onos-icon iconId="{{app._iconid_state}}"></onos-icon>
-                </td>
-                <td class="table-icon">
-                    <!-- The path below gets the app icon from the old GUI path -->
-                    <img src="../../ui/rs/applications/{{app.icon}}/icon"
-                                            height="24px" width="24px" />
-                </td>
-                <td>{{ app.title }}</td>
-                <td>{{ app.id }}</td>
-                <td>{{ app.version }}</td>
-                <td>{{ app.category }}</td>
-                <td>{{ app.origin }}</td>
-            </tr>
-        </table>
-
+    <div id="summary-list" class="summary-list">
+        <div class="table-header">
+            <table onosTableResize>
+                <tr>
+                    <th colId="state" [ngStyle]="{width: '32px'}" class="table-icon" (click)="onSort('state')">
+                        <onos-icon classes="active" [iconId]="sortIcon('state')"></onos-icon>
+                    </th>
+                    <th colId="icon" [ngStyle]="{width: '32px'}" class="table-icon"></th>
+                    <th colId="title"  (click)="onSort('title')">{{lionFn('title')}}
+                        <onos-icon classes="active" [iconId]="sortIcon('title')"></onos-icon>
+                    </th>
+                    <th colId="id" (click)="onSort('id')">{{lionFn('app_id')}}
+                        <onos-icon classes="active" [iconId]="sortIcon('id')"></onos-icon>
+                    </th>
+                    <th colId="version" (click)="onSort('version')"> {{lionFn('version')}}
+                        <onos-icon classes="active" [iconId]="sortIcon('version')"></onos-icon>
+                    </th>
+                    <th colId="category" (click)="onSort('category')"> {{lionFn('category')}}
+                        <onos-icon classes="active" [iconId]="sortIcon('category')"></onos-icon>
+                    </th>
+                    <th colId="origin" (click)="onSort('origin')"> {{lionFn('origin')}}
+                        <onos-icon classes="active" [iconId]="sortIcon('origin')"></onos-icon>
+                    </th>
+                </tr>
+            </table>
+        </div>
+        <div class="table-body">
+            <table onosTableResize>
+                <tr *ngIf="tableData.length === 0" class="no-data">
+                    <td colspan="5">
+                        {{annots.no_rows_msg}}
+                    </td>
+                </tr>
+                <!-- See https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
+                    Angular has dropped the filter and order by pipe that were present in
+                    AngularJS - filter and sort the data at source instead -->
+                <tr *ngFor="let app of tableData | filter: tableDataFilter"
+                    (click)="selectCallback($event, app)"
+                    [ngClass]="{selected: app.id === selId, 'data-change': isChanged(app.id)}">
+                    <td class="table-icon">
+                        <onos-icon iconId="{{app._iconid_state}}"></onos-icon>
+                    </td>
+                    <td class="table-icon">
+                        <!-- The path below gets the app icon from the old GUI path -->
+                        <img src="../../ui/rs/applications/{{app.icon}}/icon"
+                                                height="24px" width="24px" />
+                    </td>
+                    <td>{{ app.title }}</td>
+                    <td>{{ app.id }}</td>
+                    <td>{{ app.version }}</td>
+                    <td>{{ app.category }}</td>
+                    <td>{{ app.origin }}</td>
+                </tr>
+            </table>
+        </div>
     </div>
     <!-- There are 2 ways this component can be included
      1) Insert it in to the ngFor above and have it created as the row is rendered
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts
index 1889a44..cddf9ae 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts
@@ -19,11 +19,14 @@
 import { FormsModule } from '@angular/forms';
 import { DebugElement } from '@angular/core';
 import { By } from '@angular/platform-browser';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
 
 import { LogService } from '../../../log.service';
 import { AppsComponent } from './apps.component';
 import { AppsDetailsComponent } from '../appsdetails/appsdetails.component';
+import { ConfirmComponent } from '../../../fw/layer/confirm/confirm.component';
 import { DialogService } from '../../../fw/layer/dialog.service';
+import { FlashComponent } from '../../../fw/layer/flash/flash.component';
 import { FnService } from '../../../fw/util/fn.service';
 import { IconComponent } from '../../../fw/svg/icon/icon.component';
 import { IconService } from '../../../fw/svg/icon.service';
@@ -47,6 +50,8 @@
 
 class MockFnService {}
 
+class MockHttpClient {}
+
 class MockIconService {
     loadIconDef() {}
 }
@@ -108,10 +113,18 @@
 
         TestBed.configureTestingModule({
             imports: [ BrowserAnimationsModule, FormsModule ],
-            declarations: [ AppsComponent, IconComponent, AppsDetailsComponent, TableFilterPipe ],
+            declarations: [
+                AppsComponent,
+                ConfirmComponent,
+                IconComponent,
+                AppsDetailsComponent,
+                TableFilterPipe,
+                FlashComponent
+            ],
             providers: [
                 { provide: DialogService, useClass: MockDialogService },
                 { provide: FnService, useValue: fs },
+                { provide: HttpClient, useClass: MockHttpClient },
                 { provide: IconService, useClass: MockIconService },
                 { provide: KeyService, useClass: MockKeyService },
                 { provide: LionService, useFactory: (() => {
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
index 79fa310..ffd7b37 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-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.
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+
 import { DialogService } from '../../../fw/layer/dialog.service';
 import { FnService } from '../../../fw/util/fn.service';
 import { IconService } from '../../../fw/svg/icon.service';
@@ -28,14 +30,13 @@
 
 const INSTALLED = 'INSTALLED';
 const ACTIVE = 'ACTIVE';
-const appMgmtReq = 'appManagementRequest';
-const topPdg = 60;
-const panelWidth = 540;
-const pName = 'application-details-panel';
-const detailsReq = 'appDetailsRequest';
-const detailsResp = 'appDetailsResponse';
-const fileUploadUrl = 'applications/upload';
-const activateOption = '?activate=true';
+const APPMGMTREQ = 'appManagementRequest';
+const DETAILSREQ = 'appDetailsRequest';
+const FILEUPLOADURL = 'upload';
+const FILEDOWNLOADURL = 'download';
+const ACTIVATEOPTION = '?activate=true';
+const DRAGDROPMSG1 = 'Drag and drop one file at a time';
+const DRAGDROPMSGEXT = 'Only files ending in .oar can be dropped';
 
 /** Prefix to access the REST service for applications */
 export const APPURLPREFIX = '../../ui/rs/applications/'; // TODO: This is a hack to work off GUIv1 URL
@@ -81,6 +82,13 @@
     _iconid_state: string;
 }
 
+export enum AppAction {
+    NONE = 0,
+    ACTIVATE = 1,
+    DEACTIVATE = 2,
+    UNINSTALL = 3,
+}
+
 /**
  * Model of the Control Button
  */
@@ -91,7 +99,7 @@
 }
 
 /**
- * ONOS GUI -- Apps View Component
+ * ONOS GUI -- Apps View Component extends TableBaseImpl
  */
 @Component({
   selector: 'onos-apps',
@@ -108,15 +116,18 @@
     warnDeactivate: string;
     warnOwnRisk: string;
     ctrlBtnState: CtrlBtnState;
-    detailsPanel: any;
     appFile: any;
-    activateImmediately = '';
 
     uploadTip: string;
     activateTip: string;
     deactivateTip: string;
     uninstallTip: string;
     downloadTip: string;
+    alertMsg: string;
+    AppActionEnum: any = AppAction;
+    appAction: AppAction = AppAction.NONE;
+    confirmMsg: string = '';
+    strongWarning: string = '';
 
     constructor(
         protected fs: FnService,
@@ -129,9 +140,11 @@
         private ufs: UrlFnService,
         protected wss: WebSocketService,
         @Inject('Window') private window: Window,
+        private httpClient: HttpClient
     ) {
         super(fs, null, log, wss, 'app');
         this.responseCallback = this.appResponseCb;
+        this.parentSelCb =  this.rowSelection;
         // pre-populate sort so active apps are at the top of the list
         this.sortParams = {
             firstCol: 'state',
@@ -180,78 +193,100 @@
         this.log.debug('App response received for ', data.apps.length, 'apps');
     }
 
-    refreshCtrls() {
-        let row;
-        let rowIdx;
-        if (this.ctrlBtnState.selection) {
-            rowIdx = this.fs.find(this.selId, this.tableData);
-            row = rowIdx >= 0 ? this.tableData[rowIdx] : null;
+    /**
+     * called when a row is selected - sets the state of control icons
+     */
+    rowSelection(event: any, selRow: any) {
+        this.ctrlBtnState.installed = this.selId && selRow && selRow.state === INSTALLED;
+        this.ctrlBtnState.active = this.selId && selRow && selRow.state === ACTIVE;
+        this.ctrlBtnState.selection = this.selId;
+        this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState);
+    }
 
-            this.ctrlBtnState.installed = row && row.state === INSTALLED;
-            this.ctrlBtnState.active = row && row.state === ACTIVE;
-        } else {
-            this.ctrlBtnState.installed = false;
-            this.ctrlBtnState.active = false;
+
+    /**
+     * Perform one of the app actions - activate, deactivate or uninstall
+     * Raises a dialog which calls back the dOk() below
+     */
+    confirmAction(action: AppAction): void {
+        this.appAction = action;
+        const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
+
+        this.confirmMsg = this.lionFn(appActionLc) + ' ' + this.selId;
+        if (strongWarning[this.selId]) {
+            this.strongWarning = this.warnDeactivate + '\n' + this.warnOwnRisk;
         }
+
+        this.log.debug('Initiating', this.appAction, 'of', this.selId);
     }
 
-    createConfirmationText(action, itemId) {
-//        let content = this.ds.createDiv();
-//        content.append('p').text(this.lionFn(action) + ' ' + itemId);
-//        if (strongWarning[itemId]) {
-//            content.append('p').html(
-//                this.fs.sanitize(this.warnDeactivate) +
-//                '<br>' +
-//                this.fs.sanitize(this.warnOwnRisk)
-//            ).classed('strong', true);
-//        }
-//        return content;
-    }
+    /**
+     * Callback when the Confirm dialog is shown and a choice is made
+     */
+    dOk(choice: boolean) {
+        const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
+        if (choice) {
+            this.log.debug('Confirmed', appActionLc, 'on', this.selId);
 
-    confirmAction(action): void {
-        const itemId = this.selId;
-        const spar = this.sortParams;
-
-        function dOk() {
-            this.log.debug('Initiating', action, 'of', itemId);
-            this.wss.sendEvent(appMgmtReq, {
-                action: action,
-                name: itemId,
-                sortCol: spar.firstCol,
-                sortDir: spar.firstDir,
+            this.wss.sendEvent(APPMGMTREQ, {
+                action: appActionLc,
+                name: this.selId,
+                sortCol: this.sortParams.firstCol,
+                sortDir: SortDir[this.sortParams.firstDir],
             });
-            if (action === 'uninstall') {
-                this.detailsPanel.hide();
+            if (this.appAction === AppAction.UNINSTALL) {
+                this.selId = '';
             } else {
-                this.wss.sendEvent(detailsReq, { id: itemId });
+                this.wss.sendEvent(DETAILSREQ, { id: this.selId });
             }
-        }
 
-        function dCancel() {
-            this.log.debug('Canceling', action, 'of', itemId);
+        } else {
+            this.log.debug('Cancelled', appActionLc, 'on', this.selId);
         }
-
-//        this.ds.openDialog(dialogId, dialogOpts)
-//            .setTitle(this.lionFn('dlg_confirm_action'))
-//            .addContent(this.createConfirmationText(action, itemId))
-//            .addOk(dOk)
-//            .addCancel(dCancel)
-//            .bindKeys();
-    }
-
-    appAction(action) {
-        if (this.ctrlBtnState.selection) {
-            this.confirmAction(action);
-        }
+        this.confirmMsg = '';
+        this.strongWarning = '';
     }
 
     downloadApp() {
         if (this.ctrlBtnState.selection) {
-            (<any>this.window).location = APPURLPREFIX + this.selId + ICONURLSUFFIX;
+            (<any>this.window).location = APPURLPREFIX + this.selId + '/' + FILEDOWNLOADURL;
         }
     }
 
     /**
+     * When the file is selected this fires
+     * It passes the file on to the server through a POST request
+     * If there is an error its logged and raised to the user through Flash Component
+     */
+    fileEvent(event: any, activateImmediately?: boolean) {
+        this.log.debug('File event for', event.target.files[0]);
+        const formData = new FormData();
+        formData.append('file', event.target.files[0]);
+        let url = APPURLPREFIX + FILEUPLOADURL;
+        if (activateImmediately) {
+            url += ACTIVATEOPTION;
+        }
+        this.httpClient
+            .post<any>(APPURLPREFIX + FILEUPLOADURL, formData)
+            .subscribe(
+                data => this.log.debug(data),
+                err => {
+                    this.log.warn(err.message);
+                    this.alertMsg = err.message; // This will activate flash msg
+                }
+            );
+
+    }
+
+    /**
+     * When the upload button is clicked pass this on to the file input (hidden)
+     */
+    triggerForm() {
+        document.getElementById('uploadFile')
+                .dispatchEvent(new MouseEvent('click'));
+    }
+
+    /**
      * Read the LION bundle for App and set up the lionFn
      */
     doLion() {
@@ -267,9 +302,39 @@
         this.downloadTip = this.lionFn('tt_ctl_download');
     }
 
-    appDropped() {
-        this.activateImmediately = activateOption;
-//        $scope.$emit('FileChanged'); // TODO: Implement this
-        this.appFile = null;
+    onDrop(event: DragEvent) {
+        event.preventDefault();
+        event.stopPropagation();
+
+        const dt = event.dataTransfer;
+        const droppedFiles = dt.files;
+
+        this.log.debug(droppedFiles.length, 'File(s) dropped');
+        if (droppedFiles.length !== 1) {
+            this.log.error(DRAGDROPMSG1, droppedFiles.length, 'were dropped');
+            this.alertMsg = DRAGDROPMSG1;
+            return;
+        } else if (droppedFiles[0].name.slice(droppedFiles[0].name.length - 4) !== '.oar') {
+            this.log.error(DRAGDROPMSGEXT, droppedFiles[0].name, 'rejected');
+            this.alertMsg = DRAGDROPMSGEXT;
+            return;
+        }
+
+        const fileEvent = {
+            target: {
+                files: droppedFiles
+            }
+        };
+        this.fileEvent(fileEvent, true);
+    }
+
+    onDragOver(evt) {
+        evt.preventDefault();
+        evt.stopPropagation();
+    }
+
+    onDragLeave(evt) {
+        evt.preventDefault();
+        evt.stopPropagation();
     }
 }
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css b/web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css
index 3de7ad8..13f1847 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css
@@ -23,16 +23,38 @@
     border: solid 3px #0095d6;
 }
 
-
-/* -- confirmation dialog -- */
-.light #app-dialog p.strong {
-    color: white;
-    background-color: #ce5b58;
+#ov-app .tabular-header {
+    text-align: left;
+}
+#ov-app div.summary-list .table-header td {
+    font-weight: bold;
+    font-variant: small-caps;
+    text-transform: uppercase;
+    font-size: 10pt;
+    padding-top: 8px;
+    padding-bottom: 8px;
+    letter-spacing: 0.02em;
+    cursor: pointer;
+    background-color: #e5e5e6;
+    color: #3c3a3a;
 }
 
-.light #app-dialog.floatpanel.dialog {
-    background-color: #ffffff;
+#ov-app div.summary-list .table-body {
+    overflow:scroll;
+    max-height:70vh;
 }
+#ov-app h2 {
+    display: inline-block;
+}
+
+#ov-app, div.ctrl-btns {
+}
+
+#ov-app th, td {
+    text-align: left;
+    padding:  8px;
+}
+
 
 /* ========== DARK Theme ========== */
 
@@ -40,13 +62,3 @@
 .dark .app-title {
     color: #dddddd;
 }
-
-/* -- confirmation dialog -- */
-.dark #app-dialog p.strong {
-    color: red;
-    background-color: #ecd98e;
-}
-.dark #app-dialog.floatpanel.dialog {
-    background-color: #282528;
-    color:#ddddee;
-}
diff --git a/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
index bfa8d08..ce9af3a 100644
--- a/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
+++ b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
@@ -103,6 +103,8 @@
 
 #application-details-panel .bottom {
     padding: 0px;
+    overflow: auto;
+    height: 200px;
 }
 
 #application-details-panel .bottom table {
diff --git a/web/gui2/src/main/webapp/app/view/apps/triggerform.directive.ts b/web/gui2/src/main/webapp/app/view/apps/triggerform.directive.ts
deleted file mode 100644
index 7134c3a..0000000
--- a/web/gui2/src/main/webapp/app/view/apps/triggerform.directive.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2015-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 { Directive } from '@angular/core';
-import { LogService } from '../../log.service';
-
-/**
- * ONOS GUI -- Apps -- Trigger Form Directive
- */
-@Directive({
-  selector: '[onosTriggerForm]'
-})
-export class TriggerFormDirective {
-
-    constructor(
-        private log: LogService,
-    ) {
-        this.log.debug('TriggerFormDirective constructed');
-    }
-
-}
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts
deleted file mode 100644
index f51fb0e..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2017-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 { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { DialogService } from '../../../../app/fw/layer/dialog.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { KeyService } from '../../../../app/fw/util/key.service';
-
-class MockFnService {}
-
-class MockKeyService {}
-
-/**
- * ONOS GUI -- Layer -- Dialog Service - Unit Tests
- */
-describe('DialogService', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [DialogService,
-                { provide: LogService, useValue: log },
-                { provide: FnService, useClass: MockFnService },
-                { provide: KeyService, useClass: MockKeyService },
-            ]
-        });
-    });
-
-    it('should be created', inject([DialogService], (service: DialogService) => {
-        expect(service).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/flash/flash.component.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/flash/flash.component.spec.ts
deleted file mode 100644
index 53a7ecc..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/flash/flash.component.spec.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.
- */
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { ConsoleLoggerService } from '../../../../../app/consolelogger.service';
-import { LogService } from '../../../../../app/log.service';
-import { FlashComponent } from '../../../../../app/fw/layer/flash/flash.component';
-
-/**
- * ONOS GUI -- Layer -- Flash Component - Unit Tests
- */
-describe('FlashComponent', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-        TestBed.configureTestingModule({
-            declarations: [ FlashComponent ],
-            providers: [
-                { provide: LogService, useValue: log },
-            ]
-        });
-    });
-
-
-    it('should create', () => {
-        const fixture = TestBed.createComponent(FlashComponent);
-        const component = fixture.componentInstance;
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/widget/flashchanges.directive.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/widget/flashchanges.directive.spec.ts
deleted file mode 100644
index fd87910..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/widget/flashchanges.directive.spec.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2015-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 { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { FlashChangesDirective } from '../../../../app/fw/widget/flashchanges.directive';
-import { FnService } from '../../../../app/fw/util/fn.service';
-
-class MockFnService {}
-
-/**
- * ONOS GUI -- Widget -- Table Flash Changes Directive - Unit Tests
- */
-describe('FlashChangesDirective', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [ FlashChangesDirective,
-                { provide: FnService, useClass: MockFnService },
-                { provide: LogService, useValue: log },
-            ]
-        });
-    });
-
-    afterEach(() => {
-        log = null;
-    });
-
-    it('should create an instance', inject([FlashChangesDirective], (directive: FlashChangesDirective) => {
-        expect(directive).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts
deleted file mode 100644
index cfc49e7..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2015-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 { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { ToolbarService } from '../../../../app/fw/widget/toolbar.service';
-import { ButtonService } from '../../../../app/fw/widget/button.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { IconService } from '../../../../app/fw/svg/icon.service';
-
-class MockButtonService {}
-
-class MockIconService {}
-
-class MockFnService {}
-
-/**
- * ONOS GUI -- Widget -- Toolbar Service - Unit Tests
- */
-describe('ToolbarService', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [ToolbarService,
-                { provide: LogService, useValue: log },
-                { provide: ButtonService, useClass: MockButtonService },
-                { provide: IconService, useClass: MockIconService },
-                { provide: FnService, useClass: MockFnService },
-            ]
-        });
-    });
-
-    it('should be created', inject([ToolbarService], (service: ToolbarService) => {
-        expect(service).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/view/apps/triggerform.directive.spec.ts b/web/gui2/src/main/webapp/tests/app/view/apps/triggerform.directive.spec.ts
deleted file mode 100644
index be1be65..0000000
--- a/web/gui2/src/main/webapp/tests/app/view/apps/triggerform.directive.spec.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2015-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 { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { TriggerFormDirective } from '../../../../app/view/apps/triggerform.directive';
-
-describe('TriggerFormDirective', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [ TriggerFormDirective,
-                { provide: LogService, useValue: log },
-            ]
-        });
-    });
-
-    afterEach(() => {
-        log = null;
-    });
-
-    it('should create an instance', inject([TriggerFormDirective], (directive: TriggerFormDirective) => {
-        expect(directive).toBeTruthy();
-    }));
-});