You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/02/20 19:39:27 UTC

incubator-nifi git commit: NIFI-250: - Enabling/Disabling controller service and referencing components in the appropriate order. - Continuing to setup Reporting Task management in the UI.

Repository: incubator-nifi
Updated Branches:
  refs/heads/NIFI-250 573a8aa35 -> c1077baf9


NIFI-250:
- Enabling/Disabling controller service and referencing components in the appropriate order.
- Continuing to setup Reporting Task management in the UI.

Project: http://git-wip-us.apache.org/repos/asf/incubator-nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-nifi/commit/c1077baf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-nifi/tree/c1077baf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-nifi/diff/c1077baf

Branch: refs/heads/NIFI-250
Commit: c1077baf95f8c7e5ffc16ccea190b56af59c3fcc
Parents: 573a8aa
Author: Matt Gilman <ma...@gmail.com>
Authored: Fri Feb 20 13:39:11 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Fri Feb 20 13:39:11 2015 -0500

----------------------------------------------------------------------
 ...ontrollerServiceReferencingComponentDTO.java |  26 +-
 .../org/apache/nifi/web/NiFiServiceFacade.java  |  15 +-
 .../nifi/web/StandardNiFiServiceFacade.java     |   7 +
 .../apache/nifi/web/api/ControllerResource.java |  12 +-
 .../nifi/web/api/ControllerServiceResource.java |  23 +-
 .../org/apache/nifi/web/api/dto/DtoFactory.java |  10 +
 .../nifi/web/dao/ControllerServiceDAO.java      |   9 +
 .../dao/impl/StandardControllerServiceDAO.java  |  19 ++
 .../src/main/resources/nifi-web-api-context.xml |   1 +
 .../js/nf/canvas/nf-controller-service.js       | 214 ++++++++----
 .../src/main/webapp/js/nf/canvas/nf-settings.js | 328 +++++++++++++++----
 11 files changed, 518 insertions(+), 146 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
