Pārlūkot izejas kodu

HUE-5964 [useradmin] KOify user list page

Enrico Berti 8 gadi atpakaļ
vecāks
revīzija
45d2c0c264

+ 126 - 123
apps/useradmin/src/useradmin/templates/list_users.mako

@@ -14,7 +14,7 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 <%!
-from desktop.views import commonheader, commonfooter, antixss
+from desktop.views import commonheader, commonfooter, antixss, _ko
 from django.template.defaultfilters import date, time
 from django.utils.translation import ugettext as _
 %>
@@ -30,45 +30,39 @@ ${layout.menubar(section='users')}
 
     <%actionbar:render>
       <%def name="search()">
-          <input id="filterInput" type="text" class="input-xlarge search-query"
-                 placeholder="${_('Search for name, group, etc...')}">
+        <input type="text" class="input-xlarge search-query" placeholder="${_('Search for name, group, etc...')}" data-bind="textInput: filter">
       </%def>
       <%def name="actions()">
-        %if user.is_superuser:
-            <button id="deleteUserBtn" class="btn" title="${_('Delete')}" disabled="disabled"><i
-                class="fa fa-trash-o"></i> ${_('Delete')}</button>
-        %endif
+        <!-- ko if: isSuperUser -->
+        <button id="deleteUserBtn" class="btn" title="${_('Delete')}" data-bind="enable: chosenUsers().length > 0"><i class="fa fa-trash-o"></i> ${_('Delete')}</button>
+        <!-- /ko -->
       </%def>
       <%def name="creation()">
-        %if user.is_superuser:
-            % if not is_ldap_setup:
-                <a href="${ url('useradmin.views.edit_user') }" class="btn"><i class="fa fa-user"></i> ${_('Add user')}</a>
-            %endif
-
-            % if is_ldap_setup:
-            <a href="${ url('useradmin.views.add_ldap_users') }" class="btn"><i
-                class="fa fa-briefcase"></i> ${_('Add/Sync LDAP user')}</a>
-            <a href="javascript:void(0)" class="btn confirmationModal"
-               data-confirmation-url="${ url('useradmin.views.sync_ldap_users_groups') }"><i
-                class="fa fa-refresh"></i> ${_('Sync LDAP users/groups')}</a>
-            % endif
-
-            <a href="http://gethue.com/making-hadoop-accessible-to-your-employees-with-ldap/" class="btn"
-              title="${ ('Learn how to integrate Hue with your company') }" target="_blank">
-              <i class="fa fa-question-circle"></i> LDAP
-            </a>
+        <!-- ko if: isSuperUser -->
+        % if not is_ldap_setup:
+            <a href="${ url('useradmin.views.edit_user') }" class="btn"><i class="fa fa-user"></i> ${_('Add user')}</a>
         %endif
+
+        % if is_ldap_setup:
+        <a href="${ url('useradmin.views.add_ldap_users') }" class="btn"><i class="fa fa-briefcase"></i> ${_('Add/Sync LDAP user')}</a>
+        <a href="javascript:void(0)" class="btn confirmationModal" data-confirmation-url="${ url('useradmin.views.sync_ldap_users_groups') }"><i class="fa fa-refresh"></i> ${_('Sync LDAP users/groups')}</a>
+        % endif
+
+        <a href="http://gethue.com/making-hadoop-accessible-to-your-employees-with-ldap/" class="btn" title="${ ('Learn how to integrate Hue with your company') }" target="_blank">
+          <i class="fa fa-question-circle"></i> LDAP
+        </a>
+        <!-- /ko -->
       </%def>
     </%actionbar:render>
 
-    <table class="table table-condensed datatables">
+    <table class="table table-condensed">
       <thead>
       <tr>
-        %if user.is_superuser:
-            <th width="1%">
-              <div id="selectAll" class="hueCheckbox fa"></div>
-            </th>
-        %endif
+        <!-- ko if: isSuperUser -->
+        <th width="1%">
+          <div class="hueCheckbox fa fa-fw" data-bind="css: {'fa-check': allSelected, 'fa-minus': someSelected}, click: toggleAll"></div>
+        </th>
+        <!-- /ko -->
         <th>${_('Username')}</th>
         <th>${_('First Name')}</th>
         <th>${_('Last Name')}</th>
@@ -77,33 +71,37 @@ ${layout.menubar(section='users')}
         <th>${_('Last Login')}</th>
       </tr>
       </thead>
