瀏覽代碼

[security] Initial integration of the tree in Hive

Enrico Berti 11 年之前
父節點
當前提交
900b044a6b

+ 3 - 59
apps/security/src/security/templates/hdfs.mako

@@ -119,7 +119,6 @@ ${ layout.menubar(section='hdfs') }
                   <i class="fa fa-spinner fa-spin" data-bind="visible: $root.assist.isLoadingTree()"></i>
                 </div>
               </div>
-              <div class="path-container-ghost hide"></div>
 
               ${ tree.render(id='hdfsTree', data='$root.assist.treeData', afterRender='$root.assist.afterRender') }
             </div>
@@ -204,74 +203,19 @@ ${ layout.menubar(section='hdfs') }
 </%def>
 
 
-${ tree.import_templates(itemClick='$root.assist.setPath', iconClick='$root.assist.togglePath', itemSelected='$root.assist.path() == path()', iconModifier=treeIcons, styleModifier='aclBit', styleModifierPullRight=aclBitPullRight, anchorProperty='path', showMore='$root.assist.loadMore') }
+${ tree.import_templates(itemClick='$root.assist.setPath', iconClick='$root.assist.togglePath', itemSelected='$root.assist.path() == path()', iconModifier=treeIcons, styleModifier='aclBit', styleModifierPullRight=aclBitPullRight, anchorProperty='path', showMore='$root.assist.loadMore', strikedProperty='striked') }
 
 
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
 
+<script src="/security/static/js/common.ko.js" type="text/javascript" charset="utf-8"></script>
 <script src="/security/static/js/hdfs.ko.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/js/jquery.hdfsautocomplete.js" type="text/javascript" charset="utf-8"></script>
 
-<link rel="stylesheet" href="/static/ext/select2/select2.css">
-<script src="/static/ext/select2/select2.min.js" type="text/javascript" charset="utf-8"></script>
-
-
 <script type="text/javascript">
 