index 9032f9f..0cebe9c 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
@@ -34,7 +34,9 @@ public class ControllerServiceReferencingComponentDTO {
 
     private String referenceType;
     private Integer activeThreadCount;
-    private Set<ControllerServiceReferencingComponentDTO> references;
+    
+    private Boolean referenceCycle;
+    private Set<ControllerServiceReferencingComponentDTO> referencingComponents;
 
     /**
      * Group id for this component referencing a controller service. If this
@@ -134,12 +136,26 @@ public class ControllerServiceReferencingComponentDTO {
      * 
      * @return 
      */
-    public Set<ControllerServiceReferencingComponentDTO> getReferences() {
-        return references;
+    public Set<ControllerServiceReferencingComponentDTO> getReferencingComponents() {
+        return referencingComponents;
     }
 
-    public void setReferences(Set<ControllerServiceReferencingComponentDTO> references) {
-        this.references = references;
+    public void setReferencingComponents(Set<ControllerServiceReferencingComponentDTO> referencingComponents) {
+        this.referencingComponents = referencingComponents;
+    }
+
+    /**
+     * If this referencing component represents a ControllerService, this indicates
+     * whether it has already been represented in this hierarchy.
+     * 
+     * @return 
+     */
+    public Boolean getReferenceCycle() {
+        return referenceCycle;
+    }
+
+    public void setReferenceCycle(Boolean referenceCycle) {
+        this.referenceCycle = referenceCycle;
     }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 02b97a0..f80545e 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -19,8 +19,10 @@ package org.apache.nifi.web;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Set;
+import org.apache.nifi.controller.ScheduledState;
 
 import org.apache.nifi.controller.repository.claim.ContentDirection;
+import org.apache.nifi.controller.service.ControllerServiceState;
 import org.apache.nifi.web.api.dto.BulletinBoardDTO;
 import org.apache.nifi.web.api.dto.BulletinQueryDTO;
 import org.apache.nifi.web.api.dto.ClusterDTO;
@@ -984,9 +986,9 @@ public interface NiFiServiceFacade {
      * @param controllerServiceId
      * @param scheduledState
      * @param controllerServiceState the value of state 
-     * @return the org.apache.nifi.web.ConfigurationSnapshot<java.util.Set<org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO>> 
+     * @return The referencing component dtos
      */
-    ConfigurationSnapshot<Set<ControllerServiceReferencingComponentDTO>> updateControllerServiceReferencingComponents(Revision revision, String controllerServiceId, org.apache.nifi.controller.ScheduledState scheduledState, org.apache.nifi.controller.service.ControllerServiceState controllerServiceState);
+    ConfigurationSnapshot<Set<ControllerServiceReferencingComponentDTO>> updateControllerServiceReferencingComponents(Revision revision, String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState);
     
     /**
      * Updates the specified label.
@@ -1014,6 +1016,15 @@ public interface NiFiServiceFacade {
     void verifyUpdateControllerService(ControllerServiceDTO controllerServiceDTO);
     
     /**
+     * Verifies the referencing components of the specified controller service can be updated.
+     * 
+     * @param controllerServiceId
+     * @param scheduledState
+     * @param controllerServiceState 
+     */
+    void verifyUpdateControllerServiceReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState);
+    
+    /**
      * Verifies the specified controller service can be removed.
      *
      * @param controllerServiceId

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index ec288d0..c75e123 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -154,8 +154,10 @@ import org.apache.nifi.web.util.SnippetUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.controller.service.ControllerServiceReference;
+import org.apache.nifi.controller.service.ControllerServiceState;
 import org.apache.nifi.web.api.dto.ControllerServiceDTO;
 import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
 import org.apache.nifi.web.api.dto.ReportingTaskDTO;
@@ -335,6 +337,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     }
 
     @Override
+    public void verifyUpdateControllerServiceReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState) {
+        controllerServiceDAO.verifyUpdateReferencingComponents(controllerServiceId, scheduledState, controllerServiceState);
+    }
+
+    @Override
     public void verifyDeleteControllerService(String controllerServiceId) {
         controllerServiceDAO.verifyDelete(controllerServiceId);
     }

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index f8c539d..98f17d5 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -169,6 +169,16 @@ public class ControllerResource extends ApplicationResource {
     public ControllerServiceResource getControllerServiceResource() {
         return resourceContext.getResource(ControllerServiceResource.class);
     }
+    
+    /**
+     * Locates the Reporting Tasks sub-resource.
+     *
+     * @return
+     */
+    @Path("/reporting-tasks")
+    public ReportingTaskResource getReportingTaskResource() {
+        return resourceContext.getResource(ReportingTaskResource.class);
+    }
 
     /**
      * Locates the Group sub-resource.
@@ -781,7 +791,7 @@ public class ControllerResource extends ApplicationResource {
         // create response entity
         final ReportingTaskTypesEntity entity = new ReportingTaskTypesEntity();
         entity.setRevision(revision);
-        entity.setReportingTaskTypes(serviceFacade.getControllerServiceTypes());
+        entity.setReportingTaskTypes(serviceFacade.getReportingTaskTypes());
 
         // generate the response
         return clusterContext(generateOkResponse(entity)).build();

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
index 732dec8..a77e376 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
@@ -403,35 +403,34 @@ public class ControllerServiceResource extends ApplicationResource {
             @FormParam(VERSION) LongParameter version,
             @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
             @PathParam("availability") String availability, @PathParam("id") String id,
-            @FormParam("state") String state) {
+            @FormParam("state") @DefaultValue(StringUtils.EMPTY) String state) {
 
         // parse the state to determine the desired action
-        ScheduledState scheduledState = null;
+        
+        // need to consider controller service state first as it shares a state with
+        // scheduled state (disabled) which is applicable for referencing services
+        // but not referencing schedulable components
+        ControllerServiceState controllerServiceState = null;
         try {
-            scheduledState = ScheduledState.valueOf(state);
+            controllerServiceState = ControllerServiceState.valueOf(state);
         } catch (final IllegalArgumentException iae) {
             // ignore
         }
         
-        ControllerServiceState controllerServiceState = null;
+        ScheduledState scheduledState = null;
         try {
-            controllerServiceState = ControllerServiceState.valueOf(state);
+            scheduledState = ScheduledState.valueOf(state);
         } catch (final IllegalArgumentException iae) {
             // ignore
         }
         
         // ensure an action has been specified
         if (scheduledState == null && controllerServiceState == null) {
-            throw new IllegalArgumentException("Must specify whether updating the state a valid state. To update referencing Processors "
+            throw new IllegalArgumentException("Must specify the updated state. To update referencing Processors "
                     + "and Reporting Tasks the state should be RUNNING or STOPPED. To update the referencing Controller Services the "
                     + "state should be ENABLED or DISABLED.");
         }
         
-        // ensure the scheduled state is not disabled
-        if (scheduledState != null && ScheduledState.DISABLED.equals(scheduledState)) {
-            throw new IllegalArgumentException("Cannot disable referencing components.");
-        }
-        
         // ensure the controller service state is not ENABLING or DISABLING
         if (controllerServiceState != null && (ControllerServiceState.ENABLING.equals(controllerServiceState) || ControllerServiceState.DISABLING.equals(controllerServiceState))) {
             throw new IllegalArgumentException("Cannot set the referencing services to ENABLING or DISABLING");
@@ -448,7 +447,7 @@ public class ControllerServiceResource extends ApplicationResource {
         // handle expects request (usually from the cluster manager)
         final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
         if (expects != null) {
-//            serviceFacade.verifyUpdateControllerServiceReferences(true);
+            serviceFacade.verifyUpdateControllerServiceReferencingComponents(id, scheduledState, controllerServiceState);
             return generateContinueResponse().build();
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
----------------------------------------------------------------------
diff --git a/nifi/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/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 47c8161..cb13247 100644
--- a/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -912,6 +912,10 @@ public final class DtoFactory {
     }
     
     public Set<ControllerServiceReferencingComponentDTO> createControllerServiceReferencingComponentsDto(final ControllerServiceReference reference) {
+        return createControllerServiceReferencingComponentsDto(reference, new HashSet<ControllerServiceNode>());
+    }
+    
+    private Set<ControllerServiceReferencingComponentDTO> createControllerServiceReferencingComponentsDto(final ControllerServiceReference reference, final Set<ControllerServiceNode> visited) {
         final Set<ControllerServiceReferencingComponentDTO> referencingComponents = new LinkedHashSet<>();
         
         // get all references
@@ -932,6 +936,12 @@ public final class DtoFactory {
                 dto.setState(node.getState().name());
                 dto.setType(node.getControllerServiceImplementation().getClass().getName());
                 dto.setReferenceType(ControllerService.class.getSimpleName());
+                dto.setReferenceCycle(visited.contains(node));
+                
+                // if we haven't encountered this service before include it's referencing components
+                if (!dto.getReferenceCycle()) {
+                    dto.setReferencingComponents(createControllerServiceReferencingComponentsDto(node.getReferences(), visited));
+                }
             } else if (component instanceof ReportingTask) {
                 final ReportingTaskNode node = ((ReportingTaskNode) component);
                 dto.setState(node.getScheduledState().name());

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
index f613d9c..c1fba0c 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
@@ -86,6 +86,15 @@ public interface ControllerServiceDAO {
     void verifyUpdate(ControllerServiceDTO controllerServiceDTO);
     
     /**
+     * Determines whether the referencing component of the specified controller service can be updated.
+     * 
+     * @param controllerServiceId
+     * @param scheduledState
+     * @param controllerServiceState 
+     */
+    void verifyUpdateReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState);
+    
+    /**
      * Determines whether this controller service can be removed.
      *
      * @param controllerServiceId

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
index a9a63ad..096058d 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
@@ -182,6 +182,25 @@ public class StandardControllerServiceDAO extends ComponentDAO implements Contro
         final ControllerServiceNode controllerService = locateControllerService(controllerServiceDTO.getId());
         verifyUpdate(controllerService, controllerServiceDTO);
     }
+
+    @Override
+    public void verifyUpdateReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState) {
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+        
+        if (controllerServiceState != null) {
+            if (ControllerServiceState.ENABLED.equals(controllerServiceState)) {
+//                serviceProvider.enableReferencingServices(controllerService);
+            } else {
+//                serviceProvider.disableReferencingServices(controllerService);
+            }
+        } else if (scheduledState != null) {
+            if (ScheduledState.RUNNING.equals(scheduledState)) {
+//                serviceProvider.scheduleReferencingComponents(controllerService);
+            } else {
+//                serviceProvider.unscheduleReferencingComponents(controllerService);
+            }
+        }
+    }
     
     /**
      * Verifies the controller service can be updated.

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 134199b..34c56b6 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -102,6 +102,7 @@
         <property name="funnelDAO" ref="funnelDAO"/>
         <property name="connectionDAO" ref="connectionDAO"/>
         <property name="controllerServiceDAO" ref="controllerServiceDAO"/>
+        <property name="reportingTaskDAO" ref="reportingTaskDAO"/>
         <property name="templateDAO" ref="templateDAO"/>
         <property name="snippetDAO" ref="snippetDAO"/>
         <property name="auditService" ref="auditService"/>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
index 45d949a..3aeef8a 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
@@ -278,9 +278,8 @@ nf.ControllerService = (function () {
                 });
                 
                 var serviceState = $('<div class="referencing-component-state"></div>').addClass(referencingComponent.state === 'ENABLED' ? 'enabled' : 'disabled').addClass(referencingComponent.id + '-active-threads');
-                var serviceId = $('<span class="referencing-service-id hidden"></span>').text(referencingComponent.id);
                 var serviceType = $('<span class="referencing-component-type"></span>').text(nf.Common.substringAfterLast(referencingComponent.type, '.'));
-                var serviceItem = $('<li></li>').append(serviceTwist).append(serviceState).append(serviceId).append(serviceLink).append(serviceType).append(referencingServiceReferencesContainer);
+                var serviceItem = $('<li></li>').append(serviceTwist).append(serviceState).append(serviceLink).append(serviceType).append(referencingServiceReferencesContainer);
                 
                 services.append(serviceItem);
             } else if (referencingComponent.referenceType === 'ReportingTask') {
@@ -323,7 +322,12 @@ nf.ControllerService = (function () {
         createReferenceBlock('Controller Services', services);
     };
     
-    // sets whether the specified controller service is enabled
+    /**
+     * Sets whether the specified controller service is enabled.
+     * 
+     * @param {object} controllerService
+     * @param {boolean} enabled
+     */
     var setEnabled = function (controllerService, enabled) {
         var revision = nf.Client.getRevision();
         return $.ajax({
@@ -344,8 +348,43 @@ nf.ControllerService = (function () {
         }).fail(nf.Common.handleAjaxError);
     };
     
-    // updates the referencing components with the specified state
-    var updateReferencingComponents = function (controllerService, activated) {
+    /**
+     * Gets the id's of all controller services referencing the specified controller service.
+     * 
+     * @param {object} controllerService
+     */
+    var getReferencingControllerServiceIds = function (controllerService) {
+        var ids = d3.set();
+        ids.add(controllerService.id);
+        
+        var checkReferencingServices = function (referencingComponents) {
+            $.each(referencingComponents, function (_, referencingComponent) {
+                if (referencingComponent.referenceType === 'ControllerService') {
+                    // add the id
+                    ids.add(referencingComponent.id);
+                    
+                    // consider it's referencing components if appropriate
+                    if (referencingComponent.referenceCycle === false) {
+                        checkReferencingServices(referencingComponent.referencingComponents);
+                    }
+                }
+            });
+        };
+
+        // check the referencing servcies
+        checkReferencingServices(controllerService.referencingComponents);
+        return ids;
+    };
+    
+    /**
+     * Updates the scheduled state of the processors/reporting tasks referencing
+     * the specified controller service.
+     * 
+     * @param {type} controllerService
+     * @param {type} running
+     * @returns {unresolved}
+     */
+    var updateReferencingSchedulableComponents = function (controllerService, running) {
         var revision = nf.Client.getRevision();
         
         // issue the request to update the referencing components
@@ -355,7 +394,7 @@ nf.ControllerService = (function () {
             data: {
                 clientId: revision.clientId,
                 version: revision.version,
-                activated: activated
+                state: running ? 'RUNNING' : 'STOPPED'
             },
             dataType: 'json'
         }).done(function (response) {
@@ -366,19 +405,14 @@ nf.ControllerService = (function () {
             reloadControllerServiceReferences(controllerService);
         }).fail(nf.Common.handleAjaxError);
         
-        // if we are activating, we can stop here
-        if (activated === true) {
+        // if we're just starting schedulable components we're done when the
+        // update is finished
+        if (running) {
             return updated;
         }
         
-        // identify all services... this service
-        var services = d3.set();
-        services.add(controllerService.id);
-        
-        // and all referencing services
-        $('span.referencing-service-id').each(function() {
-            services.add($(this).text());
-        });
+        // identify all referencing services
+        var services = getReferencingControllerServiceIds(controllerService);
 
         // get the controller service grid
         var controllerServiceGrid = $('#controller-services-table').data('gridInstance');
@@ -388,7 +422,7 @@ nf.ControllerService = (function () {
         var polling = [];
         services.forEach(function(controllerServiceId) {
             var controllerService = controllerServiceData.getItemById(controllerServiceId);
-            polling.push(pollControllerServiceStatus(controllerService));
+            polling.push(pollStoppedReferencingSchedulableComponents(controllerService));
         });
         
         // wait unil the polling of each service finished
@@ -401,10 +435,15 @@ nf.ControllerService = (function () {
         }).promise();
     };
     
-    // polls for the status of the specified controller service
-    var pollControllerServiceStatus = function (controllerService) {
-        // since we are deactivating, we want to keep polling until 
-        // everything has stopped and there are 0 active threads
+    /**
+     * Polls the specified services referencing components to see if the
+     * specified condition is satisfied.
+     * 
+     * @param {object} controllerService
+     * @param {function} condition
+     */
+    var pollReferencingComponents = function (controllerService, condition) {
+        // we want to keep polling until the condition is met
         return $.Deferred(function(deferred) {
             var current = 1;
             var getTimeout = function () {
@@ -417,54 +456,86 @@ nf.ControllerService = (function () {
             };
             
             // polls for the current status of the referencing components
-            var pollReferencingComponents = function() {
+            var poll = function() {
                 $.ajax({
                     type: 'GET',
                     url: controllerService.uri + '/references',
                     dataType: 'json'
                 }).done(function (response) {
-                    checkDeactivated(response.controllerServiceReferencingComponents);
+                    if (condition(response.controllerServiceReferencingComponents)) {
+                        deferred.resolve();
+                    } else {
+                        setTimeout(poll(), getTimeout());
+                    }
                 }).fail(function (xhr, status, error) {
                     deferred.reject();
                     nf.Common.handleAjaxError(xhr, status, error);
                 });
             };
             
-            // checks the referencing components to see if any are still active
-            var checkDeactivated = function (controllerServiceReferencingComponents) {
-                var stillRunning = false;
-                
-                $.each(controllerServiceReferencingComponents, function(referencingComponent) {
-                    if (referencingComponent.referenceType === 'ControllerService') {
-                        if (referencingComponent.enable === true) {
-                            // update the current values for this component
-                            $(referencingComponent.id + '-state').removeClass('enabled disabled').addClass(referencingComponent.state === 'ENABLED' ? 'enabled' : 'disabled');
-                            
-                            // mark that something is still running
-                            stillRunning = true;
-                        }
-                    } else {
-                        if (referencingComponent.state === 'RUNNING' || referencingComponent.activeThreadCount > 0) {
-                            // update the current values for this component
-                            $(referencingComponent.id + '-active-threads').text(referencingComponent.activeThreadCount);
-                            $(referencingComponent.id + '-state').removeClass('disabled stopped running').addClass(referencingComponent.state.toLowerCase());
-                            
-                            // mark that something is still running
-                            stillRunning = true;
-                        }
+            // poll for the status of the referencing components
+            poll();
+        }).promise();
+    };
+    
+    /**
+     * Continues to poll the specified controller service until all referencing schedulable 
+     * components are stopped (not scheduled and 0 active threads).
+     * 
+     * @param {object} controllerService
+     */
+    var pollStoppedReferencingSchedulableComponents = function (controllerService) {
+        // continue to poll the service until all schedulable components have stopped
+        return pollReferencingComponents(controllerService, function (referencingComponents) {
+            var stillRunning = false;
+
+            $.each(referencingComponents, function(referencingComponent) {
+                if (referencingComponent.referenceType === 'Processor' || referencingComponent.referenceType === 'ReportingTask') {
+                    if (referencingComponent.state === 'RUNNING' || referencingComponent.activeThreadCount > 0) {
+                        // update the current values for this component
+                        $(referencingComponent.id + '-active-threads').text(referencingComponent.activeThreadCount);
+                        $(referencingComponent.id + '-state').removeClass('disabled stopped running').addClass(referencingComponent.state.toLowerCase());
+
+                        // mark that something is still running
+                        stillRunning = true;
                     }
-                });
-                
-                if (stillRunning) {
-                    setTimeout(pollReferencingComponents(), getTimeout());
-                } else {
-                    deferred.resolve();
                 }
-            };
+            });
+
+            // condition is met once all referencing are not running
+            return stillRunning === false;
+        });
+    };
+    
+    /**
+     * Updates the referencing services with the specified state.
+     * 
+     * @param {type} controllerService
+     * @param {type} enabled
+     */
+    var updateReferencingServices = function (controllerService, enabled) {
+        var revision = nf.Client.getRevision();
+        
+        // issue the request to update the referencing components
+        var updated = $.ajax({
+            type: 'PUT',
+            url: controllerService.uri + '/references',
+            data: {
+                clientId: revision.clientId,
+                version: revision.version,
+                state: enabled ? 'ENABLED' : 'DISABLED'
+            },
+            dataType: 'json'
+        }).done(function (response) {
+            // update the revision
+            nf.Client.setRevision(response.revision);
             
-            // poll for the status of the referencing components
-            pollReferencingComponents();
-        }).promise();
+            // update the service
+            reloadControllerServiceReferences(controllerService);
+        }).fail(nf.Common.handleAjaxError);
+        
+        // need to wait until finished ENALBING or DISABLING?
+        return updated;
     };
     
     /**
@@ -593,15 +664,21 @@ nf.ControllerService = (function () {
                             var controllerServiceData = controllerServiceGrid.getData();
                             var controllerService = controllerServiceData.getItemById(controllerServiceId);
                             
-                            // deactivate all referencing components
-                            var deactivated = updateReferencingComponents(controllerService, false);
+                            // stop all referencing schedulable components
+                            var stopped = updateReferencingSchedulableComponents(controllerService, false);
                             
-                            // once all referencing components have been deactivated...
-                            deactivated.done(function() {
-                                // disable this service
-                                setEnabled(controllerService, false).done(function() {
-                                    // close the dialog
-                                    $('#disable-controller-service-dialog').modal('hide');
+                            // once everything has stopped
+                            stopped.done(function () {
+                                // disable all referencing services
+                                var disabled = updateReferencingServices(controllerService, false);
+                                
+                                // everything is disabled
+                                disabled.done(function () {
+                                    // disalbe this service
+                                    setEnabled(controllerService, false).done(function () {
+                                        // close the dialog
+                                        $('#disable-controller-service-dialog').modal('hide');
+                                    });
                                 });
                             });
                         }
@@ -663,7 +740,16 @@ nf.ControllerService = (function () {
                             if (scope === config.serviceAndReferencingComponents) {
                                 // once the service is enabled, activate all referencing components
                                 enabled.done(function() {
-                                    updateReferencingComponents(controllerService, true);
+                                    // enable the referencing services
+                                    var servicesEnabled = updateReferencingServices(controllerService, true);
+                                    
+                                    // once all the referencing services are enbled
+                                    servicesEnabled.done(function () {
+                                        // start all referencing schedulable components
+                                        updateReferencingSchedulableComponents(controllerService, true).done(function() {
+                                            // hide the dialog now?
+                                        });
+                                    });
                                 });
                             }
                             

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/c1077baf/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
index 33fd1d0..f4191f7 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -32,6 +32,15 @@ nf.Settings = (function () {
             reportingTasks: '../nifi-api/controller/reporting-tasks'
         }
     };
+    
+    var gridOptions = {
+        forceFitColumns: true,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        autoEdit: false,
+        multiSelect: false
+    };
 
     /**
      * Initializes the general tab.
@@ -324,7 +333,7 @@ nf.Settings = (function () {
      * @param {type} columnDef
      * @param {type} dataContext
      */
-    var typeFormatter = function (row, cell, value, columnDef, dataContext) {
+    var expandableTypeFormatter = function (row, cell, value, columnDef, dataContext) {
         var markup = '';
         
         var indent = 0;
@@ -464,17 +473,9 @@ nf.Settings = (function () {
 
         // initialize the processor type table
         var controllerServiceTypesColumns = [
-            {id: 'type', name: 'Type', field: 'label', formatter: typeFormatter, sortable: false, resizable: true},
+            {id: 'type', name: 'Type', field: 'label', formatter: expandableTypeFormatter, sortable: false, resizable: true},
             {id: 'tags', name: 'Tags', field: 'tags', sortable: false, resizable: true}
         ];
-        var controllerServiceTypesOptions = {
-            forceFitColumns: true,
-            enableTextSelectionOnCells: true,
-            enableCellNavigation: true,
-            enableColumnReorder: false,
-            autoEdit: false,
-            multiSelect: false
-        };
 
         // initialize the dataview
         var controllerServiceTypesData = new Slick.Data.DataView({
@@ -514,7 +515,7 @@ nf.Settings = (function () {
         };
 
         // initialize the grid
-        var controllerServiceTypesGrid = new Slick.Grid('#controller-service-types-table', controllerServiceTypesData, controllerServiceTypesColumns, controllerServiceTypesOptions);
+        var controllerServiceTypesGrid = new Slick.Grid('#controller-service-types-table', controllerServiceTypesData, controllerServiceTypesColumns, gridOptions);
         controllerServiceTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
         controllerServiceTypesGrid.registerPlugin(new Slick.AutoTooltips());
         controllerServiceTypesGrid.setSortColumn('type', true);
@@ -671,10 +672,45 @@ nf.Settings = (function () {
                 // clear the tagcloud
                 $('#controller-service-tag-cloud').tagcloud('clearSelectedTags');
             }
+        }).draggable({
+            containment: 'parent',
+            handle: '.dialog-header'
         });
     };
     
     /**
+     * Formatter for the type column.
+     * 
+     * @param {type} row
+     * @param {type} cell
+     * @param {type} value
+     * @param {type} columnDef
+     * @param {type} dataContext
+     * @returns {String}
+     */
+    var typeFormatter = function (row, cell, value, columnDef, dataContext) {
+        return nf.Common.substringAfterLast(value, '.');
+    };
+    
+    /**
+     * Formatter for the availability column.
+     * 
+     * @param {type} row
+     * @param {type} cell
+     * @param {type} value
+     * @param {type} columnDef
+     * @param {type} dataContext
+     * @returns {String}
+     */
+    var availabilityFormatter = function (row, cell, value, columnDef, dataContext) {
+        if (value === config.node) {
+            return 'Node';
+        } else {
+            return 'Cluster Manager';
+        }
+    };
+    
+    /**
      * Sorts the specified data using the specified sort details.
      * 
      * @param {object} sortDetails
@@ -708,11 +744,6 @@ nf.Settings = (function () {
             return markup;
         };
         
-        // type formatter
-        var typeFormatter = function (row, cell, value, columnDef, dataContext) {
-            return nf.Common.substringAfterLast(value, '.');
-        };
-        
         // define the column model for the controller services table
         var controllerServicesColumns = [
             {id: 'moreDetails', name: '&nbsp;', resizable: false, formatter: moreControllerServiceDetails, sortable: false, width: 50, maxWidth: 50},
@@ -723,14 +754,6 @@ nf.Settings = (function () {
         
         // only show availability when clustered
         if (nf.Canvas.isClustered()) {
-            var availabilityFormatter = function (row, cell, value, columnDef, dataContext) {
-                if (value === config.node) {
-                    return 'Node';
-                } else {
-                    return 'Cluster Manager';
-                }
-            };
-            
             controllerServicesColumns.push({id: 'availability', field: 'availability', name: 'Availability', formatter: availabilityFormatter, sortable: true, resizeable: true});
         }
         
@@ -758,15 +781,6 @@ nf.Settings = (function () {
             controllerServicesColumns.push({id: 'actions', name: '&nbsp;', resizable: false, formatter: controllerServiceActionFormatter, sortable: false, width: 75, maxWidth: 75});
         }
         
-        var controllerServicesOptions = {
-            forceFitColumns: true,
-            enableTextSelectionOnCells: true,
-            enableCellNavigation: true,
-            enableColumnReorder: false,
-            autoEdit: false,
-            multiSelect: false
-        };
-
         // initialize the dataview
         var controllerServicesData = new Slick.Data.DataView({
             inlineFilters: false
@@ -780,7 +794,7 @@ nf.Settings = (function () {
         }, controllerServicesData);
         
         // initialize the grid
-        var controllerServicesGrid = new Slick.Grid('#controller-services-table', controllerServicesData, controllerServicesColumns, controllerServicesOptions);
+        var controllerServicesGrid = new Slick.Grid('#controller-services-table', controllerServicesData, controllerServicesColumns, gridOptions);
         controllerServicesGrid.setSelectionModel(new Slick.RowSelectionModel());
         controllerServicesGrid.registerPlugin(new Slick.AutoTooltips());
         controllerServicesGrid.setSortColumn('name', true);
@@ -909,7 +923,7 @@ nf.Settings = (function () {
             var controllerServicesGrid = controllerServicesElement.data('gridInstance');
             var controllerServicesData = controllerServicesGrid.getData();
 
-            // update the processors
+            // update the controller services
             controllerServicesData.setItems(services);
             controllerServicesData.reSort();
             controllerServicesGrid.invalidate();
@@ -955,6 +969,32 @@ nf.Settings = (function () {
     };
     
     /**
+     * Hides the selected reporting task.
+     */
+    var clearSelectedReportingTask = function () {
+        $('#reporting-task-type-description').text('');
+        $('#reporting-task-type-name').text('');
+        $('#reporting-task-availability-combo').combo('setSelectedOption', {
+            value: config.node
+        });
+        $('#selected-reporting-task-name').text('');
+        $('#selected-reporting-task-type').text('');
+        $('#reporting-task-description-container').hide();
+    };
+    
+    /**
+     * Clears the selected reporting task type.
+     */
+    var clearReportingTaskSelection = function () {
+        // clear the selected row
+        clearSelectedReportingTask();
+
+        // clear the active cell the it can be reselected when its included
+        var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
+        reportingTaskTypesGrid.resetActiveCell();
+    };
+    
+    /**
      * Performs the filtering.
      * 
      * @param {object} item     The item subject to filtering
@@ -980,7 +1020,7 @@ nf.Settings = (function () {
 
         // if this row is currently selected and its being filtered
         if (matches === false && $('#selected-reporting-task-type').text() === item['type']) {
-//            clearControllerServiceSelection();
+            clearReportingTaskSelection();
         }
         
         // update visibility flag
@@ -990,7 +1030,7 @@ nf.Settings = (function () {
     };
     
     /**
-     * Adds a new controller service of the specified type.
+     * Adds a new reporting task of the specified type.
      * 
      * @param {string} reportingTaskType
      */
@@ -1020,17 +1060,17 @@ nf.Settings = (function () {
             nf.Client.setRevision(response.revision);
 
             // add the item
-            var reportingTaskService = response.reportingTask;
+            var reportingTask = response.reportingTask;
             var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance');
             var reportingTaskData = reportingTaskGrid.getData();
-            reportingTaskData.addItem(reportingTaskService);
+            reportingTaskData.addItem(reportingTask);
             
             // resort
             reportingTaskData.reSort();
             reportingTaskGrid.invalidate();
             
-            // select the new controller service
-            var row = reportingTaskData.getRowById(reportingTaskService.id);
+            // select the new reporting task
+            var row = reportingTaskData.getRowById(reportingTask.id);
             reportingTaskGrid.setSelectedRows([row]);
         });
         
@@ -1058,17 +1098,17 @@ nf.Settings = (function () {
             }
         });
         
-        // specify the controller service availability
+        // specify the reporting task availability
         if (nf.Canvas.isClustered()) {
             $('#reporting-task-availability-combo').combo({
                 options: [{
                         text: 'Node',
                         value: config.node,
-                        description: 'This controller service will be available on the nodes only.'
+                        description: 'This reporting task will be available on the nodes only.'
                     }, {
                         text: 'Cluster Manager',
                         value: config.ncm,
-                        description: 'This controller service will be available on the cluster manager only.'
+                        description: 'This reporting task will be available on the cluster manager only.'
                     }]
             });
             $('#reporting-task-availability-container').show();
@@ -1092,14 +1132,6 @@ nf.Settings = (function () {
             {id: 'type', name: 'Type', field: 'label', sortable: false, resizable: true},
             {id: 'tags', name: 'Tags', field: 'tags', sortable: false, resizable: true}
         ];
-        var reportingTaskTypesOptions = {
-            forceFitColumns: true,
-            enableTextSelectionOnCells: true,
-            enableCellNavigation: true,
-            enableColumnReorder: false,
-            autoEdit: false,
-            multiSelect: false
-        };
 
         // initialize the dataview
         var reportingTaskTypesData = new Slick.Data.DataView({
@@ -1113,7 +1145,7 @@ nf.Settings = (function () {
         reportingTaskTypesData.setFilter(filterReportingTaskTypes);
         
         // initialize the grid
-        var reportingTaskTypesGrid = new Slick.Grid('#reporting-task-types-table', reportingTaskTypesData, reportingTaskTypesColumns, reportingTaskTypesOptions);
+        var reportingTaskTypesGrid = new Slick.Grid('#reporting-task-types-table', reportingTaskTypesData, reportingTaskTypesColumns, gridOptions);
         reportingTaskTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
         reportingTaskTypesGrid.registerPlugin(new Slick.AutoTooltips());
         reportingTaskTypesGrid.setSortColumn('type', true);
@@ -1133,7 +1165,7 @@ nf.Settings = (function () {
             $('#selected-reporting-task-name').text(reportingTaskType.label);
             $('#selected-reporting-task-type').text(reportingTaskType.type);
 
-            // show the selected controller service
+            // show the selected reporting task
             $('#reporting-task-description-container').show();
         });
         reportingTaskTypesGrid.onDblClick.subscribe(function (e, args) {
@@ -1204,9 +1236,9 @@ nf.Settings = (function () {
             });
         }).fail(nf.Common.handleAjaxError);
         
-        // initialize the controller service dialog
+        // initialize the reporting task dialog
         $('#new-reporting-task-dialog').modal({
-            headerText: 'Add Controller Service',
+            headerText: 'Add Reporting Task',
             overlayBackground: false,
             buttons: [{
                 buttonText: 'Add',
@@ -1226,7 +1258,7 @@ nf.Settings = (function () {
             }],
             close: function() {
                 // clear the selected row
-//                clearSelectedControllerService();
+                clearSelectedReportingTask();
 
                 // unselect any current selection
                 var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance');
@@ -1239,6 +1271,9 @@ nf.Settings = (function () {
                 // clear the tagcloud
                 $('#reporting-task-tag-cloud').tagcloud('clearSelectedTags');
             }
+        }).draggable({
+            containment: 'parent',
+            handle: '.dialog-header'
         });
     };
     
@@ -1250,22 +1285,193 @@ nf.Settings = (function () {
         initNewReportingTaskDialog();
         
         var moreReportingTaskDetails = function (row, cell, value, columnDef, dataContext) {
-            return '<img src="images/iconDetails.png" title="View Details" class="pointer view-reporting-task" style="margin-top: 5px; float: left;" />';
+            var markup = '<img src="images/iconDetails.png" title="View Details" class="pointer view-reporting-task" style="margin-top: 5px; float: left;" />&nbsp;&nbsp;';
+            if (!nf.Common.isEmpty(dataContext.validationErrors)) {
+                markup += '<img src="images/iconAlert.png" class="has-errors" style="margin-top: 4px; float: left;" /><span class="hidden row-id">' + nf.Common.escapeHtml(dataContext.id) + '</span>';
+            }
+            return markup;
         };
         
         // define the column model for the reporting tasks table
         var reportingTasksColumnModel = [
             {id: 'moreDetails', field: 'moreDetails', name: '&nbsp;', resizable: false, formatter: moreReportingTaskDetails, sortable: true, width: 50, maxWidth: 50},
             {id: 'name', field: 'name', name: 'Name', sortable: true, resizable: true},
-            {id: 'type', field: 'type', name: 'Type', sortable: true, resizable: true}
+            {id: 'type', field: 'type', name: 'Type', sortable: true, resizable: true, formatter: typeFormatter},
+            {id: 'state', field: 'state', name: 'State', sortable: true, resizeable: true}
         ];
+        
+        // only show availability when clustered
+        if (nf.Canvas.isClustered()) {
+            reportingTasksColumnModel.push({id: 'availability', field: 'availability', name: 'Availability', formatter: availabilityFormatter, sortable: true, resizeable: true});
+        }
+        
+        // only DFM can edit reporting tasks
+        if (nf.Common.isDFM()) {
+            var reportingTaskActionFormatter = function (row, cell, value, columnDef, dataContext) {
+                var markup = '';
+
+                if (dataContext.state === 'ENABLED' || dataContext.state === 'ENABLING') {
+                    markup += '<img src="images/iconDisable.png" title="Disable" class="pointer disable-controller-service" style="margin-top: 2px;" />&nbsp;';
+                } else if (dataContext.state === 'DISABLED') {
+                    markup += '<img src="images/iconEdit.png" title="Edit" class="pointer edit-controller-service" style="margin-top: 2px;" />&nbsp;';
+                    
+                    // only enable the enable icon if the service has no validation errors
+                    if (nf.Common.isEmpty(dataContext.validationErrors)) {
+                        markup += '<img src="images/iconEnable.png" title="Enable" class="pointer enable-controller-service" style="margin-top: 2px;"/>&nbsp;';
+                    }
+                    
+                    markup += '<img src="images/iconDelete.png" title="Remove" class="pointer delete-controller-service" style="margin-top: 2px;" />&nbsp;';
+                }
+
+                return markup;
+            };
+            
+            reportingTasksColumnModel.push({id: 'actions', name: '&nbsp;', resizable: false, formatter: reportingTaskActionFormatter, sortable: false, width: 75, maxWidth: 75});
+        }
+        
+        // initialize the dataview
+        var reportingTasksData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        reportingTasksData.setItems([]);
+        
+        // initialize the sort
+        sort({
+            columnId: 'name',
+            sortAsc: true
+        }, reportingTasksData);
+        
+        // initialize the grid
+        var reportingTasksGrid = new Slick.Grid('#reporting-tasks-table', reportingTasksData, reportingTasksColumnModel, gridOptions);
+        reportingTasksGrid.setSelectionModel(new Slick.RowSelectionModel());
+        reportingTasksGrid.registerPlugin(new Slick.AutoTooltips());
+        reportingTasksGrid.setSortColumn('name', true);
+        reportingTasksGrid.onSort.subscribe(function (e, args) {
+            sort({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, reportingTasksData);
+        });
+        
+        // configure a click listener
+        reportingTasksGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+            
+            // get the service at this row
+            var reportingTask = reportingTasksData.getItem(args.row);
+            
+            // determine the desired action
+            if (reportingTasksGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('edit-reporting-task')) {
+                } else if (target.hasClass('start-reporting-task')) {
+                } else if (target.hasClass('stop-reporting-task')) {
+                } else if (target.hasClass('delete-reporting-task')) {
+                }
+            } else if (reportingTasksGrid.getColumns()[args.cell].id === 'moreDetails') {
+                if (target.hasClass('view-reporting-task')) {
+                    
+                }
+            }
+        });
+
+        // wire up the dataview to the grid
+        reportingTasksData.onRowCountChanged.subscribe(function (e, args) {
+            reportingTasksGrid.updateRowCount();
+            reportingTasksGrid.render();
+        });
+        reportingTasksData.onRowsChanged.subscribe(function (e, args) {
+            reportingTasksGrid.invalidateRows(args.rows);
+            reportingTasksGrid.render();
+        });
+        reportingTasksData.syncGridSelection(reportingTasksGrid, true);
+
+        // hold onto an instance of the grid
+        $('#reporting-tasks-table').data('gridInstance', reportingTasksGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var errorIcon = $(this).find('img.has-errors');
+            if (errorIcon.length && !errorIcon.data('qtip')) {
+                var taskId = $(this).find('span.row-id').text();
+
+                // get the service item
+                var item = reportingTasksData.getItemById(taskId);
+
+                // format the errors
+                var tooltip = nf.Common.formatUnorderedList(item.validationErrors);
+
+                // show the tooltip
+                if (nf.Common.isDefinedAndNotNull(tooltip)) {
+                    errorIcon.qtip($.extend({
+                        content: tooltip,
+                        position: {
+                            target: 'mouse',
+                            viewport: $(window),
+                            adjust: {
+                                x: 8,
+                                y: 8,
+                                method: 'flipinvert flipinvert'
+                            }
+                        }
+                    }, nf.Common.config.tooltipConfig));
+                }
+            }
+        });
     };
     
     /**
      * Loads the reporting tasks.
      */
     var loadReportingTasks = function () {
+        var tasks = [];
+        
+        // get the reporting tasks that are running on the nodes
+        var nodeReportingTasks = $.ajax({
+            type: 'GET',
+            url: config.urls.reportingTasks + '/' + encodeURIComponent(config.node),
+            dataType: 'json'
+        }).done(function(response) {
+            var nodeTasks = response.reportingTasks;
+            if (nf.Common.isDefinedAndNotNull(nodeTasks)) {
+                $.each(nodeTasks, function(_, nodeTask) {
+                    tasks.push(nodeTask);
+                });
+            }
+        });
         
+        // get the reporting tasks that are running on the ncm
+        var ncmReportingTasks = $.Deferred(function(deferred) {
+            if (nf.Canvas.isClustered()) {
+                $.ajax({
+                    type: 'GET',
+                    url: config.urls.reportingTasks + '/' + encodeURIComponent(config.ncm),
+                    dataType: 'json'
+                }).done(function(response) {
+                    var ncmTasks = response.reportingTasks;
+                    if (nf.Common.isDefinedAndNotNull(ncmTasks)) {
+                        $.each(ncmTasks, function(_, ncmTask) {
+                            tasks.push(ncmTask);
+                        });
+                    }
+                    deferred.resolve();
+                }).fail(function() {
+                    deferred.reject();
+                });
+            } else {
+                deferred.resolve();
+            }
+        }).promise();
+        
+        // add all reporting tasks
+        return $.when(nodeReportingTasks, ncmReportingTasks).done(function() {
+            var reportingTasksElement = $('#reporting-tasks-table');
+            nf.Common.cleanUpTooltips(reportingTasksElement, 'img.has-errors');
+
+            var reportingTasksGrid = reportingTasksElement.data('gridInstance');
+            var reportingTasksData = reportingTasksGrid.getData();
+
+            // update the reporting tasks
+            reportingTasksData.setItems(tasks);
+            reportingTasksData.reSort();
+            reportingTasksGrid.invalidate();
+        });
     };
 
     return {
@@ -1404,9 +1610,7 @@ nf.Settings = (function () {
             var reportingTasks = loadReportingTasks();
             
             // return a deferred for all parts of the settings
-            return $.when(settings, controllerServices, reportingTasks).done(function () {
-                
-            }).fail(nf.Common.handleAjaxError);
+            return $.when(settings, controllerServices, reportingTasks).fail(nf.Common.handleAjaxError);
         }
     };
 }());
\ No newline at end of file