浏览代码

[security] Refactored bulk actions for Hive

Enrico Berti 11 年之前
父节点
当前提交
988379b

+ 4 - 4
apps/security/src/security/templates/hdfs.mako

@@ -234,7 +234,7 @@ ${ layout.menubar(section='hdfs') }
       <div class="span4 center">
         <div class="big-btn" data-bind="css: {'selected': $root.assist.bulkAction() == 'sync'}, click: function(){$root.assist.bulkAction('sync')}">
           <i class="fa fa-copy"></i><br/><br/>
-          ${ _('Replace current ACLs to checkbox selection') }
+          ${ _('Replace checkbox selection with current ACLs') }
         </div>
       </div>
       <div class="span4 center">
@@ -337,17 +337,17 @@ ${ tree.import_templates(itemClick='$root.assist.setPath', iconClick='$root.assi
     });
 
     $(document).on("added.bulk.acls", function() {
-      $(document).trigger("info", "${ _('The current ACLs have been successfully added to the selected paths.') }");
+      $(document).trigger("info", "${ _('The current ACLs have been successfully added to the checked paths.') }");
       $("#bulkActionsModal").modal("hide");
     });
 
     $(document).on("deleted.bulk.acls", function() {
-      $(document).trigger("info", "${ _('All the ACLs have been successfully removed from the selected paths.') }");
+      $(document).trigger("info", "${ _('All the ACLs have been successfully removed from the checked paths.') }");
       $("#bulkActionsModal").modal("hide");
     });
 
     $(document).on("syncd.bulk.acls", function() {
-      $(document).trigger("info", "${ _('All the ACLs have been successfully updated for the selected paths.') }");
+      $(document).trigger("info", "${ _('All the ACLs for the checked items have been replaced with the current selection.') }");
       $("#bulkActionsModal").modal("hide");
     });
 

+ 83 - 13
apps/security/src/security/templates/hive.mako

@@ -132,12 +132,6 @@ ${ layout.menubar(section='hive') }
                 <div class="clearfix"></div>
                 <div class="tree-toolbar">
                   <div class="pull-right">
-                    <a href="javascript: void(0)" data-bind="click: $root.assist.collapseOthers">
-                      <i class="fa fa-compress"></i> ${_('Close others')}
-                    </a>
-                    <a href="javascript: void(0)" data-bind="click: $root.assist.refreshTree">
-                      <i class="fa fa-refresh"></i>  ${_('Refresh')}
-                    </a>                  
                     <div class="dropdown inline-block" style="margin-right: 6px">
                       <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                         <i class="fa fa-eye-slash" data-bind="visible: $root.assist.isDiffMode"></i>
@@ -158,17 +152,20 @@ ${ layout.menubar(section='hive') }
                     <i class="fa fa-group" title="${ _('List of groups in popover for this user?') }"></i>
                   </div>
                   <div>
-                    <a href="javascript: void(0)" data-bind="click: $root.bulk_add_privileges" title="${ _('Add current current privileges to checkbox selection') }">
-                      <i class="fa fa-plus"></i>
+                    <i class="fa fa-spinner fa-spin" data-bind="visible: $root.assist.isLoadingTree()"></i>
+                    <a href="javascript: void(0)" data-bind="click: $root.assist.collapseOthers" rel="tooltip" data-placement="right" title="${_('Close other nodes')}">
+                      <i class="fa fa-compress"></i>
                     </a>
-                    <a href="javascript: void(0)" data-bind="click: $root.assist.bulkSyncAcls" title="${ _('Replace checkbox selection with current privileges') }">
-                      <i class="fa fa-copy"></i>
+                    &nbsp;
+                    <a href="javascript: void(0)" data-bind="click: $root.assist.refreshTree" rel="tooltip" data-placement="right" title="${_('Refresh the tree')}">
+                      <i class="fa fa-refresh"></i>
                     </a>
-                    <a href="javascript: void(0)" data-bind="click: $root.bulk_delete_privileges" title="${ _('Remove privileges of checkbox selection') }">
-                      <i class="fa fa-times"></i>
+                    &nbsp;
+                    <a href="javascript: void(0)" data-bind="visible: $root.assist.checkedItems().length > 0, click: function(){ $('#bulkActionsModal').modal('show'); }" rel="tooltip" data-placement="right" title="${ _('Add, replace or remove ACLs for the checked paths') }">
+                      <i class="fa fa-cogs"></i>
                     </a>
                   </div>
-                  <i class="fa fa-spinner fa-spin" data-bind="visible: $root.assist.isLoadingTree()"></i>
+
                 </div>
               </div>
 
@@ -333,6 +330,60 @@ ${ layout.menubar(section='hive') }
   </div>
 </div>
 
