diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CreateActiveRequestEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CreateActiveRequestEntity.java new file mode 100644 index 0000000000..63a07a2287 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CreateActiveRequestEntity.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +package org.apache.nifi.web.api.entity; + +import io.swagger.annotations.ApiModelProperty; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "createActiveRequest") +public class CreateActiveRequestEntity extends Entity { + private String processGroupId; + + @ApiModelProperty("The Process Group ID that this active request will update") + public String getProcessGroupId() { + return processGroupId; + } + + public void setProcessGroupId(String processGroupId) { + this.processGroupId = processGroupId; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java index 6e61182b0f..245713e0ad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java @@ -17,23 +17,14 @@ package org.apache.nifi.web.api; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.ProcessGroupAuthorizable; import org.apache.nifi.authorization.RequestAction; @@ -65,6 +56,7 @@ import org.apache.nifi.web.api.dto.VersionControlInformationDTO; import org.apache.nifi.web.api.dto.VersionedFlowDTO; import org.apache.nifi.web.api.dto.VersionedFlowUpdateRequestDTO; import org.apache.nifi.web.api.entity.AffectedComponentEntity; +import org.apache.nifi.web.api.entity.CreateActiveRequestEntity; import org.apache.nifi.web.api.entity.ProcessGroupEntity; import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity; import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity; @@ -80,6 +72,21 @@ import org.apache.nifi.web.util.LifecycleManagementException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -95,13 +102,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; - @Path("/versions") @Api(value = "/versions", description = "Endpoint for managing version control for a flow") public class VersionsResource extends ApplicationResource { @@ -168,7 +168,7 @@ public class VersionsResource extends ApplicationResource { @POST @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("start-requests") + @Path("active-requests") @ApiOperation( value = "Creates a request so that a Process Group can be placed under Version Control or have its Version Control configuration changed. Creating this request will " + "prevent any other threads from simultaneously saving local changes to Version Control. It will not, however, actually save the local flow to the Flow Registry. A " @@ -182,20 +182,28 @@ public class VersionsResource extends ApplicationResource { @ApiResponse(code = 404, message = "The specified resource could not be found."), @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") }) - public Response createVersionControlRequest() throws InterruptedException { + public Response createVersionControlRequest( + @ApiParam(value = "The versioned flow details.", required = true) final CreateActiveRequestEntity requestEntity) throws InterruptedException { if (isReplicateRequest()) { return replicate(HttpMethod.POST); } + if (requestEntity.getProcessGroupId() == null) { + throw new IllegalArgumentException("The id of the process group that will be updated must be specified."); + } + + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + return withWriteLock( serviceFacade, - /* entity */ null, + requestEntity, lookup -> { - // TODO - pass in PG ID to authorize + final Authorizable processGroup = lookup.getProcessGroup(requestEntity.getProcessGroupId()).getAuthorizable(); + processGroup.authorize(authorizer, RequestAction.WRITE, user); }, /* verifier */ null, - requestEntity -> { + entity -> { final String requestId = generateUuid(); // We need to ensure that only a single Version Control Request can occur throughout the flow. @@ -204,7 +212,7 @@ public class VersionsResource extends ApplicationResource { // As a result, may could end up in a situation where we are creating flows in the registry that are never referenced. synchronized (activeRequestMonitor) { if (activeRequest == null || activeRequest.isExpired()) { - activeRequest = new ActiveRequest(requestId); + activeRequest = new ActiveRequest(requestId, user, entity.getProcessGroupId()); } else { throw new IllegalStateException("A request is already underway to place a Process Group in this NiFi instance under Version Control. " + "Only a single such request is allowed to occurred at a time. Please try the request again momentarily."); @@ -219,7 +227,7 @@ public class VersionsResource extends ApplicationResource { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("start-requests/{id}") + @Path("active-requests/{id}") @ApiOperation( value = "Updates the request with the given ID", response = VersionControlInformationEntity.class, @@ -235,7 +243,7 @@ public class VersionsResource extends ApplicationResource { @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") }) public Response updateVersionControlRequest(@ApiParam("The request ID.") @PathParam("id") final String requestId, - @ApiParam(value = "The controller service configuration details.", required = true) final VersionControlComponentMappingEntity requestEntity) { + @ApiParam(value = "The version control component mapping.", required = true) final VersionControlComponentMappingEntity requestEntity) { // Verify request final RevisionDTO revisionDto = requestEntity.getProcessGroupRevision(); @@ -287,21 +295,40 @@ public class VersionsResource extends ApplicationResource { throw new IllegalStateException("Version Control Request with ID " + requestId + " has already expired"); } + if (activeRequest.isUpdatePerformed()) { + throw new IllegalStateException("Version Control Request with ID " + requestId + " has already been performed"); + } + final String groupId = requestEntity.getVersionControlInformation().getGroupId(); + if (!activeRequest.getProcessGroupId().equals(groupId)) { + throw new IllegalStateException("Version Control Request with ID " + requestId + " was created for a different process group id"); + } + final Revision groupRevision = new Revision(revisionDto.getVersion(), revisionDto.getClientId(), groupId); return withWriteLock( serviceFacade, requestEntity, groupRevision, lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); - processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + if (!user.equals(activeRequest.getUser())) { + throw new AccessDeniedException("Only the user that creates the Version Control Request can use it."); + } }, null, (rev, mappingEntity) -> { + // set the version control information final VersionControlInformationEntity responseEntity = serviceFacade.setVersionControlInformation(rev, groupId, mappingEntity.getVersionControlInformation(), mappingEntity.getVersionControlComponentMapping()); + + // indicate that the active request has performed the update + activeRequest.updatePerformed(); + return generateOkResponse(responseEntity).build(); }); } @@ -311,10 +338,10 @@ public class VersionsResource extends ApplicationResource { @DELETE @Consumes(MediaType.WILDCARD) @Produces(MediaType.APPLICATION_JSON) - @Path("start-requests/{id}") + @Path("active-requests/{id}") @ApiOperation( value = "Deletes the Version Control Request with the given ID. This will allow other threads to save flows to the Flow Registry. See also the documentation " - + "for POSTing to /versions/start-requests for information regarding why this is done.", + + "for POSTing to /versions/active-requests for information regarding why this is done.", notes = NON_GUARANTEED_ENDPOINT) @ApiResponses(value = { @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), @@ -328,28 +355,36 @@ public class VersionsResource extends ApplicationResource { return replicate(HttpMethod.DELETE); } - return withWriteLock( - serviceFacade, - null, - lookup -> { - }, - null, - requestEntity -> { - synchronized (activeRequestMonitor) { - if (activeRequest == null) { - throw new IllegalStateException("No Version Control Request with ID " + requestId + " is currently active"); + synchronized (activeRequestMonitor) { + if (activeRequest == null) { + throw new IllegalStateException("No Version Control Request with ID " + requestId + " is currently active"); + } + + if (!requestId.equals(activeRequest.getRequestId())) { + throw new IllegalStateException("No Version Control Request with ID " + requestId + " is currently active"); + } + + return withWriteLock( + serviceFacade, + null, + lookup -> { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user == null) { + throw new AccessDeniedException("Unknown user."); } - if (!requestId.equals(activeRequest.getRequestId())) { - throw new IllegalStateException("No Version Control Request with ID " + requestId + " is currently active"); + if (!user.equals(activeRequest.getUser())) { + throw new AccessDeniedException("Only the user that creates the Version Control Request can use it."); } - + }, + null, + requestEntity -> { + // clear the active request activeRequest = null; - } - - return generateOkResponse().build(); - }); + return generateOkResponse().build(); + }); + } } @@ -407,7 +442,7 @@ public class VersionsResource extends ApplicationResource { if (isReplicateRequest()) { // We first have to obtain a "lock" on all nodes in the cluster so that multiple Version Control requests - // are not being made simultaneously. We do this by making a POST to /nifi-api/versions/start-requests. + // are not being made simultaneously. We do this by making a POST to /nifi-api/versions/active-requests. // The Response gives us back the Request ID. final URI requestUri; try { @@ -415,7 +450,7 @@ public class VersionsResource extends ApplicationResource { final String requestId = lockVersionControl(originalUri, groupId); requestUri = new URI(originalUri.getScheme(), originalUri.getUserInfo(), originalUri.getHost(), - originalUri.getPort(), "/nifi-api/versions/start-requests/" + requestId, null, originalUri.getFragment()); + originalUri.getPort(), "/nifi-api/versions/active-requests/" + requestId, null, originalUri.getFragment()); } catch (final URISyntaxException e) { throw new RuntimeException(e); } @@ -448,9 +483,12 @@ public class VersionsResource extends ApplicationResource { lookup -> { final ProcessGroupAuthorizable groupAuthorizable = lookup.getProcessGroup(groupId); final Authorizable processGroup = groupAuthorizable.getAuthorizable(); - processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); + + // require write to this group processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); + + // require read to this group and all descendants + authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); }, () -> { final VersionedFlowDTO versionedFlow = requestEntity.getVersionedFlow(); @@ -493,15 +531,19 @@ public class VersionsResource extends ApplicationResource { private String lockVersionControl(final URI originalUri, final String groupId) throws URISyntaxException { final URI createRequestUri = new URI(originalUri.getScheme(), originalUri.getUserInfo(), originalUri.getHost(), - originalUri.getPort(), "/nifi-api/versions/start-requests", null, originalUri.getFragment()); + originalUri.getPort(), "/nifi-api/versions/active-requests", null, originalUri.getFragment()); final NodeResponse clusterResponse; try { + // create an active request entity to indicate the group id + final CreateActiveRequestEntity activeRequestEntity = new CreateActiveRequestEntity(); + activeRequestEntity.setProcessGroupId(groupId); + if (getReplicationTarget() == ReplicationTarget.CLUSTER_NODES) { - clusterResponse = getRequestReplicator().replicate(HttpMethod.POST, createRequestUri, new MultivaluedHashMap<>(), Collections.emptyMap()).awaitMergedResponse(); + clusterResponse = getRequestReplicator().replicate(HttpMethod.POST, createRequestUri, activeRequestEntity, Collections.emptyMap()).awaitMergedResponse(); } else { clusterResponse = getRequestReplicator().forwardToCoordinator( - getClusterCoordinatorNode(), HttpMethod.POST, createRequestUri, new MultivaluedHashMap<>(), Collections.emptyMap()).awaitMergedResponse(); + getClusterCoordinatorNode(), HttpMethod.POST, createRequestUri, activeRequestEntity, Collections.emptyMap()).awaitMergedResponse(); } } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); @@ -625,7 +667,7 @@ public class VersionsResource extends ApplicationResource { }, (revision, groupEntity) -> { // disconnect from version control - final VersionControlInformationEntity entity = serviceFacade.deleteVersionControl(requestRevision, groupId); + final VersionControlInformationEntity entity = serviceFacade.deleteVersionControl(revision, groupId); // generate the response return generateOkResponse(entity).build(); @@ -716,8 +758,8 @@ public class VersionsResource extends ApplicationResource { versionControlInfoDto.setGroupId(groupId); versionControlInfoDto.setModified(false); versionControlInfoDto.setVersion(snapshotMetadata.getVersion()); - versionControlInfoDto.setRegistryId(requestEntity.getRegistryId()); - versionControlInfoDto.setRegistryName(serviceFacade.getFlowRegistryName(requestEntity.getRegistryId())); + versionControlInfoDto.setRegistryId(entity.getRegistryId()); + versionControlInfoDto.setRegistryName(serviceFacade.getFlowRegistryName(entity.getRegistryId())); final ProcessGroupEntity updatedGroup = serviceFacade.updateProcessGroup(rev, groupId, versionControlInfoDto, flowSnapshot, getIdGenerationSeed().orElse(null), false, entity.getUpdateDescendantVersionedFlows()); @@ -785,6 +827,7 @@ public class VersionsResource extends ApplicationResource { final NiFiUser user = NiFiUserUtils.getNiFiUser(); + // request manager will ensure that the current is the user that submitted this request final AsynchronousWebRequest asyncRequest = requestManager.getRequest(requestType, requestId, user); final VersionedFlowUpdateRequestDTO updateRequestDto = new VersionedFlowUpdateRequestDTO(); @@ -865,6 +908,7 @@ public class VersionsResource extends ApplicationResource { final NiFiUser user = NiFiUserUtils.getNiFiUser(); + // request manager will ensure that the current is the user that submitted this request final AsynchronousWebRequest asyncRequest = requestManager.removeRequest(requestType, requestId, user); if (asyncRequest == null) { throw new ResourceNotFoundException("Could not find request of type " + requestType + " with ID " + requestId); @@ -1021,11 +1065,8 @@ public class VersionsResource extends ApplicationResource { lookup -> { // Step 2: Verify READ and WRITE permissions for user, for every component. final ProcessGroupAuthorizable groupAuthorizable = lookup.getProcessGroup(groupId); - final Authorizable processGroup = groupAuthorizable.getAuthorizable(); - processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); - processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); - super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.WRITE, true, false, true, true); + authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); + authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.WRITE, true, false, true, true); final VersionedProcessGroup groupContents = flowSnapshot.getFlowContents(); final boolean containsRestrictedComponents = FlowRegistryUtils.containsRestrictedComponent(groupContents); @@ -1049,7 +1090,7 @@ public class VersionsResource extends ApplicationResource { final Consumer> updateTask = vcur -> { try { final VersionControlInformationEntity updatedVersionControlEntity = updateFlowVersion(groupId, componentLifecycle, exampleUri, - affectedComponents, user, replicateRequest, requestEntity, flowSnapshot, request, idGenerationSeed, true, true); + affectedComponents, user, replicateRequest, processGroupEntity, flowSnapshot, request, idGenerationSeed, true, true); vcur.markComplete(updatedVersionControlEntity); } catch (final LifecycleManagementException e) { @@ -1170,11 +1211,8 @@ public class VersionsResource extends ApplicationResource { lookup -> { // Step 2: Verify READ and WRITE permissions for user, for every component. final ProcessGroupAuthorizable groupAuthorizable = lookup.getProcessGroup(groupId); - final Authorizable processGroup = groupAuthorizable.getAuthorizable(); - processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); - processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); - super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.WRITE, true, false, true, true); + authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, true, false, true, true); + authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.WRITE, true, false, true, true); final VersionedProcessGroup groupContents = flowSnapshot.getFlowContents(); final boolean containsRestrictedComponents = FlowRegistryUtils.containsRestrictedComponent(groupContents); @@ -1217,7 +1255,7 @@ public class VersionsResource extends ApplicationResource { final Consumer> updateTask = vcur -> { try { final VersionControlInformationEntity updatedVersionControlEntity = updateFlowVersion(groupId, componentLifecycle, exampleUri, - affectedComponents, user, replicateRequest, requestEntity, flowSnapshot, request, idGenerationSeed, false, true); + affectedComponents, user, replicateRequest, processGroupEntity, flowSnapshot, request, idGenerationSeed, false, true); vcur.markComplete(updatedVersionControlEntity); } catch (final LifecycleManagementException e) { @@ -1471,11 +1509,17 @@ public class VersionsResource extends ApplicationResource { private static final long MAX_REQUEST_LOCK_NANOS = TimeUnit.MINUTES.toNanos(1L); private final String requestId; + private final NiFiUser user; + private final String processGroupId; private final long creationNanos = System.nanoTime(); private final long expirationTime = creationNanos + MAX_REQUEST_LOCK_NANOS; - private ActiveRequest(final String requestId) { + private boolean updatePerformed = false; + + private ActiveRequest(final String requestId, final NiFiUser user, final String processGroupId) { this.requestId = requestId; + this.user = user; + this.processGroupId = processGroupId; } public boolean isExpired() { @@ -1485,5 +1529,21 @@ public class VersionsResource extends ApplicationResource { public String getRequestId() { return requestId; } + + public NiFiUser getUser() { + return user; + } + + public String getProcessGroupId() { + return processGroupId; + } + + public void updatePerformed() { + updatePerformed = true; + } + + public boolean isUpdatePerformed() { + return updatePerformed; + } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp index 4700b8b714..f122076398 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp @@ -21,8 +21,7 @@ highlight-crumb-id="appCtrl.nf.CanvasUtils.getGroupId();" separator-func="appCtrl.nf.Common.isDefinedAndNotNull" is-tracking="appCtrl.serviceProvider.breadcrumbsCtrl.isTracking" - is-current="appCtrl.serviceProvider.breadcrumbsCtrl.isCurrent" - is-modified="appCtrl.serviceProvider.breadcrumbsCtrl.isModified" + get-version-control-class="appCtrl.serviceProvider.breadcrumbsCtrl.getVersionControlClass" get-version-control-tooltip="appCtrl.serviceProvider.breadcrumbsCtrl.getVersionControlTooltip">
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css index d10761a9b2..2f8dd7b9bb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css @@ -161,7 +161,7 @@ div.context-menu-provenance { text-shadow: 0 0 4px rgba(255,255,255,1); } -.locally-modified { +.locally-modified, .sync-failure { float: left; color: #747474 !important; fill: #747474 !important; @@ -169,15 +169,7 @@ div.context-menu-provenance { text-shadow: 0 0 4px rgba(255,255,255,1); } -.stale { - float: left; - color: #c7685d !important; - fill: #c7685d !important; - margin-top: 0px !important; - text-shadow: 0 0 4px rgba(255,255,255,1); -} - -.locally-modified-and-stale { +.stale, .locally-modified-and-stale { float: left; color: #c7685d !important; fill: #c7685d !important; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js index 0b71e3caea..fc5599d182 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js @@ -107,31 +107,28 @@ }, /** - * Returns whether the specified version control information is current. + * Returns the class string to use for the version control of the specified breadcrumb. * * @param breadcrumbEntity - * @returns {boolean} + * @returns {string} */ - isCurrent: function (breadcrumbEntity) { + getVersionControlClass: function (breadcrumbEntity) { if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation)) { - return breadcrumbEntity.breadcrumb.versionControlInformation.current === true; + var vciState = breadcrumbEntity.breadcrumb.versionControlInformation.state; + if (vciState === 'SYNC_FAILURE') { + return 'breadcrumb-version-control-gray fa fa-question' + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { + return 'breadcrumb-version-control-red fa fa-exclamation-circle'; + } else if (vciState === 'STALE') { + return 'breadcrumb-version-control-red fa fa-arrow-circle-up'; + } else if (vciState === 'LOCALLY_MODIFIED') { + return 'breadcrumb-version-control-gray fa fa-asterisk'; + } else { + return 'breadcrumb-version-control-green fa fa-check'; + } + } else { + return ''; } - - return false; - }, - - /** - * Returns whether the specified version control information is current. - * - * @param versionControlInformation - * @returns {boolean} - */ - isModified: function (breadcrumbEntity) { - if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation)) { - return breadcrumbEntity.breadcrumb.versionControlInformation.modified === true; - } - - return false; }, /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/directives/nf-ng-breadcrumbs-directive.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/directives/nf-ng-breadcrumbs-directive.js index 1a549d738b..b7a8f26bc3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/directives/nf-ng-breadcrumbs-directive.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/directives/nf-ng-breadcrumbs-directive.js @@ -42,8 +42,7 @@ 'highlightCrumbId': '=', 'separatorFunc': '=', 'isTracking': '=', - 'isCurrent': '=', - 'isModified': '=', + 'getVersionControlClass': '=', 'getVersionControlTooltip': '=' }, link: function (scope, element, attrs) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js index 616e1512bd..0c21838091 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js @@ -463,7 +463,7 @@ } // check the selection for version control information - return versionControlInformation.current === true && versionControlInformation.modified === true; + return versionControlInformation.state === 'LOCALLY_MODIFIED'; }; /** @@ -502,7 +502,7 @@ } // check the selection for version control information - return versionControlInformation.modified === true; + return versionControlInformation.state === 'LOCALLY_MODIFIED' || versionControlInformation.state === 'LOCALLY_MODIFIED_AND_STALE'; }; /** @@ -541,7 +541,7 @@ } // check the selection for version control information - return versionControlInformation.modified === false; + return versionControlInformation.state !== 'LOCALLY_MODIFIED' && versionControlInformation.state !== 'LOCALLY_MODIFIED_AND_STALE'; }; /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js index 03aed70487..9ef210ee35 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js @@ -227,10 +227,11 @@ * * @param registryIdentifier * @param bucketCombo + * @param flowCombo * @param selectBucket * @returns {*} */ - var loadBuckets = function (registryIdentifier, bucketCombo, selectBucket) { + var loadBuckets = function (registryIdentifier, bucketCombo, flowCombo, selectBucket) { return $.ajax({ type: 'GET', url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets', @@ -258,6 +259,14 @@ optionClass: 'unset', disabled: true }); + flowCombo.combo('destroy').combo({ + options: [{ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }] + }); } // load the buckets @@ -287,6 +296,14 @@ disabled: true }] }); + flowCombo.combo('destroy').combo({ + options: [{ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }] + }); dialog.modal('refreshButtons'); }; @@ -316,7 +333,7 @@ clearFlowVersionsGrid(); } - loadBuckets(selectedOption.value, bucketCombo, selectBucket).fail(function () { + loadBuckets(selectedOption.value, bucketCombo, flowCombo, selectBucket).fail(function () { showNoBucketsAvailable(); }); } @@ -1439,7 +1456,7 @@ nfDialog.showOkDialog({ headerText: 'Revert Local Changes', - dialogContent: nfCommon.escapeHtml(changeRequest.failureReason) + dialogContent: nfCommon.escapeHtml(revertRequest.failureReason) }); } else { // update the percent complete diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js index 69db138143..111636b7e5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js @@ -397,7 +397,7 @@ 'class': 'process-group-disabled-count process-group-contents-count' }); - // current icon + // up to date icon details.append('text') .attr({ 'x': 10, @@ -409,7 +409,7 @@ }) .text('\uf00c'); - // current count + // up to date count details.append('text') .attr({ 'y': function () { @@ -418,7 +418,7 @@ 'class': 'process-group-up-to-date-count process-group-contents-count' }); - // modified icon + // locally modified icon details.append('text') .attr({ 'y': function () { @@ -429,7 +429,7 @@ }) .text('\uf069'); - // modified count + // locally modified count details.append('text') .attr({ 'y': function () { @@ -438,7 +438,7 @@ 'class': 'process-group-locally-modified-count process-group-contents-count' }); - // not current icon + // stale icon details.append('text') .attr({ 'y': function () { @@ -449,7 +449,7 @@ }) .text('\uf0aa'); - // not current count + // stale count details.append('text') .attr({ 'y': function () { @@ -458,7 +458,7 @@ 'class': 'process-group-stale-count process-group-contents-count' }); - // modified and not current icon + // locally modified and stale icon details.append('text') .attr({ 'y': function () { @@ -469,7 +469,7 @@ }) .text('\uf06a'); - // modified and not current count + // locally modified and stale count details.append('text') .attr({ 'y': function () { @@ -478,6 +478,26 @@ 'class': 'process-group-locally-modified-and-stale-count process-group-contents-count' }); + // sync failure icon + details.append('text') + .attr({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-sync-failure process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf128'); + + // sync failure count + details.append('text') + .attr({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-sync-failure-count process-group-contents-count' + }); + // ---------------- // stats background // ---------------- @@ -940,13 +960,14 @@ 'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden', 'fill': function () { if (isUnderVersionControl(processGroupData)) { - var modified = processGroupData.component.versionControlInformation.modified; - var current = processGroupData.component.versionControlInformation.current; - if (modified === true && current === false) { + var vciState = processGroupData.component.versionControlInformation.state; + if (vciState === 'SYNC_FAILURE') { + return '#666666'; + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { return '#BA554A'; - } else if (current === false) { + } else if (vciState === 'STALE') { return '#BA554A'; - } else if (modified === true) { + } else if (vciState === 'LOCALLY_MODIFIED') { return '#666666'; } else { return '#1A9964'; @@ -958,13 +979,14 @@ }) .text(function () { if (isUnderVersionControl(processGroupData)) { - var modified = processGroupData.component.versionControlInformation.modified; - var current = processGroupData.component.versionControlInformation.current; - if (modified === true && current === false) { + var vciState = processGroupData.component.versionControlInformation.state; + if (vciState === 'SYNC_FAILURE') { + return '\uf128' + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { return '\uf06a'; - } else if (current === false) { + } else if (vciState === 'STALE') { return '\uf0aa'; - } else if (modified === true) { + } else if (vciState === 'LOCALLY_MODIFIED') { return '\uf069'; } else { return '\uf00c'; @@ -1081,8 +1103,8 @@ }); var upToDateCount = details.select('text.process-group-up-to-date-count') .attr('x', function () { - var currentCountX = parseInt(upToDate.attr('x'), 10); - return currentCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + var updateToDateCountX = parseInt(upToDate.attr('x'), 10); + return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; }) .text(function (d) { return d.component.upToDateCount; @@ -1097,13 +1119,13 @@ return d.component.locallyModifiedCount === 0; }) .attr('x', function () { - var currentX = parseInt(upToDateCount.attr('x'), 10); - return currentX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER; + var upToDateX = parseInt(upToDateCount.attr('x'), 10); + return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER; }); var locallyModifiedCount = details.select('text.process-group-locally-modified-count') .attr('x', function () { - var modifiedCountX = parseInt(locallyModified.attr('x'), 10); - return modifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10); + return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; }) .text(function (d) { return d.component.locallyModifiedCount; @@ -1118,13 +1140,13 @@ return d.component.staleCount === 0; }) .attr('x', function () { - var modifiedX = parseInt(locallyModifiedCount.attr('x'), 10); - return modifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER; + var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10); + return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER; }); var staleCount = details.select('text.process-group-stale-count') .attr('x', function () { - var notCurrentCountX = parseInt(stale.attr('x'), 10); - return notCurrentCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + var staleCountX = parseInt(stale.attr('x'), 10); + return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; }) .text(function (d) { return d.component.staleCount; @@ -1139,17 +1161,38 @@ return d.component.locallyModifiedAndStaleCount === 0; }) .attr('x', function () { - var runningX = parseInt(staleCount.attr('x'), 10); - return runningX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER; + var staleX = parseInt(staleCount.attr('x'), 10); + return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER; }); - details.select('text.process-group-locally-modified-and-stale-count') + var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count') .attr('x', function () { - var modifiedAndNotCurrentCountX = parseInt(locallyModifiedAndStale.attr('x'), 10); - return modifiedAndNotCurrentCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10); + return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; }) .text(function (d) { return d.component.locallyModifiedAndStaleCount; }); + + // update sync failure + var syncFailure = details.select('text.process-group-sync-failure') + .classed('sync-failure', function (d) { + return d.component.syncFailureCount > 0; + }) + .classed('zero', function (d) { + return d.component.syncFailureCount === 0; + }) + .attr('x', function () { + var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10); + return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2; + }); + details.select('text.process-group-sync-failure-count') + .attr('x', function () { + var syncFailureCountX = parseInt(syncFailure.attr('x'), 10); + return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.component.syncFailureCount; + }); } else { // update version control information processGroup.select('text.version-control').style('visibility', false).text(''); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js index b3671a71c2..a1a3fc69d3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js @@ -425,17 +425,7 @@ * @param versionControlInformation */ getVersionControlTooltip: function (versionControlInformation) { - var modified = versionControlInformation.modified; - var current = versionControlInformation.current; - if (modified === true && current === false) { - return 'Local changes have been made and a newer version of this flow is available'; - } else if (current === false) { - return 'A newer version of this flow is available'; - } else if (modified === true) { - return 'Local changes have been made'; - } else { - return 'Flow version is current'; - } + return versionControlInformation.stateExplanation; }, /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html index 3e5d87b7ae..a69de46eae 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/views/nf-ng-breadcrumbs-directive-view.html @@ -22,14 +22,7 @@ limitations under the License. » - - - - +