-  ko.bindingHandlers.select2 = {
-    init: function (element, valueAccessor, allBindingsAccessor, vm) {
-      var options = ko.toJS(valueAccessor()) || {};
-
-      if (typeof valueAccessor().update != "undefined") {
-        if (options.type == "user" && viewModel.selectableHadoopUsers().indexOf(options.update) == -1) {
-          viewModel.availableHadoopUsers.push({
-            username: options.update
-          });
-        }
-        if (options.type == "group" && viewModel.selectableHadoopGroups().indexOf(options.update) == -1) {
-          viewModel.availableHadoopGroups.push({
-            name: options.update
-          });
-        }
-      }
-      $(element)
-          .select2(options)
-          .on("change", function (e) {
-            if (typeof e.val != "undefined" && typeof valueAccessor().update != "undefined") {
-              valueAccessor().update(e.val);
-            }
-          })
-          .on("select2-open", function () {
-            $(".select2-input").off("keyup").data("type", options.type).on("keyup", function (e) {
-              if (e.keyCode === 13) {
-                var _newVal = $(this).val();
-                var _type = $(this).data("type");
-                if (_type == "user") {
-                  viewModel.availableHadoopUsers.push({
-                    username: _newVal
-                  });
-                }
-                if (_type == "group") {
-                  viewModel.availableHadoopGroups.push({
-                    name: _newVal
-                  });
-                }
-                $(element).select2("val", _newVal, true);
-                $(element).select2("close");
-              }
-            });
-          })
-    },
-    update: function (element, valueAccessor, allBindingsAccessor, vm) {
-      if (typeof valueAccessor().update != "undefined") {
-        $(element).select2("val", valueAccessor().update());
-      }
-    }
-  };
-
-  var INITIAL = ${ initial | n,unicode };
-  var viewModel = new HdfsViewModel(INITIAL);
+  var viewModel = new HdfsViewModel(${ initial | n,unicode });
   ko.applyBindings(viewModel);
 
   $(document).ready(function () {

+ 95 - 74
apps/security/src/security/templates/hive.mako

@@ -21,26 +21,27 @@ from django.utils.translation import ugettext as _
 
 <%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="layout" file="layout.mako" />
+<%namespace name="tree" file="common_tree.mako" />
 
 ${ commonheader(_('Hadoop Security'), "security", user) | n,unicode }
 ${ layout.menubar(section='hive') }
 
 
 <script type="text/html" id="privilege">
-<tr data-bind="visible: status() != 'deleted', click: function() { if (! edition()) { edition(true); } }">
+<tr data-bind="visible: status() != 'deleted', click: function() { if (! editing()) { editing(true); } }">
 
-  <!-- ko if: edition() -->
+  <!-- ko if: editing() -->
     <td><select data-bind="options: availablePrivileges, value: privilegeScope"></select></td>
-    <td><input type="text" data-bind="value: $data.serverName" placeholder="serverName"></input></td>
-    <td colspan="2"><input type="text" data-bind="value: $data.dbName" placeholder="dbName"></input></td>
-    <td colspan="2"><input type="text" data-bind="value: $data.tableName" placeholder="tableName"></input></td>
-    <td colspan="2"><input type="text" data-bind="value: $data.URI" placeholder="URI"></input></td>
-    <td><input type="text" data-bind="value: $data.action" placeholder="action"></input></td>
+    <td><input type="text" data-bind="value: $data.serverName" placeholder="serverName"></td>
+    <td colspan="2"><input type="text" data-bind="value: $data.dbName" placeholder="dbName"></td>
+    <td colspan="2"><input type="text" data-bind="value: $data.tableName" placeholder="tableName"></td>
+    <td colspan="2"><input type="text" data-bind="value: $data.URI" placeholder="URI"></td>
+    <td><input type="text" data-bind="value: $data.action" placeholder="action"></td>
     <td><a href="javascript:void(0)"><i class="fa fa-minus" data-bind="click: remove"></i></a></td>
   </div>
   <!-- /ko -->
   
-  <!-- ko ifnot: edition() -->
+  <!-- ko ifnot: editing() -->
     <td><span data-bind="text: properties.name"></span></td>
     <td><span data-bind="text: properties.timestamp"></span></td>
     <td><a data-bind="attr: { href: '/metastore/' + properties.database() }" target="_blank"><span data-bind="text: properties.database"></span></a></td>
@@ -69,79 +70,95 @@ ${ layout.menubar(section='hive') }
           <li class="nav-header">${ _('Privileges') }</li>
           <li class="active"><a href="#edit"><i class="fa fa-pencil"></i> ${ _('Edit') }</a></li>
           <li><a href="#roles"><i class="fa fa-cubes"></i> ${ _('Roles') }</a></li>
-          <li class="nav-header"><i class="fa fa-group"></i> ${ _('Users') }
-            </br>
-            <input type="checkbox" checked="checked"> ${_('Me')}
-            </br>          
-            <select data-bind="options: availableHadoopUsers, value: doAs" size="10"></select>
-          </li>
-          <li class="nav-header"><i class="fa fa-group"></i> ${ _('Groups') }
-            </br>
-            <input type="checkbox" checked> All
-            </br>
-            <select data-bind="options: $root.availableHadoopGroups" size="10" multiple="true"></select>
-          </li>
           <li class="nav-header"><i class="fa fa-group"></i> ${ _('Server') }
-            <input type="text" data-bind="value: $root.assist.server">
+            <input type="text" data-bind="value: $root.assist.server" class="input-small" />
           </li>
         </ul>
       </div>
     </div>
 
     <div class="span10">
+
       <div id="edit" class="mainSection card card-small">
-        <h1 class="card-heading simple">${ _('Edit') }</h1>
+        <h1 class="card-heading simple">
+          ${ _('Edit privileges') }
+        </h1>
+
         <div class="card-body">
-          <input type="text" class="input-xxlarge" data-bind="value: $root.assist.path, valueUpdate: 'afterkeydown'"/>
-          <a class="btn btn-inverse" style="margin-left:10px", data-bind="attr: { href: '/metastore/' + $root.assist.path() }" target="_blank" title="${ _('Open in Metastore Browser') }">
-            <i class="fa fa-external-link"></i>
-          </a>
-        </div>
-        <div>
-          <i class="fa fa-eye"></i>
-          <i class="fa fa-eye-slash"></i>
-        </div>
-        <div>
-          <div class="span6">
-            <div data-bind="foreach: $root.assist.files">
-              <div data-bind="text: $data, click: $root.list_sentry_privileges_by_authorizable"></div>
+          <div class="row-fluid">
+            <div class="span8">
+              <div class="path-container">
+                <div class="input-append span12">
+                  <input id="path" class="path" type="text" data-bind="value: $root.assist.path, valueUpdate: 'afterkeydown'" autocomplete="off" />
+                  <a data-bind="attr: { href: '/metastore/' + $root.assist.path() }" target="_blank" title="${ _('Open in Metastore Browser') }" class="btn btn-inverse">
+                    <i class="fa fa-external-link"></i>
+                  </a>
+                </div>
+                <div class="clearfix"></div>
+                <div class="tree-toolbar">
+                  <div class="pull-right">
+
+                    <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><i class="fa fa-eye" data-bind="visible: ! $root.assist.isDiffMode()"></i> <span data-bind="visible: $root.assist.isDiffMode">${ _('Show non accessible paths for') }</span><span data-bind="visible: ! $root.assist.isDiffMode()">${ _('Impersonate the user') }</span></a>
+                      <ul class="dropdown-menu">
+                        <li data-bind="visible: ! $root.assist.isDiffMode(), click: function() { $root.assist.isDiffMode(true); }"><a tabindex="-1" href="#">${ _('Show non accessible paths for') } <strong data-bind="text: $root.doAs"></strong></a></li>
+                        <li data-bind="visible: $root.assist.isDiffMode(), click: function() { $root.assist.isDiffMode(false); }"><a tabindex="-1" href="#">${ _('Impersonate the user') } <strong data-bind="text: $root.doAs"></strong></a></li>
+                      </ul>
+                    </div>
+                    <select class="user-list" data-bind="options: $root.selectableHadoopUsers, select2: { placeholder: '${ _("Select a user") }', update: $root.doAs, type: 'user'}" style="width: 120px"></select>
+                    <i class="fa fa-group" title="List of groups in popover for this user?"></i>
+                  </div>
+                  <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>
+                  <i class="fa fa-spinner fa-spin" data-bind="visible: $root.assist.isLoadingTree()"></i>
+                </div>
+              </div>
+
+              ${ tree.render(id='hdfsTree', data='$root.assist.treeData', afterRender='$root.assist.afterRender') }
+            </div>
+            <div class="span4">
+              <span data-bind="text: ko.mapping.toJSON($root.assist.privilege)"></span>
+              <!-- ko if: $root.assist.privilege() -->
+                sentry_privileges: <span data-bind="text: $root.assist.privilege.sentry_privileges"></span>
+                message: <span data-bind="text: $root.assist.privilege.message"></span>
+              <!-- /ko -->
+
+
             </div>
-          </div>
-          <div class="span6">
-            <span data-bind="text: ko.mapping.toJSON($root.assist.privilege)"></span>
-            <!-- ko if: $root.assist.privilege() -->              
-              sentry_privileges: <span data-bind="text: $root.assist.privilege.sentry_privileges"></span>
-              message: <span data-bind="text: $root.assist.privilege.message"></span>
-            <!-- /ko -->
           </div>
         </div>
       </div>
 
+
       <div id="roles" class="mainSection hide card card-small">
-        <div class="card-heading simple">
-        <h3>${ _('Roles') }</h3>
-		  <%actionbar:render>
+        <h1 class="card-heading simple">
+          ${ _('Roles') }
+        </h1>
+
+        <div class="card-body">
+          <%actionbar:render>
 		    <%def name="search()">
 		      <input id="filterInput" type="text" class="input-xlarge search-query" placeholder="${_('Search for name, groups, etc...')}">
 		    </%def>
-		
+
 		    <%def name="actions()">
               <div class="btn-toolbar" style="display: inline; vertical-align: middle">
                 <button class="btn toolbarBtn"><i class="fa fa-expand"></i> ${ _('Expand') }</button>
               </div>
 		      <div class="btn-toolbar" style="display: inline; vertical-align: middle">
 		        <button class="btn toolbarBtn"><i class="fa fa-times"></i> ${ _('Delete') }</button>
-		      </div>		      
+		      </div>
 		    </%def>
-		
+
 		    <%def name="creation()">
 		      <a href="javascript: void(0)" data-bind="click: function(){ $root.showCreateRole(true); }" class="btn"><i class="fa fa-plus-circle"></i> ${ _('Add') }</a>
 		    </%def>
 		  </%actionbar:render>
-        </div>
 
-
-        <div class="card-body">
           <div data-bind="with: $root.role, visible: showCreateRole">
             <div class="span1">
               Name
@@ -164,13 +181,9 @@ ${ layout.menubar(section='hive') }
               <i class="fa fa-save"></i>
             </button>
           </div>
-          <div>
-          </div>
-        </div>
 
-        </br></br>
 
-        <div>
+
         <table>
           <theader>
             <th style="width:1%"><div class="hueCheckbox selectAll fa"></div></th>
@@ -198,16 +211,13 @@ ${ layout.menubar(section='hive') }
               <td>
                 <a href=""><span data-bind="text: grantorPrincipal"></span></a>
               </td>
-            </tr> 
+            </tr>
             <tr>
-                       
-
-              
                 <td colspan="2"></td>
                 <td colspan="3">
                   <table data-bind="template: { name: 'privilege', foreach: $data.privileges}, visible: $data.showPrivileges">
                   </table>
-                </td>              
+                </td>
             </tr>
             <tr>
               <td colspan="2"></td>
@@ -225,38 +235,46 @@ ${ layout.menubar(section='hive') }
                 </button>
               </td>
             </tr>
-          </div>
         </tbody>
+        </table>
         </div>
-
       </div>
 
-    </div>
-  </div>
-
-  </div>
-
+    </div> <!-- /span10 -->
 </div>
 
+<%def name="treeIcons()">
+  'fa-database': isDb(),
+  'fa-table': isTable(),
+  'fa-columns': isColumn()
+</%def>
+
+${ tree.import_templates(itemClick='$root.assist.setPath', iconClick='$root.assist.togglePath', itemSelected='$root.assist.path() == path()',iconModifier=treeIcons) }
 
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/routie-0.3.0.min.js" type="text/javascript" charset="utf-8"></script>
 
+<script src="/security/static/js/common.ko.js" type="text/javascript" charset="utf-8"></script>
 <script src="/security/static/js/hive.ko.js" type="text/javascript" charset="utf-8"></script>
 
 
 <script type="text/javascript" charset="utf-8">
-  var viewModel;
+  var viewModel = new HiveViewModel(${ initial | n,unicode });
+  ko.applyBindings(viewModel);
 
   $(document).ready(function () {
-    viewModel = new HiveViewModel(${ initial | n,unicode });
-    ko.applyBindings(viewModel);
-
     viewModel.init();
-  });
 
-  function showMainSection(mainSection) {
+    function resizeComponents () {
+      $("#path").width($(".tree-toolbar").width() - 64);
+      $("#hdfsTree").height($(window).height() - 260);
+      $(".acl-panel-content").height($(window).height() - 260);
+    }
+
+    resizeComponents();
+
+    function showMainSection(mainSection) {
     if ($("#" + mainSection).is(":hidden")) {
       $(".mainSection").hide();
       $("#" + mainSection).show();
@@ -285,6 +303,9 @@ ${ layout.menubar(section='hive') }
       showMainSection("view");
     }
   });
+  });
+
+
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 4 - 0
apps/security/src/security/templates/layout.mako