+<div id="bulkActionsModal" class="modal hide fade in" role="dialog">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h3>${ _('What would you like to do with the checked paths?') }</h3>
+  </div>
+  <div class="modal-body" style="overflow-x: hidden">
+
+    <div class="row-fluid">
+      <div class="span6">
+        <h4>${ _('Checked items') }</h4>
+        <ul class="unstyled modal-panel" data-bind="foreach: $root.assist.checkedItems">
+          <li><span class="force-word-break" data-bind="text: path()"></span></li>
+        </ul>
+      </div>
+      <div class="span6">
+
+        <h4>${ _('Selected item for privilege actions') }</h4>
+
+        <div data-bind="visible: $root.assist.privileges().length == 0"><em class="muted">${ _('No privileges found for the selected item.')}</em></div>
+        <div data-bind="template: { name: 'role', foreach: $root.assist.roles }" class="modal-panel"></div>
+
+      </div>
+    </div>
+
+    <h4>${ _('Choose your action') }</h4>
+    <div class="row-fluid">
+      <div class="span4 center">
+        <div class="big-btn" data-bind="css: {'selected': $root.bulkAction() == 'add'}, click: function(){$root.bulkAction('add')}">
+          <i class="fa fa-plus"></i><br/><br/>
+          ${ _('Add current privileges to checkbox selection') }
+        </div>
+      </div>
+      <div class="span4 center">
+        <div class="big-btn" data-bind="css: {'selected': $root.bulkAction() == 'sync'}, click: function(){$root.bulkAction('sync')}">
+          <i class="fa fa-copy"></i><br/><br/>
+          ${ _('Replace checkbox selection with current privileges') }
+        </div>
+      </div>
+      <div class="span4 center">
+        <div class="big-btn" data-bind="css: {'selected': $root.bulkAction() == 'delete'}, click: function(){$root.bulkAction('delete')}">
+          <i class="fa fa-times"></i><br/><br/>
+          ${ _('Remove privileges of checkbox selection') }
+        </div>
+      </div>
+    </div>
+
+  </div>
+  <div class="modal-footer">
+    <label class="checkbox pull-left"><input type="checkbox" data-bind="checked: $root.assist.recursive"> ${ _('Apply recursively to all subfolders and files') }</label>
+    <button class="btn" data-dismiss="modal" aria-hidden="true">${ _('Cancel') }</button>
+    <button class="btn" data-bind="css: {'btn-primary': $root.bulkAction() != 'delete', 'btn-danger': $root.bulkAction() == 'delete'}, click: $root.bulkPerfomAction">${ _('Confirm') }</button>
+  </div>
+</div>
+
 <%def name="treeIcons()">
   'fa-database': isDb(),
   'fa-table': isTable(),
@@ -462,6 +513,25 @@ ${ tree.import_templates(itemClick='$root.assist.setPath', iconClick='$root.assi
         viewModel.list_sentry_roles_by_group(); 
       });
 
+      $(document).on("added.bulk.privileges", function() {
+        $(document).trigger("info", "${ _('The current privileges have been successfully added to the checked items.') }");
+        $("#bulkActionsModal").modal("hide");
+      });
+
+      $(document).on("deleted.bulk.privileges", function() {
+        $(document).trigger("info", "${ _('All the privileges have been successfully removed from the checked items.') }");
+        $("#bulkActionsModal").modal("hide");
+      });
+
+      $(document).on("syncd.bulk.privileges", function() {
+        $(document).trigger("info", "${ _('All the privileges for the checked items have been replaced with the current selection.') }");
+        $("#bulkActionsModal").modal("hide");
+      });
+
+      $("#bulkActionsModal").modal({
+        show: false
+      });
+
     });
 </script>
 

+ 137 - 137
apps/security/static/js/hdfs.ko.js

