diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentStateDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentStateDTO.java index d5857683d1..9036d53776 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentStateDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentStateDTO.java @@ -27,6 +27,7 @@ import javax.xml.bind.annotation.XmlType; public class ComponentStateDTO { private String componentId; + private String stateDescription; private StateMapDTO clusterState; private StateMapDTO localState; @@ -44,6 +45,20 @@ public class ComponentStateDTO { this.componentId = componentId; } + /** + * @return Description of the state this component persists. + */ + @ApiModelProperty( + value = "Description of the state this component persists." + ) + public String getStateDescription() { + return stateDescription; + } + + public void setStateDescription(String stateDescription) { + this.stateDescription = stateDescription; + } + /** * @return The cluster state for this component, or null if this NiFi is a standalone instance */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java index 659be91389..5d51698375 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java @@ -33,6 +33,7 @@ public class ControllerServiceDTO extends NiFiComponentDTO { private String comments; private String availability; private String state; + private Boolean persistsState; private Map properties; private Map descriptors; @@ -101,6 +102,20 @@ public class ControllerServiceDTO extends NiFiComponentDTO { this.availability = availability; } + /** + * @return whether this controller service persists state + */ + @ApiModelProperty( + value = "Whether the controller service persists state." + ) + public Boolean getPersistsState() { + return persistsState; + } + + public void setPersistsState(Boolean persistsState) { + this.persistsState = persistsState; + } + /** * @return The state of this controller service. Possible values are ENABLED, ENABLING, DISABLED, DISABLING */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java index 0e4ddde3f7..b0b9daae59 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java @@ -37,6 +37,7 @@ public class ProcessorDTO extends NiFiComponentDTO { private Boolean supportsParallelProcessing; private Boolean supportsEventDriven; private Boolean supportsBatching; + private Boolean persistsState; private String inputRequirement; private ProcessorConfigDTO config; @@ -122,6 +123,20 @@ public class ProcessorDTO extends NiFiComponentDTO { this.supportsParallelProcessing = supportsParallelProcessing; } + /** + * @return whether this processor persists state + */ + @ApiModelProperty( + value = "Whether the processor persists state." + ) + public Boolean getPersistsState() { + return persistsState; + } + + public void setPersistsState(Boolean persistsState) { + this.persistsState = persistsState; + } + /** * @return the input requirement of this processor */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java index b8268293f7..182535d915 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java @@ -33,6 +33,7 @@ public class ReportingTaskDTO extends NiFiComponentDTO { private String state; private String availability; private String comments; + private Boolean persistsState; private String schedulingPeriod; private String schedulingStrategy; @@ -105,6 +106,20 @@ public class ReportingTaskDTO extends NiFiComponentDTO { this.schedulingPeriod = schedulingPeriod; } + /** + * @return whether this reporting task persists state + */ + @ApiModelProperty( + value = "Whether the reporting task persists state." + ) + public Boolean getPersistsState() { + return persistsState; + } + + public void setPersistsState(Boolean persistsState) { + this.persistsState = persistsState; + } + /** * @return current scheduling state of the reporting task */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 41318a1403..4f6f58a124 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -18,6 +18,7 @@ package org.apache.nifi.web; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.lf5.util.Resource; import org.apache.nifi.action.Action; import org.apache.nifi.action.Component; import org.apache.nifi.action.FlowChangeAction; @@ -2168,23 +2169,32 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { @Override public ComponentStateDTO getProcessorState(String groupId, String processorId) { - final StateMap clusterState = processorDAO.getState(groupId, processorId, Scope.CLUSTER); + final StateMap clusterState = isClustered() ? processorDAO.getState(groupId, processorId, Scope.CLUSTER) : null; final StateMap localState = processorDAO.getState(groupId, processorId, Scope.LOCAL); - return dtoFactory.createComponentStateDTO(processorId, localState, clusterState); + + // processor will be non null as it was already found when getting the state + final ProcessorNode processor = processorDAO.getProcessor(groupId, processorId); + return dtoFactory.createComponentStateDTO(processorId, processor.getProcessor().getClass(), localState, clusterState); } @Override public ComponentStateDTO getControllerServiceState(String controllerServiceId) { - final StateMap clusterState = controllerServiceDAO.getState(controllerServiceId, Scope.CLUSTER); + final StateMap clusterState = isClustered() ? controllerServiceDAO.getState(controllerServiceId, Scope.CLUSTER) : null; final StateMap localState = controllerServiceDAO.getState(controllerServiceId, Scope.LOCAL); - return dtoFactory.createComponentStateDTO(controllerServiceId, localState, clusterState); + + // controller service will be non null as it was already found when getting the state + final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId); + return dtoFactory.createComponentStateDTO(controllerServiceId, controllerService.getControllerServiceImplementation().getClass(), localState, clusterState); } @Override public ComponentStateDTO getReportingTaskState(String reportingTaskId) { - final StateMap clusterState = reportingTaskDAO.getState(reportingTaskId, Scope.CLUSTER); + final StateMap clusterState = isClustered() ? reportingTaskDAO.getState(reportingTaskId, Scope.CLUSTER) : null; final StateMap localState = reportingTaskDAO.getState(reportingTaskId, Scope.LOCAL); - return dtoFactory.createComponentStateDTO(reportingTaskId, localState, clusterState); + + // reporting task will be non null as it was already found when getting the state + final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId); + return dtoFactory.createComponentStateDTO(reportingTaskId, reportingTask.getReportingTask().getClass(), localState, clusterState); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index e2be595150..6ed39a2d0d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -32,6 +32,7 @@ import org.apache.nifi.action.details.FlowChangeMoveDetails; import org.apache.nifi.action.details.FlowChangePurgeDetails; import org.apache.nifi.action.details.MoveDetails; import org.apache.nifi.action.details.PurgeDetails; +import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.authorization.Authority; @@ -279,14 +280,30 @@ public final class DtoFactory { * @param clusterState cluster state * @return dto */ - public ComponentStateDTO createComponentStateDTO(final String componentId, final StateMap localState, final StateMap clusterState) { + public ComponentStateDTO createComponentStateDTO(final String componentId, final Class componentClass, final StateMap localState, final StateMap clusterState) { final ComponentStateDTO dto = new ComponentStateDTO(); dto.setComponentId(componentId); + dto.setStateDescription(getStateDescription(componentClass)); dto.setLocalState(createStateMapDTO(Scope.LOCAL, localState)); dto.setClusterState(createStateMapDTO(Scope.CLUSTER, clusterState)); return dto; } + /** + * Gets the description of the state this component persists. + * + * @param componentClass the component class + * @return state description + */ + private String getStateDescription(final Class componentClass) { + final Stateful capabilityDesc = componentClass.getAnnotation(Stateful.class); + if (capabilityDesc != null) { + return capabilityDesc.description(); + } else { + return null; + } + } + /** * Creates a StateMapDTO for the given scope and state map. * @@ -295,6 +312,10 @@ public final class DtoFactory { * @return dto */ public StateMapDTO createStateMapDTO(final Scope scope, final StateMap stateMap) { + if (stateMap == null) { + return null; + } + final StateMapDTO dto = new StateMapDTO(); dto.setScope(scope.toString()); @@ -1066,6 +1087,7 @@ public final class DtoFactory { dto.setActiveThreadCount(reportingTaskNode.getActiveThreadCount()); dto.setAnnotationData(reportingTaskNode.getAnnotationData()); dto.setComments(reportingTaskNode.getComments()); + dto.setPersistsState(reportingTaskNode.getReportingTask().getClass().isAnnotationPresent(Stateful.class)); final Map defaultSchedulingPeriod = new HashMap<>(); defaultSchedulingPeriod.put(SchedulingStrategy.TIMER_DRIVEN.name(), SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod()); @@ -1133,6 +1155,7 @@ public final class DtoFactory { dto.setState(controllerServiceNode.getState().name()); dto.setAnnotationData(controllerServiceNode.getAnnotationData()); dto.setComments(controllerServiceNode.getComments()); + dto.setPersistsState(controllerServiceNode.getControllerServiceImplementation().getClass().isAnnotationPresent(Stateful.class)); // sort a copy of the properties final Map sortedProperties = new TreeMap<>(new Comparator() { @@ -1610,6 +1633,7 @@ public final class DtoFactory { dto.setStyle(node.getStyle()); dto.setParentGroupId(node.getProcessGroup().getIdentifier()); dto.setInputRequirement(node.getInputRequirement().name()); + dto.setPersistsState(node.getProcessor().getClass().isAnnotationPresent(Stateful.class)); dto.setType(node.getProcessor().getClass().getCanonicalName()); dto.setName(node.getName()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml index 8e871316c4..2a8d7eab96 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml @@ -281,6 +281,7 @@ ${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js ${staging.dir}/js/nf/canvas/nf-custom-ui.js ${staging.dir}/js/nf/canvas/nf-queue-listing.js + ${staging.dir}/js/nf/canvas/nf-component-state.js ${staging.dir}/js/nf/canvas/nf-controller-service.js ${staging.dir}/js/nf/canvas/nf-reporting-task.js ${staging.dir}/js/nf/canvas/nf-processor-configuration.js diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties index 43e60f4804..b239dd2d84 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties @@ -22,6 +22,7 @@ nf.canvas.script.tags=\n\ \n\ \n\ +\n\ \n\ \n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp index 5a9eab7421..287a58c481 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp @@ -122,6 +122,7 @@ +
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/component-state-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/component-state-dialog.jsp new file mode 100644 index 0000000000..725234f92d --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/component-state-dialog.jsp @@ -0,0 +1,45 @@ +<%-- + 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. +--%> +<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> +
+
+
+
Name
+
+ +
+
+
+
Description
+
+ +
+
+
+
+ +
+
+ Displaying  of  +
+
+
+ +
+
\ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css index 2df31d6f0c..1a4eb66142 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css @@ -22,6 +22,7 @@ @import url(queue-listing.css); @import url(remote-process-group-configuration.css); @import url(controller-service.css); +@import url(component-state.css); @import url(reporting-task.css); @import url(port-configuration.css); @import url(port-details.css); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/component-state.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/component-state.css new file mode 100644 index 0000000000..35efca3546 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/component-state.css @@ -0,0 +1,88 @@ +/* + * 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. + */ + +/* + Component state +*/ + +#component-state-dialog { + position: absolute; + overflow: hidden; + width: 600px; + height: 500px; + font-size: 10px; + z-index: 1301; + display: none; +} + +#component-state-description { + height: 50px; +} + +/* + Component state filter +*/ + +#component-state-filter-controls { + float: right; + margin-top: 10px; + margin-right: 2px; + margin-bottom: 7px; +} + +#component-state-filter-status { + font-size: 9px; + font-weight: bold; + color: #9f6000; + clear: left; + line-height: normal; + margin-left: 5px; +} + +#component-state-filter { + padding: 3px 0px 1px 3px; + font-size: 12px; + height: 18px; + line-height: 20px; + width: 173px; + border: 1px solid #ccc; + float: left; +} + +/* + Component state table +*/ + +#component-state-table { + width: 578px; + height: 235px; + border: 1px solid #666; +} + +/* + Clear +*/ + +#clear-link-container { + margin-top: 16px; +} + +#clear-link.disabled { + color: #bbb; + font-style: italic; + text-decoration: none !important; +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconViewState.png b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconViewState.png new file mode 100644 index 0000000000..c9ffb383ac Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconViewState.png differ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 2f1bbd323c..d003fa7b74 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -1063,6 +1063,23 @@ nf.Actions = (function () { nf.QueueListing.listQueue(connection); }, + /** + * Views the state for the specified processor. + * + * @param {selection} selection + */ + viewState: function (selection) { + if (selection.size() !== 1 || !nf.CanvasUtils.isProcessor(selection)) { + return; + } + + // get the processor data + var processor = selection.datum(); + + // view the state for the selected processor + nf.ComponentState.showState(processor.component, nf.CanvasUtils.supportsModification(selection)); + }, + /** * Opens the fill color dialog for the component in the specified selection. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js index 005f7e3f31..fb002bea38 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js @@ -1151,6 +1151,7 @@ nf.Canvas = (function () { nf.Settings.init(); nf.Actions.init(); nf.QueueListing.init(); + nf.ComponentState.init(); // initialize the component behaviors nf.Draggable.init(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js new file mode 100644 index 0000000000..d4fb63c59c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-component-state.js @@ -0,0 +1,358 @@ +/* + * 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. + */ + +/* global nf */ + +/** + * Views state for a given component. + */ +nf.ComponentState = (function () { + + var config = { + filterText: 'Filter', + styles: { + filterList: 'filter-list' + } + }; + + /** + * Filters the component state table. + */ + var applyFilter = function () { + // get the dataview + var componentStateTable = $('#component-state-table').data('gridInstance'); + + // ensure the grid has been initialized + if (nf.Common.isDefinedAndNotNull(componentStateTable)) { + var componentStateData = componentStateTable.getData(); + + // update the search criteria + componentStateData.setFilterArgs({ + searchString: getFilterText() + }); + componentStateData.refresh(); + } + }; + + /** + * Determines if the item matches the filter. + * + * @param {object} item The item to filter + * @param {object} args The filter criteria + * @returns {boolean} Whether the item matches the filter + */ + var filter = function (item, args) { + if (args.searchString === '') { + return true; + } + + try { + // perform the row filtering + var filterExp = new RegExp(args.searchString, 'i'); + } catch (e) { + // invalid regex + return false; + } + + // determine if the item matches the filter + var matchesKey = item['key'].search(filterExp) >= 0; + var matchesValue = item['value'].search(filterExp) >= 0; + + // conditionally consider the scope + var matchesScope = false; + if (nf.Common.isDefinedAndNotNull(item['scope'])) { + matchesScope = item['scope'].search(filterExp) >= 0; + } + + return matchesKey || matchesValue || matchesScope; + }; + + /** + * Sorts the specified data using the specified sort details. + * + * @param {object} sortDetails + * @param {object} data + */ + var sort = function (sortDetails, data) { + // defines a function for sorting + var comparer = function (a, b) { + var aString = nf.Common.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : ''; + var bString = nf.Common.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : ''; + return aString === bString ? 0 : aString > bString ? 1 : -1; + }; + + // perform the sort + data.sort(comparer, sortDetails.sortAsc); + }; + + /** + * Get the text out of the filter field. If the filter field doesn't + * have any text it will contain the text 'filter list' so this method + * accounts for that. + */ + var getFilterText = function () { + var filterText = ''; + var filterField = $('#component-state-filter'); + if (!filterField.hasClass(config.styles.filterList)) { + filterText = filterField.val(); + } + return filterText; + }; + + /** + * Clears the component state table. + */ + var clearTable = function () { + var componentStateGrid = $('#component-state-table').data('gridInstance'); + var componentStateData = componentStateGrid.getData(); + componentStateData.setItems([]); + }; + + /** + * Loads the table with the component state. + * + * @param {object} componentState + */ + var loadComponentState = function (localState, clusterState) { + var count = 0; + + var componentStateGrid = $('#component-state-table').data('gridInstance'); + var componentStateData = componentStateGrid.getData(); + + // begin the update + componentStateData.beginUpdate(); + + // local state + if (nf.Common.isDefinedAndNotNull(localState)) { + $.each(localState.state, function (i, stateEntry) { + componentStateData.addItem($.extend({ + id: count++, + scope: stateEntry.nodeAddress + }, stateEntry)); + }); + } + + if (nf.Common.isDefinedAndNotNull(clusterState)) { + $.each(clusterState.state, function (i, stateEntry) { + componentStateData.addItem($.extend({ + id: count++, + scope: 'Cluster' + }, stateEntry)); + }); + } + + // complete the update + componentStateData.endUpdate(); + + // update the total number of state entries + $('#total-component-state-entries').text(count); + }; + + /** + * Reset the dialog. + */ + var resetDialog = function () { + // clear the fields + $('#component-state-name').text(''); + $('#component-state-description').text(''); + $('#total-component-state-entries').text(''); + + // clear any filter strings + $('#component-state-filter').addClass(config.styles.filterList).val(config.filterText); + + // reset clear link + $('#clear-link').removeClass('disabled').attr('title', ''); + + // clear the table + clearTable(); + + // clear the component + $('#component-state-table').removeData('component'); + }; + + return { + init: function () { + // intialize the component state filter + $('#component-state-filter').on('focus', function () { + if ($(this).hasClass(config.styles.filterList)) { + $(this).removeClass(config.styles.filterList).val(''); + } + }).on('blur', function () { + if ($(this).val() === '') { + $(this).addClass(config.styles.filterList).val(config.filterText); + } + }).on('keyup', function () { + applyFilter(); + }).addClass(config.styles.filterList).val(config.filterText); + + // initialize the processor configuration dialog + $('#component-state-dialog').modal({ + headerText: 'Component State', + overlayBackground: true, + buttons: [{ + buttonText: 'Ok', + handler: { + click: function () { + $(this).modal('hide'); + } + } + }], + handler: { + close: function () { + resetDialog(); + } + } + }).draggable({ + containment: 'parent', + handle: '.dialog-header' + }); + + // clear state link + $('#clear-link').on('click', function () { + if ($(this).hasClass('disabled') === false) { + // clear the table + clearTable(); + + // clear the state + var revision = nf.Client.getRevision(); + var component = $('#component-state-table').data('component'); + $.ajax({ + type: 'POST', + url: component.uri + '/state/clear-requests', + data: { + version: revision.version, + clientId: revision.clientId + }, + dataType: 'json' + }).done(function (response) { + // update the revision + nf.Client.setRevision(response.revision); + + // reload the table with no state + loadComponentState() + }).fail(nf.Common.handleAjaxError); + } + }); + + // initialize the queue listing table + var componentStateColumns = [ + {id: 'key', field: 'key', name: 'Key', sortable: true, resizable: true}, + {id: 'value', field: 'value', name: 'Value', sortable: true, resizable: true} + ]; + + // conditionally show the cluster node identifier + if (nf.Canvas.isClustered()) { + componentStateColumns.push({id: 'scope', field: 'scope', name: 'Scope', sortable: true, resizable: true, formatter: scopeFormatter}); + } + + var componentStateOptions = { + forceFitColumns: true, + enableTextSelectionOnCells: true, + enableCellNavigation: false, + enableColumnReorder: false, + autoEdit: false + }; + + // initialize the dataview + var componentStateData = new Slick.Data.DataView({ + inlineFilters: false + }); + componentStateData.setItems([]); + componentStateData.setFilterArgs({ + searchString: '', + property: 'key' + }); + componentStateData.setFilter(filter); + + // initialize the sort + sort({ + columnId: 'key', + sortAsc: true + }, componentStateData); + + // initialize the grid + var componentStateGrid = new Slick.Grid('#component-state-table', componentStateData, componentStateColumns, componentStateOptions); + componentStateGrid.setSelectionModel(new Slick.RowSelectionModel()); + componentStateGrid.registerPlugin(new Slick.AutoTooltips()); + componentStateGrid.setSortColumn('key', true); + componentStateGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.field, + sortAsc: args.sortAsc + }, componentStateData); + }); + + // wire up the dataview to the grid + componentStateData.onRowCountChanged.subscribe(function (e, args) { + componentStateGrid.updateRowCount(); + componentStateGrid.render(); + + // update the total number of displayed items + $('#displayed-component-state-entries').text(args.current); + }); + componentStateData.onRowsChanged.subscribe(function (e, args) { + componentStateGrid.invalidateRows(args.rows); + componentStateGrid.render(); + }); + + // hold onto an instance of the grid + $('#component-state-table').data('gridInstance', componentStateGrid); + + // initialize the number of display items + $('#displayed-component-state-entries').text('0'); + }, + + /** + * Shows the state for a given component. + * + * @param {object} component + * @param {boolean} canClear + */ + showState: function (component, canClear) { + return $.ajax({ + type: 'GET', + url: component.uri + '/state', + dataType: 'json' + }).done(function (response) { + var componentState = response.componentState; + var componentStateTable = $('#component-state-table'); + + // load the table + loadComponentState(componentState.localState, componentState.clusterState); + + // populate the name/description + $('#component-state-name').text(component.name); + $('#component-state-description').text(componentState.stateDescription); + + // store the component + componentStateTable.data('component', component); + + // show the dialog + $('#component-state-dialog').modal('show'); + + // only activate the link when appropriate + if (canClear === false) { + $('#clear-link').addClass('disabled').attr('title', 'Component state can only be cleared when the component is not actively running'); + } + + // reset the grid size + var componentStateGrid = componentStateTable.data('gridInstance'); + componentStateGrid.resizeCanvas(); + }).fail(nf.Common.handleAjaxError); + } + }; +}()); \ No newline at end of file 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 55df1e9d52..990ca07dd7 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 @@ -231,6 +231,25 @@ nf.ContextMenu = (function () { (nf.CanvasUtils.isInputPort(selection) && nf.Canvas.getParentGroupId() !== null); }; + /** + * Determines whether the current selection is a processor. + * + * @param {selection} selection + */ + var isStatefulProcessor = function (selection) { + // ensure the correct number of components are selected + if (selection.size() !== 1) { + return false; + } + + if (nf.CanvasUtils.isProcessor(selection)) { + var processorData = selection.datum(); + return processorData.component.persistsState === true; + } else { + return false; + } + }; + /** * Determines whether the current selection is a process group. * @@ -399,6 +418,7 @@ nf.ContextMenu = (function () { {condition: canStopTransmission, menuItem: {img: 'images/iconTransmissionInactive.png', text: 'Disable transmission', action: 'disableTransmission'}}, {condition: supportsStats, menuItem: {img: 'images/iconChart.png', text: 'Stats', action: 'showStats'}}, {condition: canAccessProvenance, menuItem: {img: 'images/iconProvenance.png', imgStyle: 'context-menu-provenance', text: 'Data provenance', action: 'openProvenance'}}, + {condition: isStatefulProcessor, menuItem: {img: 'images/iconViewState.png', text: 'View state', action: 'viewState'}}, {condition: canMoveToFront, menuItem: {img: 'images/iconToFront.png', text: 'Bring to front', action: 'toFront'}}, {condition: isConnection, menuItem: {img: 'images/iconGoTo.png', text: 'Go to source', action: 'showSource'}}, {condition: isConnection, menuItem: {img: 'images/iconGoTo.png', text: 'Go to destination', action: 'showDestination'}}, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js index 6d04a00227..231872061e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js @@ -572,24 +572,33 @@ nf.ProcessorConfiguration = (function () { // get the processor details var processor = selectionData.component; + var requests = []; + // reload the processor in case an property descriptors have updated - var reloadProcessor = nf.Processor.reload(processor); - + requests.push(nf.Processor.reload(processor)); + // get the processor history - var loadHistory = $.ajax({ + requests.push($.ajax({ type: 'GET', url: '../nifi-api/controller/history/processors/' + encodeURIComponent(processor.id), dataType: 'json' - }); + })); + + // get the processor state if we're a DFM + if (nf.Common.isDFM()) { + requests.push(); + } // once everything is loaded, show the dialog - $.when(reloadProcessor, loadHistory).done(function (processorResponse, historyResponse) { + $.when.apply(window, requests).done(function (processorResponse, historyResponse, stateResponse) { // get the updated processor processor = processorResponse[0].processor; // get the processor history var processorHistory = historyResponse[0].componentHistory; - + + console.log(stateResponse); + // record the processor details $('#processor-configuration').data('processorDetails', processor);