@@ -48,6 +48,10 @@ def is_selected(section, matcher):
 <%def name="menubar(section='')">
   <link href="/security/static/css/security.css" rel="stylesheet">
 
+  <link rel="stylesheet" href="/static/ext/select2/select2.css">
+  <script src="/static/ext/select2/select2.min.js" type="text/javascript" charset="utf-8"></script>
+
+
   <div class="navbar navbar-inverse navbar-fixed-top">
       <div class="navbar-inner">
         <div class="container-fluid">

+ 1 - 1
apps/security/src/security/views.py

@@ -30,7 +30,7 @@ def hive(request):
 
   return render("hive.mako", request, {
       'assist': assist,
-      'initial': json.dumps({'hadoop_groups': _get_hadoop_groups()}),
+      'initial': json.dumps({'user': request.user.username, 'hadoop_groups': _get_hadoop_groups()}),
   })
 
 

+ 10 - 6
apps/security/static/css/security.css

@@ -27,6 +27,10 @@
   padding: 0;
 }
 
+.tree {
+  margin-left: 0;
+}
+
 .node-row {
   margin: 4px;
   padding: 2px;
@@ -49,11 +53,6 @@
   background-color: #F6F6F6 !important;
 }
 
-.path-container {
-  background-color: #FFF;
-  padding-top: 8px;
-}
-
 .acl-panel {
   border-left: 1px solid #e5e5e5;
   padding-top: 6px;
@@ -95,10 +94,15 @@
 }
 
 .path {
-  height: 34px;
+  height: 34px!important;
   font-size: 14px;
 }
 