@@ -53,7 +53,7 @@ function printAcl(acl) {
 var Assist = function (vm, assist) {
   var self = this;
 
-  self.compareNames = function (a,b) {
+  self.compareNames = function (a, b) {
     if (a.name.toLowerCase() < b.name.toLowerCase())
       return -1;
     if (a.name.toLowerCase() > b.name.toLowerCase())
@@ -123,7 +123,7 @@ var Assist = function (vm, assist) {
   self.originalAcls = ko.observableArray();
   self.regularAcls = ko.computed(function () {
     return $.grep(self.acls(), function (acl) {
-      return ! acl.isDefault();
+      return !acl.isDefault();
     });
   });
   self.defaultAcls = ko.computed(function () {
@@ -141,14 +141,14 @@ var Assist = function (vm, assist) {
       return ['new', 'deleted', 'modified'].indexOf(acl.status()) != -1;
     });
   });
-  
+
   self.owner = ko.observable('');
   self.group = ko.observable('');
 
   self.checkedItems = ko.observableArray([]);
 
-  self.afterRender = function() {
-    if (! self.fromLoadMore && ! self.fromRebuildTree) {
+  self.afterRender = function () {
+    if (!self.fromLoadMore && !self.fromRebuildTree) {
       $(document).trigger("rendered.tree");
     }
     self.fromLoadMore = false;
@@ -287,13 +287,13 @@ var Assist = function (vm, assist) {
     self.growingTree(jQuery.extend(true, {}, self.initialGrowingTree));
     Object.keys(self.treeAdditionalData).forEach(function (path) {
       if (typeof force == "boolean" && force) {
-        self.fetchPath(path, function() {
+        self.fetchPath(path, function () {
           self.updatePathProperty(self.growingTree(), path, "isExpanded", true);
           self.loadData(self.growingTree());
         });
       } else {
         if (self.treeAdditionalData[path].loaded) {
-          self.fetchPath(path, function() {
+          self.fetchPath(path, function () {
             self.updatePathProperty(self.growingTree(), path, "isExpanded", true);
             self.loadData(self.growingTree());
           });
@@ -307,7 +307,7 @@ var Assist = function (vm, assist) {
     paths.push(leaf.path);
     if (leaf.nodes.length > 0) {
       leaf.nodes.forEach(function (node) {
-        if (node.isDir){
+        if (node.isDir) {
           self.rebuildTree(node, paths);
         }
       });
@@ -316,14 +316,14 @@ var Assist = function (vm, assist) {
   }
 
   self.setPath = function (obj, toggle) {
-    if (self.getTreeAdditionalDataForPath(obj.path()).loaded || (! obj.isExpanded() && ! self.getTreeAdditionalDataForPath(obj.path()).loaded)) {
-      if (typeof toggle == "boolean" && toggle){
+    if (self.getTreeAdditionalDataForPath(obj.path()).loaded || (!obj.isExpanded() && !self.getTreeAdditionalDataForPath(obj.path()).loaded)) {
+      if (typeof toggle == "boolean" && toggle) {
         obj.isExpanded(!obj.isExpanded());
       }
       self.updatePathProperty(self.growingTree(), obj.path(), "isExpanded", obj.isExpanded());
     }
     else {
-      if (typeof toggle == "boolean" && toggle){
+      if (typeof toggle == "boolean" && toggle) {
         obj.isExpanded(!obj.isExpanded());
       } else {
         obj.isExpanded(false);
@@ -339,7 +339,7 @@ var Assist = function (vm, assist) {
 
   self.checkPath = function (obj) {
     obj.isChecked(!obj.isChecked());
-    if (obj.isChecked()){
+    if (obj.isChecked()) {
       self.checkedItems.push(obj);
     }
     else {
@@ -376,45 +376,45 @@ var Assist = function (vm, assist) {
   self.fetchPath = function (optionalPath, loadCallback) {
     var _path = typeof optionalPath != "undefined" ? optionalPath : self.path();
     $.getJSON('/security/api/hdfs/list' + _path, {
-        'pagesize': 15,
-        'pagenum': self.pagenum(),
-        'format': 'json',
-        'doas': vm.doAs(),
-        'isDiffMode': self.isDiffMode(),
-      },
-      function (data) {
-        if (data.error != null){
-          if (data.error == "FILE_NOT_FOUND"){
-            self.path("/");
-          }
-        }
-        else {
-          self.loadParents(data.breadcrumbs);
-          if (data['files'] && data['files'][0] && data['files'][0]['type'] == 'dir') { // Hack for now
-            $.each(data.files, function (index, item) {
-              self.convertItemToObject(item);
-            });
-          }
-          else {
-            self.convertItemToObject(data);
-          }
-          self.getTreeAdditionalDataForPath(_path).loaded = true;
-          if (data.page != null && data.page.number != null) {
-            self.updatePathProperty(self.growingTree(), _path, "page", data.page);
-          }
-          if (typeof loadCallback != "undefined") {
-            loadCallback(data);
+          'pagesize': 15,
+          'pagenum': self.pagenum(),
+          'format': 'json',
+          'doas': vm.doAs(),
+          'isDiffMode': self.isDiffMode(),
+        },
+        function (data) {
+          if (data.error != null) {
+            if (data.error == "FILE_NOT_FOUND") {
+              self.path("/");
+            }
           }
           else {
-            self.loadData(self.growingTree());
-          }
-          if (typeof optionalPath == "undefined") {
-            self.getAcls();
+            self.loadParents(data.breadcrumbs);
+            if (data['files'] && data['files'][0] && data['files'][0]['type'] == 'dir') { // Hack for now
+              $.each(data.files, function (index, item) {
+                self.convertItemToObject(item);
+              });
+            }
+            else {
+              self.convertItemToObject(data);
+            }
+            self.getTreeAdditionalDataForPath(_path).loaded = true;
+            if (data.page != null && data.page.number != null) {
+              self.updatePathProperty(self.growingTree(), _path, "page", data.page);
+            }
+            if (typeof loadCallback != "undefined") {
+              loadCallback(data);
+            }
+            else {
+              self.loadData(self.growingTree());
+            }
+            if (typeof optionalPath == "undefined") {
+              self.getAcls();
+            }
           }
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        $(document).trigger("error", xhr.responseText);
-      });
+        }).fail(function (xhr, textStatus, errorThrown) {
+          $(document).trigger("error", xhr.responseText);
+        });
   };
 
   self.loadMore = function (what) {
@@ -426,12 +426,12 @@ var Assist = function (vm, assist) {
   self.getAcls = function () {
     $(".jHueNotify").hide();
     var _isLoading = window.setTimeout(function () {
-        self.isLoadingAcls(true);
-      }, 1000);
-        logGA('get_acls');
-        $.getJSON('/security/api/hdfs/get_acls', {
-        'path': self.path()
-      }, function (data) {
+      self.isLoadingAcls(true);
+    }, 1000);
+    logGA('get_acls');
+    $.getJSON('/security/api/hdfs/get_acls', {
+      'path': self.path()
+    }, function (data) {
       window.clearTimeout(_isLoading);
       if (data != null) {
         self.acls.removeAll();
@@ -458,27 +458,27 @@ var Assist = function (vm, assist) {
     logGA('updateAcls');
 
     $.post("/security/api/hdfs/update_acls", {
-        'path': self.path(),
-        'acls': ko.mapping.toJSON(self.acls()),
-        'originalAcls': ko.mapping.toJSON(self.originalAcls())
-      }, function (data) {
-        var toDelete = []
-        $.each(self.acls(), function (index, item) {
-          if (item.status() == 'deleted') {
-            toDelete.push(item);
-          } else {
-            item.status('');
-          }
-        });
-        $.each(toDelete, function (index, item) {
-          self.acls.remove(item);
-        });
-        self.refreshTree();
-        $(document).trigger("updated.acls");
-      }
+          'path': self.path(),
+          'acls': ko.mapping.toJSON(self.acls()),
+          'originalAcls': ko.mapping.toJSON(self.originalAcls())
+        }, function (data) {
+          var toDelete = []
+          $.each(self.acls(), function (index, item) {
+            if (item.status() == 'deleted') {
+              toDelete.push(item);
+            } else {
+              item.status('');
+            }
+          });
+          $.each(toDelete, function (index, item) {
+            self.acls.remove(item);
+          });
+          self.refreshTree();
+          $(document).trigger("updated.acls");
+        }
     ).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", JSON.parse(xhr.responseText).message);
-    });
+          $(document).trigger("error", JSON.parse(xhr.responseText).message);
+        });
   }
 
   self.bulkAction = ko.observable("");
@@ -497,67 +497,67 @@ var Assist = function (vm, assist) {
     }
     self.bulkAction("");
   }
-  
-  self.bulkDeleteAcls = function() {
-	$(".jHueNotify").hide();
-	logGA('bulkDeleteAcls');
-
-	var checkedPaths = self.checkedItems();
-	
-	$.post("/security/api/hdfs/bulk_delete_acls", {
-        'path': self.path(),
-        'checkedPaths': ko.mapping.toJSON(checkedPaths),
-        'recursive': ko.mapping.toJSON(self.recursive()),
-      }, function (data) {
-        if (checkedPaths.indexOf(self.path()) != -1) {
-          self.acls.removeAll();
+
+  self.bulkDeleteAcls = function () {
+    $(".jHueNotify").hide();
+    logGA('bulkDeleteAcls');
+
+    var checkedPaths = self.checkedItems();
+
+    $.post("/security/api/hdfs/bulk_delete_acls", {
+          'path': self.path(),
+          'checkedPaths': ko.mapping.toJSON(checkedPaths),
+          'recursive': ko.mapping.toJSON(self.recursive())
+        }, function (data) {
+          if (checkedPaths.indexOf(self.path()) != -1) {
+            self.acls.removeAll();
+          }
+          self.refreshTree();
+          $(document).trigger("deleted.bulk.acls");
         }
-        self.refreshTree();
-        $(document).trigger("deleted.bulk.acls");
-      }
     ).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", JSON.parse(xhr.responseText).message);
-    });
+          $(document).trigger("error", JSON.parse(xhr.responseText).message);
+        });
   }
 
-  self.bulkAddAcls = function() {
-	$(".jHueNotify").hide();
-	logGA('bulkAddAcls');
-
-	var checkedPaths = self.checkedItems();
-	
-	$.post("/security/api/hdfs/bulk_add_acls", {
-        'path': self.path(),
-        'acls': ko.mapping.toJSON(self.acls()),
-        'checkedPaths': ko.mapping.toJSON(checkedPaths),
-        'recursive': ko.mapping.toJSON(self.recursive()),
-      }, function (data) {
-        self.refreshTree();
-        $(document).trigger("added.bulk.acls");
-      }
+  self.bulkAddAcls = function () {
+    $(".jHueNotify").hide();
+    logGA('bulkAddAcls');
+
+    var checkedPaths = self.checkedItems();
+
+    $.post("/security/api/hdfs/bulk_add_acls", {
+          'path': self.path(),
+          'acls': ko.mapping.toJSON(self.acls()),
+          'checkedPaths': ko.mapping.toJSON(checkedPaths),
+          'recursive': ko.mapping.toJSON(self.recursive())
+        }, function (data) {
+          self.refreshTree();
+          $(document).trigger("added.bulk.acls");
+        }
     ).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", JSON.parse(xhr.responseText).message);
-    });
+          $(document).trigger("error", JSON.parse(xhr.responseText).message);
+        });
   }
 
-  self.bulkSyncAcls = function() {
-	$(".jHueNotify").hide();
-	logGA('bulkSyncAcls');
-
-	var checkedPaths = self.checkedItems();
-	
-	$.post("/security/api/hdfs/bulk_sync_acls", {
-        'path': self.path(),
-        'acls': ko.mapping.toJSON(self.acls()),
-        'checkedPaths': ko.mapping.toJSON(checkedPaths),
-        'recursive': ko.mapping.toJSON(self.recursive()),
-      }, function (data) {
-        self.refreshTree();
-        $(document).trigger("syncd.bulk.acls");
-      }
+  self.bulkSyncAcls = function () {
+    $(".jHueNotify").hide();
+    logGA('bulkSyncAcls');
+
+    var checkedPaths = self.checkedItems();
+
+    $.post("/security/api/hdfs/bulk_sync_acls", {
+          'path': self.path(),
+          'acls': ko.mapping.toJSON(self.acls()),
+          'checkedPaths': ko.mapping.toJSON(checkedPaths),
+          'recursive': ko.mapping.toJSON(self.recursive())
+        }, function (data) {
+          self.refreshTree();
+          $(document).trigger("syncd.bulk.acls");
+        }
     ).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", JSON.parse(xhr.responseText).message);
-    });
+          $(document).trigger("error", JSON.parse(xhr.responseText).message);
+        });
   }
 }
 
@@ -574,16 +574,16 @@ var HdfsViewModel = function (initial) {
   self.availableHadoopUsers = ko.observableArray();
   self.availableHadoopGroups = ko.observableArray();
 
-  self.selectableHadoopUsers = ko.computed(function() {
-    var _users = ko.utils.arrayMap(self.availableHadoopUsers(), function(user) {
+  self.selectableHadoopUsers = ko.computed(function () {
+    var _users = ko.utils.arrayMap(self.availableHadoopUsers(), function (user) {
       return user.username;
     });
     return _users.sort();
   }, self);
 
-  self.selectableHadoopGroups = ko.computed(function() {
-    var _users = ko.utils.arrayMap(self.availableHadoopGroups(), function(group) {
-        return group.name;
+  self.selectableHadoopGroups = ko.computed(function () {
+    var _users = ko.utils.arrayMap(self.availableHadoopGroups(), function (group) {
+      return group.name;
     });
     return _users.sort();
   }, self);
@@ -592,14 +592,14 @@ var HdfsViewModel = function (initial) {
   self.init = function (path) {
     self.fetchUsers();
     self.assist.path(path);
-    $(document).one("loaded.parents", function(){
+    $(document).one("loaded.parents", function () {
       self.assist.isLoadingTree(true);
       var _paths = self.assist.rebuildTree(self.assist.growingTree().nodes[0], []);
-      _paths.forEach(function(ipath, cnt){
+      _paths.forEach(function (ipath, cnt) {
         self.assist.updatePathProperty(self.assist.growingTree(), ipath, "isExpanded", true);
-        self.assist.fetchPath(ipath, function(){
-          if (cnt == _paths.length -1){
-            self.assist.fetchPath(path, function(){
+        self.assist.fetchPath(ipath, function () {
+          if (cnt == _paths.length - 1) {
+            self.assist.fetchPath(path, function () {
               self.assist.updatePathProperty(self.assist.growingTree(), path, "isExpanded", true);
               self.assist.fromRebuildTree = true;
               self.assist.loadData(self.assist.growingTree());

+ 106 - 95
apps/security/static/js/hive.ko.js

@@ -69,41 +69,41 @@ var Privilege = function (vm, privilege) {
   // UI
   self.showAdvanced = ko.observable(false);
   self.path = ko.computed({
-	read: function () {
-	  if (self.tableName().length > 0) {
-	    return self.dbName() + "." + self.tableName();
-	  } else {
+    read: function () {
+      if (self.tableName().length > 0) {
+        return self.dbName() + "." + self.tableName();
+      } else {
         return self.dbName();
-	  }
-	},
-	write: function (value) {
-	  var lastSpacePos = value.lastIndexOf(".");
-	    if (lastSpacePos > 0) {
-	      this.dbName(value.substring(0, lastSpacePos));
-	      this.tableName(value.substring(lastSpacePos + 1));
-	    } else {
-	      this.dbName(value);
-	      this.tableName('');
-	    }
-	  },
-	owner: self
+      }
+    },
+    write: function (value) {
+      var lastSpacePos = value.lastIndexOf(".");
+      if (lastSpacePos > 0) {
+        this.dbName(value.substring(0, lastSpacePos));
+        this.tableName(value.substring(lastSpacePos + 1));
+      } else {
+        this.dbName(value);
+        this.tableName('');
+      }
+    },
+    owner: self
   });
-  self.privilegeScope = ko.computed(function() {
+  self.privilegeScope = ko.computed(function () {
     if (self.tableName().length > 0) {
-      return 'TABLE';	
+      return 'TABLE';
     } else if (self.dbName().length > 0) {
-  	  return 'DATABASE';
+      return 'DATABASE';
     } else {
       return 'SERVER';
     }
-  });  
-  
+  });
+
   self.remove = function (privilege) {
-	if (privilege.status() == 'new') {
-	  privilege.status('alreadydeleted');
-	} else {
+    if (privilege.status() == 'new') {
+      privilege.status('alreadydeleted');
+    } else {
       privilege.status('deleted');
-	}
+    }
   }
 }
 
@@ -134,9 +134,9 @@ var Role = function (vm, role) {
   });
 
   self.groupsChanged = ko.computed(function () {
-	return ! ($(self.groups()).not(self.originalGroups()).length == 0 && $(self.originalGroups()).not(self.groups()).length == 0);
+    return !($(self.groups()).not(self.originalGroups()).length == 0 && $(self.originalGroups()).not(self.groups()).length == 0);
   });
-  
+
   self.reset = function () {
     self.name('');
     self.groups.removeAll();
@@ -149,37 +149,37 @@ var Role = function (vm, role) {
   }
 
   self.addPrivilege = function () {
-	if (vm.getSectionHash() == 'edit') {
+    if (vm.getSectionHash() == 'edit') {
       self.privileges.push(new Privilege(vm, {'serverName': vm.assist.server(), 'status': 'new', 'editing': true, 'dbName': vm.assist.db(), 'tableName': vm.assist.table()}));
-	} else {
+    } else {
       self.privileges.push(new Privilege(vm, {'serverName': vm.assist.server(), 'status': 'new', 'editing': true}));
-	}
+    }
   }
-  
+
   self.resetGroups = function () {
-	self.groups.removeAll();
-	$.each(self.originalGroups(), function (index, group) {
-	  self.groups.push(group);
-	});
+    self.groups.removeAll();
+    $.each(self.originalGroups(), function (index, group) {
+      self.groups.push(group);
+    });
   }
-  
-  self.saveGroups = function() {
+
+  self.saveGroups = function () {
     $(".jHueNotify").hide();
     $.post("/security/api/hive/update_role_groups", {
-    	role: ko.mapping.toJSON(self)
+      role: ko.mapping.toJSON(self)
     }, function (data) {
       if (data.status == 0) {
         self.showEditGroups(false);
-    	self.originalGroups.removeAll();
-    	$.each(self.groups(), function (index, group) {
-    	  self.originalGroups.push(group);
-    	});        
+        self.originalGroups.removeAll();
+        $.each(self.groups(), function (index, group) {
+          self.originalGroups.push(group);
+        });
       } else {
         $(document).trigger("error", data.message);
       }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
-	});
+    });
   }
 
   self.create = function () {
@@ -224,7 +224,7 @@ var Role = function (vm, role) {
       role: ko.mapping.toJSON(role)
     }, function (data) {
       if (data.status == 0) {
-    	vm.list_sentry_privileges_by_authorizable();
+        vm.list_sentry_privileges_by_authorizable();
         vm.list_sentry_privileges_by_role(role); // Refresh all role privileges
       } else {
         $(document).trigger("error", data.message);
@@ -284,6 +284,8 @@ var Assist = function (vm, initial) {
 
   self.growingTree = ko.observable(jQuery.extend(true, {}, self.initialGrowingTree));
 
+  self.checkedItems = ko.observableArray([]);
+
   self.addDatabases = function (path, databases, skipLoading) {
     var _tree = self.growingTree();
     databases.forEach(function (db) {
@@ -293,7 +295,7 @@ var Assist = function (vm, initial) {
           _mainFound = true;
         }
       });
-      if (! _mainFound) {
+      if (!_mainFound) {
         var _item = {
           path: db,
           name: db,
@@ -307,7 +309,7 @@ var Assist = function (vm, initial) {
         _tree.nodes.push(_item);
       }
     });
-    if (typeof skipLoading == "undefined" || !skipLoading){
+    if (typeof skipLoading == "undefined" || !skipLoading) {
       self.loadData(self.growingTree());
     }
   }
@@ -342,7 +344,7 @@ var Assist = function (vm, initial) {
         _branch.nodes.push(_item);
       }
     });
-    if (typeof skipLoading == "undefined" || !skipLoading){
+    if (typeof skipLoading == "undefined" || !skipLoading) {
       self.loadData(self.growingTree());
     }
   }
@@ -383,7 +385,7 @@ var Assist = function (vm, initial) {
         _branch.nodes.push(_item);
       }
     });
-    if (typeof skipLoading == "undefined" || !skipLoading){
+    if (typeof skipLoading == "undefined" || !skipLoading) {
       self.loadData(self.growingTree());
     }
   }
@@ -420,7 +422,7 @@ var Assist = function (vm, initial) {
   self.refreshTree = function (force) {
     self.growingTree(jQuery.extend(true, {}, self.initialGrowingTree));
     // load root first
-    self.fetchHivePath("", function(){
+    self.fetchHivePath("", function () {
       Object.keys(self.treeAdditionalData).forEach(function (path) {
         if (path.indexOf(".") == -1 && path != "") {
           if (typeof force == "boolean" && force) {
@@ -428,10 +430,10 @@ var Assist = function (vm, initial) {
           }
           else {
             if (self.treeAdditionalData[path].loaded) {
-              self.fetchHivePath(path, function(){
+              self.fetchHivePath(path, function () {
                 Object.keys(self.treeAdditionalData).forEach(function (ipath) {
-                  if (ipath.split(".").length == 2 && ipath.split(".")[0] == path){
-                    self.fetchHivePath(ipath, function() {
+                  if (ipath.split(".").length == 2 && ipath.split(".")[0] == path) {
+                    self.fetchHivePath(ipath, function () {
                       self.updateTreeProperty(self.growingTree(), "isExpanded", true);
                       self.loadData(self.growingTree());
                     });
@@ -454,7 +456,7 @@ var Assist = function (vm, initial) {
       self.updatePathProperty(self.growingTree(), obj.path(), "isExpanded", obj.isExpanded());
     }
     else {
-      if (typeof toggle == "boolean" && toggle){
+      if (typeof toggle == "boolean" && toggle) {
         obj.isExpanded(!obj.isExpanded());
       } else {
         obj.isExpanded(false);
@@ -472,6 +474,12 @@ var Assist = function (vm, initial) {
 
   self.checkPath = function (obj) {
     obj.isChecked(!obj.isChecked());
+    if (obj.isChecked()) {
+      self.checkedItems.push(obj);
+    }
+    else {
+      self.checkedItems.remove(obj);
+    }
     self.updatePathProperty(self.growingTree(), obj.path(), "isChecked", obj.isChecked());
   }
 
@@ -507,32 +515,14 @@ var Assist = function (vm, initial) {
     return leaf;
   }
 
-  self.getCheckedItems = function (leaf, checked) {
-    if (leaf == null){
-      leaf = self.growingTree();
-    }
-    if (checked == null){
-      checked = []
-    }
-    if (leaf.isChecked){
-      checked.push(leaf);
-    }
-    if (leaf.nodes.length > 0) {
-      leaf.nodes.forEach(function (node) {
-        self.getCheckedItems(node, checked);
-      });
-    }
-    return checked;
-  }
-
   self.loadParents = function (callback) {
-    self.fetchHivePath("", function(){
+    self.fetchHivePath("", function () {
       self.updatePathProperty(self.growingTree(), "", "isExpanded", true);
       var _crumbs = self.path().split(".");
-      self.fetchHivePath(_crumbs[0], function() {
+      self.fetchHivePath(_crumbs[0], function () {
         self.updatePathProperty(self.growingTree(), _crumbs[0], "isExpanded", true);
-        if (_crumbs.length > 1){
-          self.fetchHivePath(_crumbs[0] + "." + _crumbs[1], function(){
+        if (_crumbs.length > 1) {
+          self.fetchHivePath(_crumbs[0] + "." + _crumbs[1], function () {
             self.updatePathProperty(self.growingTree(), _crumbs[0] + "." + _crumbs[1], "isExpanded", true);
             self.loadData(self.growingTree());
             if (typeof callback != "undefined") {
@@ -614,7 +604,7 @@ var HiveViewModel = function (initial) {
   self.roleFilter = ko.observable("");
   self.filteredRoles = ko.computed(function () {
     var _filter = self.roleFilter().toLowerCase();
-    if (! _filter) {
+    if (!_filter) {
       return self.roles();
     } else {
       return ko.utils.arrayFilter(self.roles(), function (role) {
@@ -663,7 +653,7 @@ var HiveViewModel = function (initial) {
     _groups.push("");
     return _groups.sort();
   }, self);
-  
+
   self.selectAllRoles = function () {
     self.allRolesSelected(!self.allRolesSelected());
     ko.utils.arrayForEach(self.roles(), function (role) {
@@ -693,7 +683,7 @@ var HiveViewModel = function (initial) {
 
   self.expandSelectedRoles = function () {
     ko.utils.arrayForEach(self.selectedRoles(), function (role) {
-      if (! role.showPrivileges()) { 
+      if (!role.showPrivileges()) {
         self.list_sentry_privileges_by_role(role);
       }
     });
@@ -730,7 +720,7 @@ var HiveViewModel = function (initial) {
       success: function (data) {
         if (typeof data.status !== "undefined" && data.status == -1) {
           $(document).trigger("error", data.message);
-        } 
+        }
         else {
           self.roles.removeAll();
           $.each(data.roles, function (index, item) {
@@ -794,7 +784,7 @@ var HiveViewModel = function (initial) {
       'table': self.assist.table()
     }
   }
-  
+
   self.list_sentry_privileges_by_authorizable = function () {
     if (self.assist.path() != "") {
       $.ajax({
@@ -810,17 +800,17 @@ var HiveViewModel = function (initial) {
           self.assist.privileges.removeAll();
           $.each(data.privileges, function (index, item) {
             var _role = null;
-            self.assist.roles().forEach(function(role){
-              if (role.name() == item.roleName){
+            self.assist.roles().forEach(function (role) {
+              if (role.name() == item.roleName) {
                 _role = role;
               }
             });
-            if (_role == null){
+            if (_role == null) {
               var _idx = self.assist.roles.push(new Role(self, { name: item.roleName }));
               _role = self.assist.roles()[_idx - 1];
             }
             _role.privileges.push(_create_ko_privilege(item));
-        	self.assist.privileges.push(_create_ko_privilege(item));
+            self.assist.privileges.push(_create_ko_privilege(item));
           });
         }
       }).fail(function (xhr, textStatus, errorThrown) {
@@ -829,41 +819,62 @@ var HiveViewModel = function (initial) {
     }
   };
 
+  self.bulkAction = ko.observable("");
+
+  self.bulkPerfomAction = function () {
+    switch (self.bulkAction()) {
+      case "add":
+        self.bulk_add_privileges();
+        break;
+      case "sync":
+        self.assist.bulkSyncAcls();
+        break;
+      case "delete":
+        self.bulk_delete_privileges();
+        break;
+    }
+    self.bulkAction("");
+  }
+
   self.bulk_delete_privileges = function (role) {
     $(".jHueNotify").hide();
-    var checkedPaths = self.assist.getCheckedItems();
+    var checkedPaths = self.assist.checkedItems();
     $.post("/security/api/hive/bulk_delete_privileges", {
       'authorizableHierarchy': ko.mapping.toJSON(_create_authorizable_from_ko()),
       'checkedPaths': ko.mapping.toJSON(checkedPaths),
+      'recursive': false
     }, function (data) {
       if (data.status == 0) {
         self.list_sentry_privileges_by_authorizable(); // Refresh
+        $(document).trigger("deleted.bulk.privileges");
       } else {
         $(document).trigger("error", data.message);
       }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  }  
+  }
 
   self.bulk_add_privileges = function (role) {
     $(".jHueNotify").hide();
-    var checkedPaths = self.assist.getCheckedItems();
+    var checkedPaths = self.assist.checkedItems();
     $.post("/security/api/hive/bulk_add_privileges", {
       'privileges': ko.mapping.toJSON(self.assist.privileges),
       'authorizableHierarchy': ko.mapping.toJSON(_create_authorizable_from_ko()),
       'checkedPaths': ko.mapping.toJSON(checkedPaths),
+      'recursive': false
     }, function (data) {
       if (data.status == 0) {
         self.list_sentry_privileges_by_authorizable(); // Refresh
+        $(document).trigger("added.bulk.privileges");
       } else {
         $(document).trigger("error", data.message);
       }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
-  } 
-  
+  }
+
   self.fetchUsers = function () {
     $.getJSON('/desktop/api/users/autocomplete', {
       'include_myself': true,
@@ -877,7 +888,7 @@ var HiveViewModel = function (initial) {
 
   self.updatePathHash = function (path) {
     var _hash = window.location.hash;
-    if (_hash.indexOf("@") == -1){
+    if (_hash.indexOf("@") == -1) {
       window.location.hash = path;
     }
     else {
@@ -887,10 +898,10 @@ var HiveViewModel = function (initial) {
 
   self.updateSectionHash = function (section) {
     var _hash = window.location.hash;
-    if (_hash == ""){
+    if (_hash == "") {
       window.location.hash = "@" + section;
     }
-    if (_hash.indexOf("@") == -1){
+    if (_hash.indexOf("@") == -1) {
       window.location.hash = _hash + "@" + section;
     }
     else {
@@ -901,7 +912,7 @@ var HiveViewModel = function (initial) {
   self.getPathHash = function () {
     if (window.location.hash != "") {
       var _hash = window.location.hash.substr(1);
-      if (_hash.indexOf("@") > -1){
+      if (_hash.indexOf("@") > -1) {
         return _hash.split("@")[0];
       }
       else {
@@ -914,7 +925,7 @@ var HiveViewModel = function (initial) {
   self.getSectionHash = function () {
     if (window.location.hash != "") {
       var _hash = window.location.hash.substr(1);
-      if (_hash.indexOf("@") > -1){
+      if (_hash.indexOf("@") > -1) {
         return _hash.split("@")[1];
       }
     }