-      <tbody>
-          % for listed_user in users:
-          <tr class="tableRow"
-              data-search="${listed_user.username}${listed_user.first_name}${listed_user.last_name}${listed_user.email}${', '.join([group.name for group in listed_user.groups.all()])}">
-          %if user.is_superuser:
-              <td data-row-selector-exclude="true">
-                <div class="hueCheckbox userCheck fa" data-row-selector-exclude="true" data-id="${ listed_user.id }"></div>
-              </td>
-          %endif
+      <tbody data-bind="foreach: filteredUsers">
+        <tr>
+          <!-- ko if: $parent.isSuperUser -->
+            <!-- ko if: username() !== '${ user.username }' -->
+            <td class="center" data-bind="click: function(){ selected(!selected()) }" style="cursor: default" data-row-selector-exclude="true">
+              <div class="hueCheckbox fa" data-bind="css: { 'fa-check': selected }"></div>
+            </td>
+            <!-- /ko -->
+            <!-- ko if: username() === '${ user.username }' -->
+            <td class="center" style="cursor: default" data-row-selector-exclude="true">
+              <div class="hueCheckbox disabled"></div>
+            </td>
+            <!-- /ko -->
+          <!-- /ko -->
           <td>
-            %if user.is_superuser or user.username == listed_user.username:
-              <strong><a title="${_('Edit %(username)s') % dict(username=listed_user.username)}"
-                         href="${ url('useradmin.views.edit_user', username=listed_user.username) }"
-                         data-row-selector="true">${listed_user.username}</a></strong>
-            %else:
-              <strong>${listed_user.username}</strong>
-            %endif
+            <!-- ko if: $parent.isSuperUser() || username() == '${user.username}' -->
+            <strong><a data-bind="text: username, attr: {'title': '${ _ko('Edit user') } ' + username(), 'href': editURL}" data-row-selector="true"></a></strong>
+            <!-- /ko -->
+            <!-- ko if: !$parent.isSuperUser() && username() != '${user.username}' -->
+            <strong data-bind="text: username"></strong>
+            <!-- /ko -->
           </td>
-            <td>${listed_user.first_name}</td>
-            <td>${listed_user.last_name}</td>
-            <td>${listed_user.email}</td>
-            <td>${', '.join([group.name for group in listed_user.groups.all()])}</td>
-            <td>${date(listed_user.last_login)} ${time(listed_user.last_login).replace("p.m.","PM").replace("a.m.","AM")}</td>
-          </tr>
-          % endfor
+          <td data-bind="text: first_name"></td>
+          <td data-bind="text: last_name"></td>
+          <td data-bind="text: email"></td>
+          <td data-bind="text: readableGroups"></td>
+          <td data-bind="text: localeFormat(last_login)"></td>
+        </tr>
       </tbody>
-      <tfoot class="hide">
+      <!-- ko if: filteredUsers().length === 0 -->
+      <tfoot>
       <tr>
         <td colspan="8">
           <div class="alert">
@@ -112,6 +110,7 @@ ${layout.menubar(section='users')}
         </td>
       </tr>
       </tfoot>
+      <!-- /ko -->
     </table>
   </div>
 
@@ -130,8 +129,7 @@ ${layout.menubar(section='users')}
         <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
       </div>
       <div class="hide">
-        <select name="user_ids" data-bind="options: availableUsers, selectedOptions: chosenUsers"
-                multiple="true"></select>
+        <select name="user_ids" data-bind="options: availableUsers().map(function(item){ return item.id() }), selectedOptions: chosenUsers().map(function(item){ return item.id() })" multiple="true"></select>
       </div>
     </form>
   </div>
@@ -141,41 +139,83 @@ ${layout.menubar(section='users')}
 <script src="${ static('desktop/ext/js/datatables-paging-0.1.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
