Adding ability to drop OAR files to install apps.

Change-Id: I989a92db4c94ef86d029d6b36f769f28e4fee52d
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 7601478..93d9146 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
@@ -22,15 +22,18 @@
 import org.onosproject.core.ApplicationId;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Objects;
 
 /**
  * Application upload resource.
@@ -38,11 +41,20 @@
 @Path("applications")
 public class ApplicationResource extends BaseResource {
 
+    static String lastInstalledAppName = null;
+
+
     @Path("upload")
     @POST
     @Consumes(MediaType.MULTIPART_FORM_DATA)
-    public Response upload(@FormDataParam("file") InputStream stream) throws IOException {
-        get(ApplicationAdminService.class).install(stream);
+    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);
+        lastInstalledAppName = app.id().name();
+        if (Objects.equals(activate, "true")) {
+            service.activate(app.id());
+        }
         return Response.ok().build();
     }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
index 51e538b..e6a5d00 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
@@ -30,6 +30,7 @@
 
 import java.util.Collection;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
 import static org.onosproject.app.ApplicationState.ACTIVE;
 
 /**
@@ -113,15 +114,15 @@
             String iconId = state == ACTIVE ? ICON_ID_ACTIVE : ICON_ID_INACTIVE;
 
             row.cell(STATE, state)
-                .cell(STATE_IID, iconId)
-                .cell(ID, id.name())
-                .cell(ICON, id.name())
-                .cell(VERSION, app.version())
-                .cell(CATEGORY, app.category())
-                .cell(ORIGIN, app.origin())
-                .cell(TITLE, app.title())
-                .cell(DESC, app.description())
-                .cell(URL, app.url());
+                    .cell(STATE_IID, iconId)
+                    .cell(ID, id.name())
+                    .cell(ICON, id.name())
+                    .cell(VERSION, app.version())
+                    .cell(CATEGORY, app.category())
+                    .cell(ORIGIN, app.origin())
+                    .cell(TITLE, app.title())
+                    .cell(DESC, app.description())
+                    .cell(URL, app.url());
         }
     }
 
@@ -160,6 +161,13 @@
         public void process(long sid, ObjectNode payload) {
             String id = string(payload, ID);
             ApplicationService as = get(ApplicationService.class);
+
+            // If the ID was not specified in the payload, use the name of the
+            // most recently uploaded app.
+            if (isNullOrEmpty(id)) {
+                id = ApplicationResource.lastInstalledAppName;
+            }
+
             ApplicationId appId = as.getId(id);
             ApplicationState state = as.getState(appId);
             Application app = as.getApplication(appId);
@@ -198,5 +206,6 @@
             rootNode.set(DETAILS, data);
             sendMessage(APP_DETAILS_RESP, 0, rootNode);
         }
+
     }
 }
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index 7c01044..c5a4ee2 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -172,4 +172,4 @@
 }
 .dark #application-details-panel .bottom tr:nth-child(even) {
     background-color: #555;
-}
\ No newline at end of file
+}
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 535af2f..c97b441 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -1,5 +1,5 @@
 <!-- app partial HTML -->
-<div id="ov-app">
+<div id="ov-app" filedrop on-file-drop="appDropped()">
     <div class="tabular-header">
         <h2>Applications ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
@@ -14,6 +14,7 @@
                        type="file" size="50" accept=".oar"
                        file-model="appFile">
             </form>
+
             <div icon icon-size="36" icon-id="plus"
                  class="active" trigger-form
                  tooltip tt-msg="uploadTip">
diff --git a/web/gui/src/main/webapp/app/view/app/app.js b/web/gui/src/main/webapp/app/view/app/app.js
index 1f1078b..7cb0cdb 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -25,13 +25,14 @@
     var $log, $scope, wss, fs, ks, ps, is;
 
     // internal state
-    var  detailsPanel,
-         pStartY,
-         pHeight,
-         top,
-         middle,
-         bottom,
-         wSize = false;
+    var detailsPanel,
+        pStartY,
+        pHeight,
+        top,
+        middle,
+        bottom,
+        wSize = false,
+        activateImmediately;
 
     // constants
     var INSTALLED = 'INSTALLED',
@@ -43,6 +44,7 @@
         detailsReq = 'appDetailsRequest',
         detailsResp = 'appDetailsResponse',
         fileUploadUrl = 'applications/upload',
+        activateOption = '?activate=true',
         iconUrlPrefix = 'rs/applications/',
         iconUrlSuffix = '/icon',
         dialogId = 'app-dialog',
@@ -200,16 +202,18 @@
 
     function respDetailsCb(data) {
         $scope.panelData = data.details;
+        $scope.selId = data.details.id;
+        $scope.ctrlBtnState.selection = data.details.id;
         $scope.$apply();
     }
 
     angular.module('ovApp', [])
     .controller('OvAppCtrl',
-        ['$log', '$scope', '$http',
+        ['$log', '$scope', '$http', '$timeout',
          'WebSocketService', 'FnService', 'KeyService', 'PanelService',
          'IconService', 'UrlFnService', 'DialogService', 'TableBuilderService',
 
-    function (_$log_, _$scope_, $http, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
+    function (_$log_, _$scope_, $http, $timeout, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
         $log = _$log_;
         $scope = _$scope_;
         wss = _wss_;
@@ -302,6 +306,11 @@
                     sortCol: spar.sortCol,
                     sortDir: spar.sortDir
                 });
+                if (action == 'uninstall') {
+                    detailsPanel.hide();
+                } else {
+                    wss.sendEvent(detailsReq, {id: itemId});
+                }
             }
 
             function dCancel() {
@@ -323,22 +332,32 @@
         };
 
         $scope.$on('FileChanged', function () {
-            var formData = new FormData();
+            var formData = new FormData(),
+                url;
             if ($scope.appFile) {
                 formData.append('file', $scope.appFile);
-                $http.post(ufs.rsUrl(fileUploadUrl), formData, {
+                url = fileUploadUrl + (activateImmediately || '');
+                $http.post(ufs.rsUrl(url), formData, {
                     transformRequest: angular.identity,
                     headers: {
                         'Content-Type': undefined
                     }
                 })
                     .finally(function () {
+                        activateImmediately = null;
                         $scope.sortCallback($scope.sortParams);
                         document.getElementById('inputFileForm').reset();
+                        $timeout(function () { wss.sendEvent(detailsReq); }, 250);
                     });
             }
         });
 
+        $scope.appDropped = function() {
+            activateImmediately = activateOption;
+            $scope.$emit('FileChanged');
+            $scope.appFile = null;
+        };
+
         $scope.$on('$destroy', function () {
             ks.unbindKeys();
             wss.unbindHandlers(handlers);
@@ -380,6 +399,42 @@
         };
     }])
 
+    .directive("filedrop", function ($parse, $document) {
+        return {
+            restrict: "A",
+            link: function (scope, element, attrs) {
+                var onAppDrop = $parse(attrs.onFileDrop);
+
+                // When an item is dragged over the document
+                var onDragOver = function (e) {
+                    e.preventDefault();
+                };
+
+                // When the user leaves the window, cancels the drag or drops the item
+                var onDragEnd = function (e) {
+                    e.preventDefault();
+                };
+
+                // When a file is dropped
+                var loadFile = function (file) {
+                    scope.appFile = file;
+                    scope.$apply(onAppDrop(scope));
+                };
+
+                // Dragging begins on the document
+                $document.bind("dragover", onDragOver);
+
+                // Dragging ends on the overlay, which takes the whole window
+                element.bind("dragleave", onDragEnd)
+                    .bind("drop", function (e) {
+                        $log.info('Drag leave', e);
+                        loadFile(e.dataTransfer.files[0]);
+                        onDragEnd(e);
+                    });
+            }
+        };
+    })
+
     .directive('applicationDetailsPanel',
         ['$rootScope', '$window', '$timeout', 'KeyService',
         function ($rootScope, $window, $timeout, ks) {