+.path-container {
+  background-color: #FFF;
+  padding-top: 8px;
+}
+
 .path-container .btn-inverse {
   height: 34px;
   width: 34px;

+ 66 - 0
apps/security/static/js/common.ko.js

@@ -0,0 +1,66 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you 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.
+
+ko.bindingHandlers.select2 = {
+  init: function (element, valueAccessor, allBindingsAccessor, vm) {
+    var options = ko.toJS(valueAccessor()) || {};
+
+    if (typeof valueAccessor().update != "undefined") {
+      if (options.type == "user" && viewModel.selectableHadoopUsers().indexOf(options.update) == -1) {
+        viewModel.availableHadoopUsers.push({
+          username: options.update
+        });
+      }
+      if (options.type == "group" && viewModel.selectableHadoopGroups().indexOf(options.update) == -1) {
+        viewModel.availableHadoopGroups.push({
+          name: options.update
+        });
+      }
+    }
+    $(element)
+        .select2(options)
+        .on("change", function (e) {
+          if (typeof e.val != "undefined" && typeof valueAccessor().update != "undefined") {
+            valueAccessor().update(e.val);
+          }
+        })
+        .on("select2-open", function () {
+          $(".select2-input").off("keyup").data("type", options.type).on("keyup", function (e) {
+            if (e.keyCode === 13) {
+              var _newVal = $(this).val();
+              var _type = $(this).data("type");
+              if (_type == "user") {
+                viewModel.availableHadoopUsers.push({
+                  username: _newVal
+                });
+              }
+              if (_type == "group") {
+                viewModel.availableHadoopGroups.push({
+                  name: _newVal
+                });
+              }
+              $(element).select2("val", _newVal, true);
+              $(element).select2("close");
+            }
+          });
+        })
+  },
+  update: function (element, valueAccessor, allBindingsAccessor, vm) {
+    if (typeof valueAccessor().update != "undefined") {
+      $(element).select2("val", valueAccessor().update());
+    }
+  }
+};

+ 0 - 8
apps/security/static/js/hdfs.ko.js

@@ -112,7 +112,6 @@ var Assist = function (vm, assist) {
     window.location.hash = path;
   });
   self.pagenum = ko.observable(1);
-  self.files = ko.observableArray();
   self.fromLoadMore = false;
   self.fromRebuildTree = false;
 
