From 8dc60c72d48dba910e97bb36d400031c5f0882d0 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 24 Oct 2016 10:28:17 -0400 Subject: [PATCH] NIFI-2884: - Adding support to selecting multiple users before updating a policy. NIFI-2533: - Only including a user/group in the search results if they are not currently selected. NIFI-2286: - Providing a tooltip for the add user and remove policy button. This closes #1155. Signed-off-by: Bryan Bende --- .../apache/nifi/web/api/TenantsResource.java | 23 +- .../partials/canvas/policy-management.jsp | 4 +- .../partials/canvas/search-users-dialog.jsp | 16 +- .../src/main/webapp/css/common-ui.css | 9 + .../src/main/webapp/css/flow-status.css | 7 - .../src/main/webapp/css/policy-management.css | 65 ++++- .../js/nf/canvas/nf-policy-management.js | 238 +++++++++++------- .../main/webapp/js/nf/users/nf-users-table.js | 5 + 8 files changed, 260 insertions(+), 107 deletions(-) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java index 8073e8a111..ab82aceb7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java @@ -38,7 +38,6 @@ import org.apache.nifi.web.api.dto.RevisionDTO; import org.apache.nifi.web.api.dto.TenantDTO; import org.apache.nifi.web.api.dto.UserDTO; import org.apache.nifi.web.api.dto.UserGroupDTO; -import org.apache.nifi.web.api.entity.ClusterSearchResultsEntity; import org.apache.nifi.web.api.entity.TenantEntity; import org.apache.nifi.web.api.entity.TenantsEntity; import org.apache.nifi.web.api.entity.UserEntity; @@ -166,6 +165,10 @@ public class TenantsResource extends ApplicationResource { throw new IllegalArgumentException("User ID cannot be specified."); } + if (StringUtils.isBlank(requestUserEntity.getComponent().getIdentity())) { + throw new IllegalArgumentException("User identity must be specified."); + } + if (isReplicateRequest()) { return replicate(HttpMethod.POST, requestUserEntity); } @@ -551,6 +554,10 @@ public class TenantsResource extends ApplicationResource { throw new IllegalArgumentException("User group ID cannot be specified."); } + if (StringUtils.isBlank(requestUserGroupEntity.getComponent().getIdentity())) { + throw new IllegalArgumentException("User group identity must be specified."); + } + if (isReplicateRequest()) { return replicate(HttpMethod.POST, requestUserGroupEntity); } @@ -864,19 +871,19 @@ public class TenantsResource extends ApplicationResource { // ------------ /** - * Searches the cluster for a node with a given address. + * Searches for a tenant with a given identity. * - * @param value Search value that will be matched against a node's address - * @return Nodes that match the specified criteria + * @param value Search value that will be matched against a user/group identity + * @return Tenants match the specified criteria */ @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) @Path("search-results") @ApiOperation( - value = "Searches the cluster for a node with the specified address", + value = "Searches for a tenant with the specified identity", notes = NON_GUARANTEED_ENDPOINT, - response = ClusterSearchResultsEntity.class, + response = TenantsEntity.class, authorizations = { @Authorization(value = "Read - /tenants", type = "") } @@ -892,7 +899,7 @@ public class TenantsResource extends ApplicationResource { ) public Response searchCluster( @ApiParam( - value = "Node address to search for.", + value = "Identity to search for.", required = true ) @QueryParam("q") @DefaultValue(StringUtils.EMPTY) String value) { @@ -925,6 +932,7 @@ public class TenantsResource extends ApplicationResource { final TenantEntity entity = new TenantEntity(); entity.setPermissions(userEntity.getPermissions()); + entity.setRevision(userEntity.getRevision()); entity.setId(userEntity.getId()); entity.setComponent(tenant); @@ -942,6 +950,7 @@ public class TenantsResource extends ApplicationResource { final TenantEntity entity = new TenantEntity(); entity.setPermissions(userGroupEntity.getPermissions()); + entity.setRevision(userGroupEntity.getRevision()); entity.setId(userGroupEntity.getId()); entity.setComponent(tenant); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp index b833c7c476..7cd08e0825 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp @@ -78,8 +78,8 @@
- - + +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/search-users-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/search-users-dialog.jsp index 03c62ca4a3..43be5c02ff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/search-users-dialog.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/search-users-dialog.jsp @@ -17,7 +17,21 @@ <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css index cf6a7a6558..0466df5b7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css @@ -536,6 +536,15 @@ div.context-menu-item-text { color: #262626; } +/* search */ + +li.search-no-matches { + padding: 4px; + font-weight: bold; + color: #aaa; + font-style: italic; +} + /* progress bars */ md-progress-linear > div { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-status.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-status.css index c13344db4a..186d9841a8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-status.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-status.css @@ -170,13 +170,6 @@ input.search-flow { line-height: 1.4em; } -#search-flow-results li.search-no-matches { - padding: 4px; - font-weight: bold; - color: #aaa; - font-style: italic; -} - div.search-glass-pane { position: absolute; background-image: url(../images/spacer.png); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css index 21cdc1ff48..a1a5f89c3b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css @@ -194,7 +194,7 @@ div.policy-selected-component-type { #search-users-dialog { width: 450px; - height: 250px; + height: 450px; } #search-users-results .ui-autocomplete { @@ -211,6 +211,69 @@ div.policy-selected-component-type { border-radius: 0; } +div.secure-port-setting { + margin-bottom: 15px; + width: 410px; +} + +li.search-users-header { + font-weight: bold; + padding-top: 4px; + padding-left: 4px; + padding-right: 4px; + height: 14px; +} + +div.search-users-match-header { + font-weight: normal; + margin-left: 10px; +} + +li.search-users-no-matches { + padding: 4px; + font-weight: bold; + color: #aaa; + font-style: italic; +} + +div.allowed-container { + width: 408px; + height: 100px; + border: 1px solid #aaa; + overflow-x: hidden; + overflow-y: scroll; +} + +ul.allowed { + list-style-type: none; +} + +ul.allowed li { + height: 16px; + width: 385px; + border-bottom: 1px solid #ddd; + color: #262626; + overflow: hidden; + margin: 2px; + padding: 2px; + line-height: 16px; + font-weight: normal; +} + +span.allowed-entity { + float: left; + width: 360px; +} + +div.remove-allowed-entity { + float: right; + width: 16px; + height: 16px; + cursor: pointer; + font-size: 14px; + line-height: 16px; +} + /* admin policy message */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js index c8e87f3049..d738b58813 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js @@ -50,85 +50,31 @@ nf.PolicyManagement = (function () { }, handler: { click: function () { - var tenantSearchTerm = $('#search-users-field').val(); + // add to table and update policy + var policyGrid = $('#policy-table').data('gridInstance'); + var policyData = policyGrid.getData(); - // create the search request - $.ajax({ - type: 'GET', - data: { - q: tenantSearchTerm - }, - dataType: 'json', - url: config.urls.searchTenants - }).done(function (response) { - var selectedUsers = $.map(response.users, function (user) { - return $.extend({ - type: 'user' - }, user); - }); - var selectedGroups = $.map(response.userGroups, function (userGroup) { - return $.extend({ - type: 'group' - }, userGroup); - }); + // begin the update + policyData.beginUpdate(); - var selectedUser = []; - selectedUser = selectedUser.concat(selectedUsers, selectedGroups); - - // add the user to the policy table - var addUser = function (user) { - // add to table and update policy - var policyGrid = $('#policy-table').data('gridInstance'); - var policyData = policyGrid.getData(); - - // begin the update - policyData.beginUpdate(); - - // remove the user - policyData.addItem(user); - - // end the update - policyData.endUpdate(); - - // update the policy - updatePolicy(); - }; - - // ensure the search found some results - if (!$.isArray(selectedUser) || selectedUser.length === 0) { - nf.Dialog.showOkDialog({ - headerText: 'User Search', - dialogContent: 'No users match \'' + nf.Common.escapeHtml(tenantSearchTerm) + '\'.' - }); - } else if (selectedUser.length > 1) { - var exactMatch = false; - - // look for an exact match - $.each(selectedUser, function (_, userEntity) { - if (userEntity.component.identity === tenantSearchTerm) { - addUser(userEntity); - exactMatch = true; - return false; - } - }); - - // if there is an exact match, use it - if (exactMatch) { - // close the dialog - $('#search-users-dialog').modal('hide'); - } else { - nf.Dialog.showOkDialog({ - headerText: 'User Search', - dialogContent: 'More than one user matches \'' + nf.Common.escapeHtml(tenantSearchTerm) + '\'.' - }); - } - } else if (selectedUser.length === 1) { - addUser(selectedUser[0]); - - // close the dialog - $('#search-users-dialog').modal('hide'); - } + // add all users/groups + $.each(getTenantsToAdd($('#allowed-users')), function (_, user) { + // remove the user + policyData.addItem(user); }); + $.each(getTenantsToAdd($('#allowed-groups')), function (_, group) { + // remove the user + policyData.addItem(group); + }); + + // end the update + policyData.endUpdate(); + + // update the policy + updatePolicy(); + + // close the dialog + $('#search-users-dialog').modal('hide'); } } }, @@ -150,11 +96,18 @@ nf.PolicyManagement = (function () { close: function () { // reset the search fields $('#search-users-field').userSearchAutocomplete('reset').val(''); - $('#selected-user-id').text(''); + + // clear the selected users/groups + $('#allowed-users, #allowed-groups').empty(); } } }); + // listen for removal requests + $(document).on('click', 'div.remove-allowed-entity', function () { + $(this).closest('li').remove(); + }); + // configure the user auto complete $.widget('nf.userSearchAutocomplete', $.ui.autocomplete, { reset: function () { @@ -169,18 +122,25 @@ nf.PolicyManagement = (function () { // results are normalized into a single element array var searchResults = items[0]; + var allowedGroups = getAllAllowedGroups(); + var allowedUsers = getAllAllowedUsers(); + var self = this; $.each(searchResults.userGroups, function (_, tenant) { - self._renderGroup(ul, { - label: tenant.component.identity, - value: tenant.component.identity - }); + // see if this match is not already selected + if ($.inArray(tenant.id, allowedGroups) === -1) { + self._renderGroup(ul, $.extend({ + type: 'group' + }, tenant)); + } }); $.each(searchResults.users, function (_, tenant) { - self._renderUser(ul, { - label: tenant.component.identity, - value: tenant.component.identity - }); + // see if this match is not already selected + if ($.inArray(tenant.id, allowedUsers) === -1) { + self._renderUser(ul, $.extend({ + type: 'user' + }, tenant)); + } }); // ensure there were some results @@ -190,14 +150,14 @@ nf.PolicyManagement = (function () { }, _resizeMenu: function () { var ul = this.menu.element; - ul.width($('#search-users-field').width() + 6); + ul.width($('#search-users-field').outerWidth() - 6); }, _renderUser: function (ul, match) { - var userContent = $('').text(match.label); + var userContent = $('').text(match.component.identity); return $('
  • ').data('ui-autocomplete-item', match).append(userContent).appendTo(ul); }, _renderGroup: function (ul, match) { - var groupLabel = $('').text(match.label); + var groupLabel = $('').text(match.component.identity); var groupContent = $('').append('
    ').append(groupLabel); return $('
  • ').data('ui-autocomplete-item', match).append(groupContent).appendTo(ul); } @@ -224,10 +184,110 @@ nf.PolicyManagement = (function () { }).done(function (searchResponse) { response(searchResponse); }); + }, + select: function (event, ui) { + addAllowedTenant(ui.item); + + // reset the search field + $(this).val(''); + + // stop event propagation + return false; } }); }; - + + /** + * Gets all allowed groups including those already in the policy and those selected while searching (not yet saved). + * + * @returns {Array} + */ + var getAllAllowedGroups = function () { + var policyGrid = $('#policy-table').data('gridInstance'); + var policyData = policyGrid.getData(); + + var userGroups = []; + + // consider existing groups in the policy table + var items = policyData.getItems(); + $.each(items, function (_, item) { + if (item.type === 'group') { + userGroups.push(item.id); + } + }); + + // also consider groups already selected in the search users dialog + $.each(getTenantsToAdd($('#allowed-groups')), function (_, group) { + userGroups.push(group.id); + }); + + return userGroups; + }; + + /** + * Gets the user groups that will be added upon applying the changes. + * + * @param {jQuery} container + * @returns {Array} + */ + var getTenantsToAdd = function (container) { + var tenants = []; + + // also consider groups already selected in the search users dialog + container.children('li').each(function (_, allowedTenant) { + var tenant = $(allowedTenant).data('tenant'); + if (nf.Common.isDefinedAndNotNull(tenant)) { + tenants.push(tenant); + } + }); + + return tenants; + }; + + /** + * Gets all allowed users including those already in the policy and those selected while searching (not yet saved). + * + * @returns {Array} + */ + var getAllAllowedUsers = function () { + var policyGrid = $('#policy-table').data('gridInstance'); + var policyData = policyGrid.getData(); + + var users = []; + + // consider existing users in the policy table + var items = policyData.getItems(); + $.each(items, function (_, item) { + if (item.type === 'user') { + users.push(item.id); + } + }); + + // also consider users already selected in the search users dialog + $.each(getTenantsToAdd($('#allowed-users')), function (_, user) { + users.push(user.id); + }); + + return users; + }; + + /** + * Added the specified tenant to the listing of users/groups which will be added when applied. + * + * @param allowedTenant user/group to add + */ + var addAllowedTenant = function (allowedTenant) { + var allowedTenants = allowedTenant.type === 'user' ? $('#allowed-users') : $('#allowed-groups'); + + // append the user + var tenant = $('').addClass('allowed-entity ellipsis').text(allowedTenant.component.identity).ellipsis(); + var tenantAction = $('
    ').addClass('remove-allowed-entity fa fa-trash'); + $('
  • ').data('tenant', allowedTenant).append(tenant).append(tenantAction).appendTo(allowedTenants); + }; + + /** + * Initializes the policy table. + */ var initPolicyTable = function () { $('#override-policy-dialog').modal({ headerText: 'Override Policy', diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js index 79267e059c..1656050aa0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js @@ -824,7 +824,12 @@ nf.UsersTable = (function () { $('#new-user-button').on('click', function () { buildUsersList(); buildGroupsList(); + + // show the dialog $('#user-dialog').modal('show'); + + // set the focus automatically, only when adding a new user + $('#user-identity-edit-dialog').focus(); }); $('#new-user-button').prop('disabled', false);