-  var viewModel = {
-    availableUsers: ko.observableArray(${ users_json | n,antixss }),
-    chosenUsers: ko.observableArray([])
-  };
-  var mainDataTable;
   $(document).ready(function () {
 
-    ko.applyBindings(viewModel);
+    var ListUsersViewModel = function () {
+      var self = this;
 
-    mainDataTable = $(".datatables").dataTable({
-      "sPaginationType":"bootstrap",
-      "iDisplayLength":100,
-      "bLengthChange":false,
-      "sDom": "<'row'r>t<'row-fluid'<'dt-pages'p><'dt-records'i>>",
-      "bInfo": false,
-      "bFilter": true,
-      "aoColumns": [
-        %if user.is_superuser:
-            { "bSortable": false },
-        %endif
-        null,
-        null,
-        null,
-        null,
-        null,
-        { "sType": "date" },
-      ],
-      "oLanguage": {
-        "sEmptyTable": "${_('No data available')}",
-        "sZeroRecords": "${_('No matching records')}",
+      self.filter = ko.observable('').extend({throttle: 500});
+
+      self.isSuperUser = ko.observable(${ user.is_superuser and 'true' or 'false' })
+      self.availableUsers = ko.observableArray([]);
+
+      self.filteredUsers = ko.pureComputed(function () {
+        return self.availableUsers().filter(function (item) {
+          return item.username().toLowerCase().indexOf(self.filter()) > -1 ||
+              item.first_name().toLowerCase().indexOf(self.filter()) > -1 ||
+              item.last_name().toLowerCase().indexOf(self.filter()) > -1 ||
+              item.email().toLowerCase().indexOf(self.filter()) > -1 ||
+              item.readableGroups().toLowerCase().indexOf(self.filter()) > -1
+        });
+      });
+
+      self.chosenUsers = ko.pureComputed(function () {
+        return self.filteredUsers().filter(function (item) {
+          return item.selected()
+        });
+      });
+
+      self.toggleAll = function () {
+        var selected = self.allSelected();
+        ko.utils.arrayForEach(self.filteredUsers(), function (user) {
+          if (user.username() !== '${ user.username }') {
+            user.selected(!selected);
+          }
+          else {
+            user.selected(false);
+          }
+        });
+      };
+
+      self.allSelected = ko.pureComputed(function () {
+        var filtered = self.filteredUsers().filter(function (item) {
+          return item.selected()
+        });
+        return filtered.length > 0 && filtered.length === self.filteredUsers().length - 1
+      });
+
+      self.someSelected = ko.pureComputed(function () {
+        var filtered = self.filteredUsers().filter(function (item) {
+          return item.selected()
+        });
+        return filtered.length > 0 && filtered.length < self.filteredUsers().length - 1
+      });
+
+      self.init = function () {
+        $.getJSON('/useradmin/users', function (data) {
+          if (data && data.users) {
+            var users = [];
+            data.users.forEach(function (u) {
+              var user = ko.mapping.fromJS(u);
+              user.selected = ko.observable(false);
+              user.readableGroups = ko.pureComputed(function () {
+                return u.groups ? user.groups().map(function (item) {
+                      return item.name()
+                    }).join(', ') : ''
+              });
+              users.push(user);
+            })
+            self.availableUsers(users);
+          }
+        });
       }
-    });
+    };
 
-    $(".dataTables_wrapper").css("min-height", "0");
-    $(".dataTables_filter").hide();
+    var viewModel = new ListUsersViewModel();
+
+    viewModel.init();
+
+    ko.applyBindings(viewModel);
 
     $(".confirmationModal").click(function () {
       var _this = $(this);
@@ -192,44 +232,7 @@ ${layout.menubar(section='users')}
       });
     });
 
-    $("#selectAll").click(function () {
-      if ($(this).attr("checked")) {
-        $(this).removeAttr("checked").removeClass("fa-check");;
-        $(".userCheck").removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).attr("checked", "checked").addClass("fa-check");
-        $(".userCheck").addClass("fa-check").attr("checked", "checked");
-      }
-      toggleActions();
-    });
-
-    $(document).on('click', '.userCheck', function () {
-      if ($(this).attr("checked")) {
-        $(this).removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).addClass("fa-check").attr("checked", "checked");
-      }
-      toggleActions();
-    });
-
-    function toggleActions() {
-      if ($(".userCheck[checked='checked']").length >= 1) {
-        $("#deleteUserBtn").removeAttr("disabled");
-      }
-      else {
-        $("#deleteUserBtn").attr("disabled", "disabled");
-      }
-    }
-
     $("#deleteUserBtn").click(function () {
-      viewModel.chosenUsers.removeAll();
-
-      $(".hueCheckbox[checked='checked']").each(function (index) {
-        viewModel.chosenUsers.push($(this).data("id"));
-      });
-
       $("#deleteUser").modal("show");
     });
 

+ 8 - 2
apps/useradmin/src/useradmin/views.py

@@ -60,8 +60,12 @@ __groups_lock = threading.Lock()
 
 def list_users(request):
   is_ldap_setup = bool(LDAP.LDAP_SERVERS.get()) or LDAP.LDAP_URL.get() is not None
+  users = User.objects.all()
+  if request.ajax:
+    return JsonResponse({'users' : massage_users_for_json(users, True)})
+
   return render("list_users.mako", request, {
-      'users': User.objects.all(),
+      'users': users,
       'users_json': json.dumps(list(User.objects.values_list('id', flat=True))),
       'request': request,
       'is_ldap_setup': is_ldap_setup
@@ -120,7 +124,9 @@ def massage_users_for_json(users, extended=False):
       'username': user.username,
       'first_name': user.first_name,
       'last_name': user.last_name,
-      'email': user.email
+      'email': user.email,
+      'last_login': user.last_login,
+      'editURL': reverse('useradmin.views.edit_user', kwargs={'username': user.username})
     }
     if extended:
       appendable['groups'] = massage_groups_for_json(user.groups.all())

+ 10 - 0
desktop/core/src/desktop/static/desktop/css/hue3.css

@@ -1204,10 +1204,20 @@ a#advanced-btn:hover {
   cursor: default;
 }
 
+.hueCheckbox.disabled {
+  border: 1px solid #DDDDDD;
+  background-color: #FEFEFE;
+  cursor: not-allowed;
+}
+
 .hueCheckbox:hover {
   border-color: #999999;
 }
 
+.hueCheckbox.disabled:hover {
+  border-color: #DDDDDD;
+}
+
 .hueCheckboxText {
   left: 3px;
   position: relative;