list_users.mako 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. ## Licensed to Cloudera, Inc. under one
  2. ## or more contributor license agreements. See the NOTICE file
  3. ## distributed with this work for additional information
  4. ## regarding copyright ownership. Cloudera, Inc. licenses this file
  5. ## to you under the Apache License, Version 2.0 (the
  6. ## "License"); you may not use this file except in compliance
  7. ## with the License. You may obtain a copy of the License at
  8. ##
  9. ## http://www.apache.org/licenses/LICENSE-2.0
  10. ##
  11. ## Unless required by applicable law or agreed to in writing, software
  12. ## distributed under the License is distributed on an "AS IS" BASIS,
  13. ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ## See the License for the specific language governing permissions and
  15. ## limitations under the License.
  16. <%!
  17. from django.template.defaultfilters import date, time
  18. from django.utils.translation import ugettext as _
  19. from desktop.lib.django_util import USERNAME_RE_RULE
  20. from desktop.views import commonheader, commonfooter, antixss
  21. import re
  22. %>
  23. <%namespace name="actionbar" file="actionbar.mako" />
  24. <%namespace name="layout" file="layout.mako" />
  25. %if not is_embeddable:
  26. ${ commonheader(_('Hue Users'), "useradmin", user, request) | n,unicode }
  27. %endif
  28. ${layout.menubar(section='users')}
  29. <div id="usersComponents" class="useradmin container-fluid">
  30. <div class="card card-small">
  31. <h1 class="card-heading simple">${_('Hue Users')}</h1>
  32. <%actionbar:render>
  33. <%def name="search()">
  34. <input type="text" class="input-xlarge search-query filter-input" placeholder="${_('Search for name, group, etc...')}">
  35. </%def>
  36. <%def name="actions()">
  37. %if user.is_superuser:
  38. <button class="btn delete-user-btn" title="${_('Delete')}" disabled="disabled"><i class="fa fa-trash-o"></i> ${_('Delete')}</button>
  39. %endif
  40. </%def>
  41. <%def name="creation()">
  42. %if user.is_superuser:
  43. % if not is_ldap_setup:
  44. <a href="${ url('useradmin.views.edit_user') }" class="btn"><i class="fa fa-user"></i> ${_('Add user')}</a>
  45. %endif
  46. % if is_ldap_setup:
  47. <a href="${ url('useradmin.views.add_ldap_users') }" class="btn"><i class="fa fa-briefcase"></i> ${_('Add/Sync LDAP user')}</a>
  48. <a href="javascript:void(0)" class="btn confirmationModal"
  49. data-confirmation-url="${ url('useradmin_views_sync_ldap_users_groups') }${ is_embeddable and '?is_embeddable=true' or ''}">
  50. <i class="fa fa-refresh"></i> ${_('Sync LDAP users/groups')}
  51. </a>
  52. % endif
  53. <a href="http://gethue.com/making-hadoop-accessible-to-your-employees-with-ldap/"
  54. title="${ _('Learn how to integrate Hue with your company LDAP') }" target="_blank">
  55. <i class="fa fa-question-circle"></i>
  56. </a>
  57. %endif
  58. </%def>
  59. </%actionbar:render>
  60. <table class="table table-condensed datatables">
  61. <thead>
  62. <tr>
  63. %if user.is_superuser:
  64. <th width="1%">
  65. <div class="select-all hue-checkbox fa"></div>
  66. </th>
  67. %endif
  68. <th>${_('Username')}</th>
  69. <th>${_('First Name')}</th>
  70. <th>${_('Last Name')}</th>
  71. <th>${_('E-mail')}</th>
  72. <th>${_('Groups')}</th>
  73. <th>${_('Last Login')}</th>
  74. </tr>
  75. </thead>
  76. <tbody>
  77. % for listed_user in users:
  78. <tr class="tableRow"
  79. 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()])}">
  80. %if user.is_superuser:
  81. <td data-row-selector-exclude="true">
  82. <div class="hue-checkbox userCheck fa" data-row-selector-exclude="true" data-id="${ listed_user.id }"></div>
  83. </td>
  84. %endif
  85. <td>
  86. %if (user.is_superuser or user.username == listed_user.username) and re.match(USERNAME_RE_RULE, listed_user.username):
  87. <strong><a title="${_('Edit %(username)s') % dict(username=listed_user.username)}"
  88. href="${ url('useradmin.views.edit_user', username=listed_user.username) }"
  89. data-row-selector="true">${listed_user.username}</a></strong>
  90. %else:
  91. <strong>${listed_user.username}</strong>
  92. %endif
  93. </td>
  94. <td>${listed_user.first_name}</td>
  95. <td>${listed_user.last_name}</td>
  96. <td>${listed_user.email}</td>
  97. <td>${', '.join([group.name for group in listed_user.groups.all()])}</td>
  98. <td>${date(listed_user.last_login)} ${time(listed_user.last_login).replace("p.m.","PM").replace("a.m.","AM")}</td>
  99. </tr>
  100. % endfor
  101. </tbody>
  102. <tfoot class="hide">
  103. <tr>
  104. <td colspan="8">
  105. <div class="alert">
  106. ${_('There are no users matching the search criteria.')}
  107. </div>
  108. </td>
  109. </tr>
  110. </tfoot>
  111. </table>
  112. </div>
  113. <div id="syncLdap" class="modal hide fade"></div>
  114. <div class="modal hide fade delete-user">
  115. <form action="${ url('useradmin.views.delete_user') }" method="POST">
  116. ${ csrf_token(request) | n,unicode }
  117. % if is_embeddable:
  118. <input type="hidden" value="true" name="is_embeddable" />
  119. % endif
  120. <div class="modal-header">
  121. <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
  122. <h2 class="modal-title">${ _("Are you sure you want to delete the selected user(s)?") }</h2>
  123. </div>
  124. <div class="modal-footer">
  125. <a href="#" class="btn" data-dismiss="modal">${_('No')}</a>
  126. <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
  127. </div>
  128. <div class="hide">
  129. <select name="user_ids" data-bind="options: availableUsers, selectedOptions: chosenUsers" multiple="true"></select>
  130. </div>
  131. </form>
  132. </div>
  133. <div class="modal hide fade sync-ldap">
  134. </div>
  135. </div>
  136. <script src="${ static('desktop/ext/js/datatables-paging-0.1.js') }" type="text/javascript" charset="utf-8"></script>
  137. <script type="text/javascript">
  138. $(document).ready(function () {
  139. var $usersComponents = $('#usersComponents');
  140. $(document).off('click', '#usersComponents .userCheck');
  141. var viewModel = {
  142. availableUsers: ko.observableArray(${ users_json | n,antixss }),
  143. chosenUsers: ko.observableArray([])
  144. };
  145. ko.applyBindings(viewModel, $usersComponents[0]);
  146. var dt = $usersComponents.find('.datatables').dataTable({
  147. "sPaginationType":"bootstrap",
  148. "iDisplayLength":100,
  149. "bLengthChange":false,
  150. "sDom": "<'row'r>t<'row-fluid'<'dt-pages'p><'dt-records'i>>",
  151. "bInfo": false,
  152. "bFilter": true,
  153. "aoColumns": [
  154. %if user.is_superuser:
  155. { "bSortable": false },
  156. %endif
  157. null,
  158. null,
  159. null,
  160. null,
  161. null,
  162. { "sType": "date" },
  163. ],
  164. "oLanguage": {
  165. "sEmptyTable": "${_('No data available')}",
  166. "sZeroRecords": "${_('No matching records')}",
  167. }
  168. });
  169. $usersComponents.find(".filter-input").jHueDelayedInput(function () {
  170. if (dt) {
  171. dt.fnFilter($usersComponents.find(".filter-input").val().toLowerCase());
  172. }
  173. });
  174. $usersComponents.find('[data-rel="tooltip"]').tooltip({
  175. placement: 'right'
  176. });
  177. % if is_embeddable:
  178. $usersComponents.find('.delete-user form').ajaxForm({
  179. dataType: 'json',
  180. success: function(data) {
  181. if (data && data.url){
  182. huePubSub.publish('open.link', data.url);
  183. }
  184. $.jHueNotify.info("${ _('The users were deleted.') }")
  185. $usersComponents.find(".delete-user").modal("hide");
  186. },
  187. error: function(response, status, err) {
  188. if (response.responseJSON && response.responseJSON.message && response.status == 401) {
  189. $.jHueNotify.error(response.responseJSON.message);
  190. }
  191. else {
  192. $.jHueNotify.error("${ _('An unknown error has occurred while deleting the user. Please try again.') }");
  193. }
  194. $usersComponents.find(".delete-user").modal("hide");
  195. }
  196. });
  197. % endif
  198. $usersComponents.find(".dataTables_wrapper").css("min-height", "0");
  199. $usersComponents.find(".dataTables_filter").hide();
  200. $usersComponents.find(".confirmationModal").click(function () {
  201. var _this = $(this);
  202. $.ajax({
  203. url: _this.data("confirmation-url"),
  204. beforeSend: function (xhr) {
  205. xhr.setRequestHeader("X-Requested-With", "Hue");
  206. },
  207. dataType: "html",
  208. success: function (data) {
  209. $usersComponents.find(".sync-ldap").html(data);
  210. % if is_embeddable:
  211. $usersComponents.find('.sync-ldap form').ajaxForm({
  212. dataType: 'json',
  213. beforeSend: function (xhr) {
  214. $usersComponents.find('input[type="submit"]').attr('disabled','disabled');
  215. },
  216. success: function(data) {
  217. $usersComponents.find('input[type="submit"]').removeAttr("disabled");
  218. if (data && data.status == -1) {
  219. renderUseradminErrors(data.errors);
  220. }
  221. else if (data && data.url) {
  222. huePubSub.publish('open.link', data.url);
  223. $.jHueNotify.info("${ _('The users and groups were updated correctly.') }")
  224. $usersComponents.find(".sync-ldap").modal("hide");
  225. }
  226. },
  227. error: function(data) {
  228. $usersComponents.find('input[type="submit"]').removeAttr("disabled");
  229. }
  230. });
  231. % endif
  232. $usersComponents.find(".sync-ldap").modal("show");
  233. }
  234. });
  235. });
  236. $usersComponents.find(".select-all").click(function () {
  237. if ($(this).attr("checked")) {
  238. $(this).removeAttr("checked").removeClass("fa-check");;
  239. $usersComponents.find(".userCheck").removeClass("fa-check").removeAttr("checked");
  240. }
  241. else {
  242. $(this).attr("checked", "checked").addClass("fa-check");
  243. $usersComponents.find(".userCheck").addClass("fa-check").attr("checked", "checked");
  244. }
  245. toggleActions();
  246. });
  247. $(document).on('click', '#usersComponents .userCheck', function () {
  248. if ($(this).attr("checked")) {
  249. $(this).removeClass("fa-check").removeAttr("checked");
  250. }
  251. else {
  252. $(this).addClass("fa-check").attr("checked", "checked");
  253. }
  254. toggleActions();
  255. });
  256. function toggleActions() {
  257. if ($usersComponents.find(".userCheck[checked='checked']").length >= 1) {
  258. $usersComponents.find(".delete-user-btn").removeAttr("disabled");
  259. }
  260. else {
  261. $usersComponents.find(".delete-user-btn").attr("disabled", "disabled");
  262. }
  263. }
  264. $usersComponents.find(".delete-user-btn").click(function () {
  265. viewModel.chosenUsers.removeAll();
  266. $usersComponents.find(".hue-checkbox[checked='checked']").each(function (index) {
  267. viewModel.chosenUsers.push($(this).data("id"));
  268. });
  269. $usersComponents.find(".delete-user").modal("show");
  270. });
  271. $usersComponents.find("a[data-row-selector='true']").jHueRowSelector();
  272. });
  273. </script>
  274. ${layout.commons()}
  275. %if not is_embeddable:
  276. ${ commonfooter(request, messages) | n,unicode }
  277. %endif