@@ -347,15 +346,8 @@ var Assist = function (vm, assist) {
       function (data) {
         self.loadParents(data.breadcrumbs);
         if (data['files'] && data['files'][0] && data['files'][0]['type'] == 'dir') { // Hack for now
-          self.files.removeAll();
           $.each(data.files, function (index, item) {
             self.convertItemToObject(item);
-            self.files.push(ko.mapping.fromJS({
-                'path': item.path,
-                'aclBit': item.rwx.indexOf('+') != -1,
-                'striked': item.striked != null
-              })
-            );
           });
         }
         else {

+ 380 - 143
apps/security/static/js/hive.ko.js

@@ -20,55 +20,55 @@ var Privilege = function (vm, privilege) {
 
   self.privilegeScope = ko.observable(typeof privilege.privilegeScope != "undefined" && privilege.privilegeScope != null ? privilege.privilegeScope : "");
   self.serverName = ko.observable(typeof privilege.serverName != "undefined" && privilege.serverName != null ? privilege.serverName : "");
-  self.serverName.subscribe(function() {
+  self.serverName.subscribe(function () {
     if (self.status() == '') {
       self.status('modified');
     }
   });
   self.dbName = ko.observable(typeof privilege.dbName != "undefined" && privilege.dbName != null ? privilege.dbName : "");
-  self.dbName.subscribe(function() {
-	if (self.status() == '') {
-	  self.status('modified');
-	}
+  self.dbName.subscribe(function () {
+    if (self.status() == '') {
+      self.status('modified');
+    }
   });
   self.tableName = ko.observable(typeof privilege.tableName != "undefined" && privilege.tableName != null ? privilege.tableName : "");
-  self.tableName.subscribe(function() {
-	if (self.status() == '') {
-	  self.status('modified');
-	}
+  self.tableName.subscribe(function () {
+    if (self.status() == '') {
+      self.status('modified');
+    }
   });
   self.URI = ko.observable(typeof privilege.URI != "undefined" && privilege.URI != null ? privilege.URI : "");
-  self.URI.subscribe(function() {
-	if (self.status() == '') {
-	  self.status('modified');
-	}
+  self.URI.subscribe(function () {
+    if (self.status() == '') {
+      self.status('modified');
+    }
   });
   self.action = ko.observable(typeof privilege.action != "undefined" && privilege.action != null ? privilege.action : "");
-  self.action.subscribe(function() {
-	if (self.status() == '') {
-	  self.status('modified');
-	}
+  self.action.subscribe(function () {
+    if (self.status() == '') {
+      self.status('modified');
+    }
   });
 
   self.status = ko.observable(typeof privilege.status != "undefined" && privilege.status != null ? privilege.status : "");
-  self.edition = ko.observable(typeof privilege.edition != "undefined" && privilege.edition != null ? privilege.edition : false);
+  self.editing = ko.observable(typeof privilege.editing != "undefined" && privilege.editing != null ? privilege.editing : false);
 
   self.availablePrivileges = ko.observableArray(['SERVER', 'DATABASE', 'TABLE']);
   self.availableActions = ko.observableArray(['SELECT', 'INSERT', 'ALL', '']);
-  
-  self.remove = function(privilege) {
-	privilege.status('deleted');
+
+  self.remove = function (privilege) {
+    privilege.status('deleted');
   }
 }
 
-var Role = function(vm, role) {
+var Role = function (vm, role) {
   var self = this;
 
   self.name = ko.observable(typeof role.name != "undefined" && role.name != null ? role.name : "");
   self.grantorPrincipal = ko.observable(typeof role.grantorPrincipal != "undefined" && role.grantorPrincipal != null ? role.grantorPrincipal : "");
   self.groups = ko.observableArray();
-  $.each(typeof role.groups != "undefined" && role.groups != null ? role.groups : [], function(index, group) {
-	self.groups.push(group);
+  $.each(typeof role.groups != "undefined" && role.groups != null ? role.groups : [], function (index, group) {
+    self.groups.push(group);
   });
   self.privileges = ko.observableArray(); // Not included in the API
   self.showPrivileges = ko.observable(false);
@@ -77,68 +77,68 @@ var Role = function(vm, role) {
     return $.grep(self.privileges(), function (privilege) {
       return ['new', 'deleted', 'modified'].indexOf(privilege.status()) != -1;
     });
-  });  
+  });
 
-  self.reset = function() {
-	self.name('');
+  self.reset = function () {
+    self.name('');
     self.groups.removeAll();
     self.privileges.removeAll();
   }
 
-  self.addGroup = function() {
+  self.addGroup = function () {
     self.groups.push('');
   }
 
-  self.addPrivilege = function() {
-	self.privileges.push(new Privilege(vm, {'serverName': vm.assist.server(), 'status': 'new', 'edition': true}));
+  self.addPrivilege = function () {
+    self.privileges.push(new Privilege(vm, {'serverName': vm.assist.server(), 'status': 'new', 'editing': true}));
   }
 
-  self.create = function() {
-	$(".jHueNotify").hide();
+  self.create = function () {
+    $(".jHueNotify").hide();
     $.post("/security/api/hive/create_role", {
-        role: ko.mapping.toJSON(self)
-      }, function (data) {
-        if (data.status == 0) {
-          $(document).trigger("info", data.message);
-          vm.roles.unshift(new Role(vm, data.role));
-          self.reset();
-          vm.showCreateRole(false);
-        }
-        else {
-          $(document).trigger("error", data.message);
-        }
+      role: ko.mapping.toJSON(self)
+    }, function (data) {
+      if (data.status == 0) {
+        $(document).trigger("info", data.message);
+        vm.roles.unshift(new Role(vm, data.role));
+        self.reset();
+        vm.showCreateRole(false);
+      }
+      else {
+        $(document).trigger("error", data.message);
+      }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
   }
 
-  self.remove = function(role) {
-	$(".jHueNotify").hide();
+  self.remove = function (role) {
+    $(".jHueNotify").hide();
     $.post("/security/api/hive/drop_sentry_role", {
-    	roleName: role.name
-      }, function (data) {
-        if (data.status == 0) {
-          $(document).trigger("info", data.message);
-          vm.removeRole(role.name);
-        }
-        else {
-          $(document).trigger("error", data.message);
-        }
+      roleName: role.name
+    }, function (data) {
+      if (data.status == 0) {
+        $(document).trigger("info", data.message);
+        vm.removeRole(role.name);
+      }
+      else {
+        $(document).trigger("error", data.message);
+      }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
   }
 
-  self.savePrivileges = function(role) {
-	$(".jHueNotify").hide();
+  self.savePrivileges = function (role) {
+    $(".jHueNotify").hide();
     $.post("/security/api/hive/save_privileges", {
-        role: ko.mapping.toJSON(role)
-      }, function (data) {
-        if (data.status == 0) {
-          vm.list_sentry_privileges_by_role(role); // Refresh all role privileges
-        } else {
-          $(document).trigger("error", data.message);
-        }
+      role: ko.mapping.toJSON(role)
+    }, function (data) {
+      if (data.status == 0) {
+        vm.list_sentry_privileges_by_role(role); // Refresh all role privileges
+      } else {
+        $(document).trigger("error", data.message);
+      }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
@@ -149,41 +149,257 @@ var Role = function(vm, role) {
 var Assist = function (vm) {
   var self = this;
 
-  self.path = ko.observable('');  
-  self.path.subscribe(function() {
-	self.fetchDatabases();
+  self.compareNames = function (a, b) {
+    if (a.name.toLowerCase() < b.name.toLowerCase())
+      return -1;
+    if (a.name.toLowerCase() > b.name.toLowerCase())
+      return 1;
+    return 0;
+  }
+
+  self.path = ko.observable('');
+  self.path.subscribe(function () {
+    self.fetchHivePath();
   });
   self.server = ko.observable('server1');
-  self.db = ko.computed(function() {
+  self.db = ko.computed(function () {
     return self.path().split(/[.]/)[0];
   });
-  self.table = ko.computed(function() {
+  self.table = ko.computed(function () {
     return self.path().split(/[.]/)[1];
   });
-  self.files = ko.observableArray();
   self.privilege = ko.observable();
 
-  self.fetchDatabases = function() {
-	var path = self.path().replace('.', '/');
-
-    var request = {
-      url: '/beeswax/api/autocomplete/' + path,
-      dataType: 'json',
-      type: 'GET',
-      success: function(data) {
-    	if (data.databases) {
-          self.files(data.databases);
-    	} else if (data.tables && data.tables.length > 0) {
-    	  var tables = $.map(data.tables, function(table, index) {return self.db() + '.' + table;});
-    	  self.files(tables);
-    	}
-      },
-      cache: false
-    };
-    $.ajax(request).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
+  self.isDiffMode = ko.observable(false);
+  self.isDiffMode.subscribe(function () {
+    //self.refreshTree();
+  });
+
+  self.isLoadingTree = ko.observable(false);
+
+  self.treeAdditionalData = {};
+  self.treeData = ko.observable({nodes: []});
+  self.loadData = function (data) {
+    self.treeData(new TreeNodeModel(data));
+  };
+
+  self.initialGrowingTree = {
+    path: "__HUEROOT__",
+    name: "__HUEROOT__",
+    selected: false,
+    nodes: []
+  };
+
+  self.growingTree = ko.observable(jQuery.extend(true, {}, self.initialGrowingTree));
+
+  self.addDatabases = function (databases) {
+    var _tree = self.growingTree();
+    databases.forEach(function (db) {
+      var _mainFound = false;
+      _tree.nodes.forEach(function (node) {
+        if (node.path == db) {
+          _mainFound = true;
+        }
+      });
+      if (!_mainFound) {
+        var _item = {
+          path: db,
+          name: db,
+          isDb: true,
+          isTable: false,
+          isColumn: false,
+          isExpanded: false,
+          nodes: []
+        };
+        _tree.nodes.push(_item);
+      }
+    });
+    self.loadData(self.growingTree());
+  }
+
+  self.addTables = function (tables) {
+    var _branch = self.growingTree();
+    _branch.nodes.forEach(function (node) {
+      if (node.path == self.path()) {
+        _branch = node;
+      }
+    });
+
+    tables.forEach(function (table) {
+      var _mainFound = false;
+      var _path = self.path() + "." + table;
+      _branch.nodes.forEach(function (node) {
+        if (node.path == _path) {
+          _mainFound = true;
+        }
+      });
+      if (!_mainFound) {
+        var _item = {
+          path: _path,
+          name: table,
+          isDb: false,
+          isTable: true,
+          isColumn: false,
+          isExpanded: false,
+          nodes: []
+        };
+        _branch.nodes.push(_item);
+      }
+    });
+    self.loadData(self.growingTree());
+  }
+
+  self.addColumns = function (columns) {
+    var _branch = self.growingTree();
+    _branch.nodes.forEach(function (node) {
+      if (node.path == self.path().split(".")[0]) {
+
+        node.nodes.forEach(function (inode) {
+          if (inode.path == self.path()) {
+            _branch = inode;
+          }
+        });
+
+      }
+    });
+
+    columns.forEach(function (column) {
+      var _mainFound = false;
+      var _path = self.path() + "." + column;
+      _branch.nodes.forEach(function (node) {
+        if (node.path == _path) {
+          _mainFound = true;
+        }
+      });
+      if (!_mainFound) {
+        var _item = {
+          path: _path,
+          name: column,
+          isDb: false,
+          isTable: false,
+          isColumn: true,
+          isExpanded: false,
+          nodes: []
+        };
+        _branch.nodes.push(_item);
+      }
     });
+    self.loadData(self.growingTree());
+  }
+
+  self.setPath = function (obj, toggle) {
+    if (self.getTreeAdditionalDataForPath(obj.path()).loaded || (!obj.isExpanded() && !self.getTreeAdditionalDataForPath(obj.path()).loaded)) {
+      if (typeof toggle == "boolean" && toggle) {
+        obj.isExpanded(!obj.isExpanded());
+      }
+      else {
+        obj.isExpanded(true);
+      }
+      self.updatePathProperty(self.growingTree(), obj.path(), "isExpanded", obj.isExpanded());
+    }
+    self.path(obj.path());
+  }
+
+  self.togglePath = function (obj) {
+    self.setPath(obj, true);
+  }
+
+
+  self.getTreeAdditionalDataForPath = function (path) {
+    if (typeof self.treeAdditionalData[path] == "undefined") {
+      var _add = {
+        loaded: false
+      }
+      self.treeAdditionalData[path] = _add;
+    }
+    return self.treeAdditionalData[path];
+  }
+
+  self.updatePathProperty = function (leaf, path, property, value) {
+    if (leaf.path == path) {
+      leaf[property] = value;
+    }
+    if (leaf.nodes.length > 0) {
+      leaf.nodes.forEach(function (node) {
+        self.updatePathProperty(node, path, property, value);
+      });
+    }
+    return leaf;
+  }
+
+  self.updateTreeProperty = function (leaf, property, value) {
+    leaf[property] = value;
+    if (leaf.nodes.length > 0) {
+      leaf.nodes.forEach(function (node) {
+        self.updateTreeProperty(node, property, value);
+      });
+    }
+    return leaf;
+  }
+
+  self.collapseTree = function () {
+    self.updateTreeProperty(self.growingTree(), "isExpanded", false);
+    self.updatePathProperty(self.growingTree(), "/", "isExpanded", true);
+    self.loadData(self.growingTree());
+  }
+
+  self.collapseOthers = function () {
+    self.updateTreeProperty(self.growingTree(), "isExpanded", false);
+    self.updatePathProperty(self.growingTree(), "/", "isExpanded", true);
+
+    var _path = self.path();
+    var _crumb = "";
+    for (var i = 0; i < _path.length; i++) {
+      if ((_path[i] === "/" && _crumb != "")) {
+        self.updatePathProperty(self.growingTree(), _crumb, "isExpanded", true);
+      }
+      _crumb += _path[i];
+    }
+
+    self.updatePathProperty(self.growingTree(), _path, "isExpanded", true);
+
+    self.loadData(self.growingTree());
+  }
+
+
+  self.fetchHivePath = function () {
+    if (self.path().split(".").length < 3) {
+      var _path = self.path().replace('.', '/');
+
+      var request = {
+        url: '/beeswax/api/autocomplete/' + _path,
+        dataType: 'json',
+        type: 'GET',
+        success: function (data) {
+          if (data.databases) {
+            self.addDatabases(data.databases);
+          }
+          else if (data.tables && data.tables.length > 0) {
+            self.addTables(data.tables);
+            var tables = $.map(data.tables, function (table, index) {
+              return self.db() + '.' + table;
+            });
+          }
+          else if (data.columns && data.columns.length > 0) {
+            self.addColumns(data.columns);
+          }
+
+          vm.list_sentry_privileges_by_authorizable();
+        },
+        cache: false
+      };
+      $.ajax(request).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    }
+    else {
+      vm.list_sentry_privileges_by_authorizable();
+    }
   };
+
+  self.afterRender = function () {
+    $(document).trigger("rendered.tree");
+  }
 }
 
 
@@ -196,25 +412,39 @@ var HiveViewModel = function (initial) {
   self.availableHadoopGroups = ko.mapping.fromJS(initial.hadoop_groups);
   self.assist = new Assist(self);
 
-  // Edition
+  // Editing
   self.showCreateRole = ko.observable(false);
   self.role = new Role(self, {});
   self.privilege = new Privilege(self, {});
 
-  self.doAs = ko.observable('');
-  self.doAs.subscribe(function() {
-	self.assist.fetchDatabases();
+  self.doAs = ko.observable(initial.user);
+  self.doAs.subscribe(function () {
+    self.assist.fetchHivePath();
   });
   self.availableHadoopUsers = ko.observableArray();
   self.availableHadoopGroups = ko.observableArray();
 
-  self.init = function() {
-	self.fetchUsers();
+  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;
+    });
+    return _users.sort();
+  }, self);
+
+  self.init = function () {
+    self.fetchUsers();
     self.list_sentry_roles_by_group();
-    self.assist.fetchDatabases();
+    self.assist.fetchHivePath();
   };
 
-  self.removeRole = function(roleName) {
+  self.removeRole = function (roleName) {
     $.each(self.roles(), function (index, role) {
       if (role.name == roleName) {
         self.roles.remove(role);
@@ -223,82 +453,89 @@ var HiveViewModel = function (initial) {
     });
   };
 
-  self.list_sentry_roles_by_group = function() {
-    $.getJSON('/security/api/hive/list_sentry_roles_by_group', {    	
-      }, function (data) {
-        $.each(data.roles, function(index, item) {
-    	  self.roles.push(new Role(self, item));
+  self.list_sentry_roles_by_group = function () {
+    $.getJSON('/security/api/hive/list_sentry_roles_by_group', function (data) {
+      if (typeof data.status !== "undefined" && data.status == -1) {
+        $(document).trigger("error", data.message);
+      }
+      else {
+        $.each(data.roles, function (index, item) {
+          self.roles.push(new Role(self, item));
         });
+      }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
   };
 
-  self.list_sentry_privileges_by_role = function(role) {
+  self.list_sentry_privileges_by_role = function (role) {
     $.ajax({
       type: "POST",
       url: "/security/api/hive/list_sentry_privileges_by_role",
-      data: {    	
+      data: {
         'roleName': role.name
       },
       success: function (data) {
-    	role.privileges.removeAll();
-        $.each(data.sentry_privileges, function(index, item) {
-          role.privileges.push(_create_ko_privilege(item));
-        });
-        role.showPrivileges(true);
+        if (typeof data.status !== "undefined" && data.status == -1) {
+          $(document).trigger("error", data.message);
+        }
+        else {
+          role.privileges.removeAll();
+          $.each(data.sentry_privileges, function (index, item) {
+            role.privileges.push(_create_ko_privilege(item));
+          });
+          role.showPrivileges(true);
+        }
       }
     }).fail(function (xhr, textStatus, errorThrown) {
       $(document).trigger("error", xhr.responseText);
     });
   };
-  
+
   function _create_ko_privilege(privilege) {
-	var _privilege = new Privilege(self, {
-        'privilegeScope': privilege.scope,
-        'serverName': privilege.server,
-        'dbName': privilege.database,
-        'tableName': privilege.table,
-        'URI': privilege.URI,
-        'action': privilege.action
+    var _privilege = new Privilege(self, {
+      'privilegeScope': privilege.scope,
+      'serverName': privilege.server,
+      'dbName': privilege.database,
+      'tableName': privilege.table,
+      'URI': privilege.URI,
+      'action': privilege.action
     });
     _privilege.properties = ko.mapping.fromJS(privilege);
     return _privilege;
   }
 
-  self.list_sentry_privileges_by_authorizable = function(path) {
-	self.assist.path(path);
-    $.ajax({
-      type: "POST",
-      url: "/security/api/hive/list_sentry_privileges_by_authorizable",
-      data: {    	
-    	groups: ko.mapping.toJSON(['sambashare', 'hadoop']),
-    	roleSet: ko.mapping.toJSON({all: true, roles: []}),
-        authorizableHierarchy: ko.mapping.toJSON({
+  self.list_sentry_privileges_by_authorizable = function () {
+    if (self.assist.path() != ""){
+      $.ajax({
+        type: "POST",
+        url: "/security/api/hive/list_sentry_privileges_by_authorizable",
+        data: {
+          groups: ko.mapping.toJSON(['sambashare', 'hadoop']),
+          roleSet: ko.mapping.toJSON({all: true, roles: []}),
+          authorizableHierarchy: ko.mapping.toJSON({
             'server': self.assist.server(),
             'db': self.assist.db(),
-            'table': self.assist.table(),
-        }),
-      },
-      success: function (data) {
-    	self.assist.privilege(ko.mapping.fromJS(data));
-      }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-    });
+            'table': self.assist.table()
+          })
+        },
+        success: function (data) {
+          self.assist.privilege(ko.mapping.fromJS(data));
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    }
   };
-  
+
   self.fetchUsers = function () {
     $.getJSON('/desktop/api/users/autocomplete', {
-      
+      'include_myself': true,
+      'extend_user': true
     }, function (data) {
-      $.each(data.users, function (i, user) {
-        self.availableHadoopUsers.push(user.username);
-      });
-
-      $.each(data.groups, function (i, group) {
-        self.availableHadoopGroups.push(group.name);
-      });      
+      self.availableHadoopUsers(data.users);
+      self.availableHadoopGroups(data.groups);
+      $(document).trigger("loaded.users");
     });
   }
 };

+ 5 - 2
desktop/core/src/desktop/templates/common_tree.mako

@@ -25,7 +25,7 @@
   from django.utils.translation import ugettext as _
 %>
 
-<%def name="import_templates(itemClick=None, iconClick=None, itemDblClick=None, itemSelected=None, iconModifier=None, styleModifier=None, styleModifierPullRight=None, showMore=None, anchorProperty=None)">
+<%def name="import_templates(itemClick=None, iconClick=None, itemDblClick=None, itemSelected=None, iconModifier=None, styleModifier=None, styleModifierPullRight=None, showMore=None, anchorProperty=None, strikedProperty=None)">
 
   <script src="/static/js/ko.tree.js" type="text/javascript" charset="utf-8"></script>
 
@@ -112,7 +112,10 @@
       %if styleModifier:
         style: { color: ${styleModifier}() ? '#b94a48': ''},
       %endif
-      css:{'striked': striked}"></a></strong>
+      %if strikedProperty:
+      css:{'striked': striked},
+      %endif
+      visible: true"></a></strong>
 
       %if styleModifierPullRight:
       ${styleModifierPullRight()}