NIFI-1554:

- Refactoring Cluster endpoints.
- Updating frontend to accomodate clustering endpoints.
- Remove the 'Make Primary' action.
- This closes #470
This commit is contained in:
Matt Gilman 2016-05-26 12:58:44 -04:00
parent 9152a9fdbb
commit a0ff2f7a9f
5 changed files with 228 additions and 330 deletions

View File

@ -17,11 +17,12 @@
package org.apache.nifi.web.api.dto;
import com.wordnik.swagger.annotations.ApiModelProperty;
import java.util.Date;
import java.util.List;
import org.apache.nifi.web.api.dto.util.DateTimeAdapter;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.apache.nifi.web.api.dto.util.DateTimeAdapter;
import java.util.Date;
import java.util.List;
/**
* Details of a node within this NiFi.
@ -46,7 +47,8 @@ public class NodeDTO {
*/
@XmlJavaTypeAdapter(DateTimeAdapter.class)
@ApiModelProperty(
value = "the time of the nodes's last heartbeat."
value = "the time of the nodes's last heartbeat.",
readOnly = true
)
public Date getHeartbeat() {
return heartbeat;
@ -61,7 +63,8 @@ public class NodeDTO {
*/
@XmlJavaTypeAdapter(DateTimeAdapter.class)
@ApiModelProperty(
value = "The time of the node's last connection request."
value = "The time of the node's last connection request.",
readOnly = true
)
public Date getConnectionRequested() {
return connectionRequested;
@ -77,7 +80,8 @@ public class NodeDTO {
* @return The active thread count
*/
@ApiModelProperty(
value = "The active threads for the NiFi on the node."
value = "The active threads for the NiFi on the node.",
readOnly = true
)
public Integer getActiveThreadCount() {
return activeThreadCount;
@ -91,7 +95,8 @@ public class NodeDTO {
* @return queue for the controller
*/
@ApiModelProperty(
value = "The queue the NiFi on the node."
value = "The queue the NiFi on the node.",
readOnly = true
)
public String getQueued() {
return queued;
@ -105,7 +110,8 @@ public class NodeDTO {
* @return node's host/IP address
*/
@ApiModelProperty(
value = "The node's host/ip address."
value = "The node's host/ip address.",
readOnly = true
)
public String getAddress() {
return address;
@ -119,7 +125,8 @@ public class NodeDTO {
* @return node ID
*/
@ApiModelProperty(
value = "The id of the node."
value = "The id of the node.",
readOnly = true
)
public String getNodeId() {
return nodeId;
@ -133,7 +140,8 @@ public class NodeDTO {
* @return port the node is listening for API requests
*/
@ApiModelProperty(
value = "The port the node is listening for API requests."
value = "The port the node is listening for API requests.",
readOnly = true
)
public Integer getApiPort() {
return apiPort;
@ -161,7 +169,8 @@ public class NodeDTO {
* @return node's events
*/
@ApiModelProperty(
value = "The node's events."
value = "The node's events.",
readOnly = true
)
public List<NodeEventDTO> getEvents() {
return events;
@ -175,7 +184,8 @@ public class NodeDTO {
* @return whether this node is the primary node within the cluster
*/
@ApiModelProperty(
value = "Whether the node is the primary node within the cluster."
value = "Whether the node is the primary node within the cluster.",
readOnly = true
)
public Boolean isPrimary() {
return primary;
@ -190,7 +200,8 @@ public class NodeDTO {
*/
@XmlJavaTypeAdapter(DateTimeAdapter.class)
@ApiModelProperty(
value = "The time at which this Node was last refreshed."
value = "The time at which this Node was last refreshed.",
readOnly = true
)
public Date getNodeStartTime() {
return nodeStartTime;

View File

@ -33,12 +33,16 @@ import org.apache.nifi.web.api.dto.NodeDTO;
import org.apache.nifi.web.api.dto.search.NodeSearchResultDTO;
import org.apache.nifi.web.api.entity.ClusterEntity;
import org.apache.nifi.web.api.entity.ClusterSearchResultsEntity;
import org.apache.nifi.web.api.entity.NodeEntity;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
@ -62,20 +66,6 @@ public class ClusterResource extends ApplicationResource {
private NiFiServiceFacade serviceFacade;
private NiFiProperties properties;
/**
* Locates the ClusterConnection sub-resource.
*
* @return node resource
*/
@Path("/nodes")
@ApiOperation(
value = "Gets the node resource",
response = NodeResource.class
)
public NodeResource getNodeResource() {
return resourceContext.getResource(NodeResource.class);
}
/**
* Returns a 200 OK response to indicate this is a valid cluster endpoint.
*
@ -83,7 +73,7 @@ public class ClusterResource extends ApplicationResource {
*/
@HEAD
@Consumes(MediaType.WILDCARD)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces(MediaType.WILDCARD)
public Response getClusterHead() {
if (properties.isClusterManager()) {
return Response.ok().build();
@ -99,7 +89,7 @@ public class ClusterResource extends ApplicationResource {
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces(MediaType.APPLICATION_JSON)
// TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
@ApiOperation(
value = "Gets the contents of the cluster",
@ -144,7 +134,7 @@ public class ClusterResource extends ApplicationResource {
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces(MediaType.APPLICATION_JSON)
@Path("/search-results")
// TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
@ApiOperation(
@ -209,6 +199,174 @@ public class ClusterResource extends ApplicationResource {
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
/**
* Gets the contents of the specified node in this NiFi cluster.
*
* @param id The node id.
* @return A nodeEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("nodes/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
@ApiOperation(
value = "Gets a node in the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Read Only", type = "ROLE_MONITOR"),
@Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id) {
if (properties.isClusterManager()) {
// get the specified relationship
final NodeDTO dto = serviceFacade.getNode(id);
// create the response entity
final NodeEntity entity = new NodeEntity();
entity.setNode(dto);
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
/**
* Updates the contents of the specified node in this NiFi cluster.
*
* @param id The id of the node
* @param nodeEntity A nodeEntity
* @return A nodeEntity
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("nodes/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@ApiOperation(
value = "Updates a node in the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response updateNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id,
@ApiParam(
value = "The node configuration. The only configuration that will be honored at this endpoint is the status or primary flag.",
required = true
) NodeEntity nodeEntity) {
if (properties.isClusterManager()) {
if (nodeEntity == null || nodeEntity.getNode() == null) {
throw new IllegalArgumentException("Node details must be specified.");
}
// get the request node
final NodeDTO requestNodeDTO = nodeEntity.getNode();
if (!id.equals(requestNodeDTO.getNodeId())) {
throw new IllegalArgumentException(String.format("The node id (%s) in the request body does "
+ "not equal the node id of the requested resource (%s).", requestNodeDTO.getNodeId(), id));
}
// update the node
final NodeDTO node = serviceFacade.updateNode(requestNodeDTO);
// create the response entity
NodeEntity entity = new NodeEntity();
entity.setNode(node);
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
/**
* Removes the specified from this NiFi cluster.
*
* @param id The id of the node
* @return A nodeEntity
*/
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("nodes/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@ApiOperation(
value = "Removes a node from the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response deleteNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id) {
if (properties.isClusterManager()) {
serviceFacade.deleteNode(id);
// create the response entity
final NodeEntity entity = new NodeEntity();
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
// setters
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
this.serviceFacade = serviceFacade;

View File

@ -1,228 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.api;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.Authorization;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.IllegalClusterResourceRequestException;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.api.dto.NodeDTO;
import org.apache.nifi.web.api.entity.NodeEntity;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* RESTful endpoint for managing a cluster connection.
*/
@Api(hidden = true)
public class NodeResource extends ApplicationResource {
private NiFiServiceFacade serviceFacade;
private NiFiProperties properties;
/**
* Gets the contents of the specified node in this NiFi cluster.
*
* @param id The node id.
* @return A nodeEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
@ApiOperation(
value = "Gets a node in the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Read Only", type = "ROLE_MONITOR"),
@Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id) {
if (properties.isClusterManager()) {
// get the specified relationship
final NodeDTO dto = serviceFacade.getNode(id);
// create the response entity
final NodeEntity entity = new NodeEntity();
entity.setNode(dto);
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
/**
* Updates the contents of the specified node in this NiFi cluster.
*
* @param id The id of the node
* @param nodeEntity A nodeEntity
* @return A nodeEntity
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@ApiOperation(
value = "Updates a node in the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response updateNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id,
@ApiParam(
value = "The node configuration. The only configuration that will be honored at this endpoint is the status or primary flag.",
required = true
)
NodeEntity nodeEntity) {
if (properties.isClusterManager()) {
if (nodeEntity == null || nodeEntity.getNode() == null) {
throw new IllegalArgumentException("Node details must be specified.");
}
// get the request node
final NodeDTO requestNodeDTO = nodeEntity.getNode();
if (!id.equals(requestNodeDTO.getNodeId())) {
throw new IllegalArgumentException(String.format("The node id (%s) in the request body does "
+ "not equal the node id of the requested resource (%s).", requestNodeDTO.getNodeId(), id));
}
// update the node
final NodeDTO node = serviceFacade.updateNode(requestNodeDTO);
// create the response entity
NodeEntity entity = new NodeEntity();
entity.setNode(node);
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
/**
* Removes the specified from this NiFi cluster.
*
* @param id The id of the node
* @return A nodeEntity
*/
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}")
// TODO - @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@ApiOperation(
value = "Removes a node from the cluster",
response = NodeEntity.class,
authorizations = {
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response deleteNode(
@ApiParam(
value = "The node id.",
required = true
)
@PathParam("id") String id) {
if (properties.isClusterManager()) {
serviceFacade.deleteNode(id);
// create the response entity
final NodeEntity entity = new NodeEntity();
// generate the response
return generateOkResponse(entity).build();
}
throw new IllegalClusterResourceRequestException("Only a cluster manager can process the request.");
}
// setters
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
this.serviceFacade = serviceFacade;
}
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
}

View File

@ -297,10 +297,6 @@
<property name="serviceFacade" ref="serviceFacade"/>
<property name="properties" ref="nifiProperties"/>
</bean>
<bean id="nodeResource" class="org.apache.nifi.web.api.NodeResource" scope="singleton">
<property name="serviceFacade" ref="serviceFacade"/>
<property name="properties" ref="nifiProperties"/>
</bean>
<bean id="systemDiagnosticsResource" class="org.apache.nifi.web.api.SystemDiagnosticsResource" scope="singleton">
<property name="serviceFacade" ref="serviceFacade"/>
<property name="properties" ref="nifiProperties"/>

View File

@ -134,13 +134,19 @@ nf.ClusterTable = (function () {
* @argument {string} nodeId The node id
*/
var connect = function (nodeId) {
var entity = {
'node': {
'nodeId': nodeId,
'status': 'CONNECTING'
}
};
$.ajax({
type: 'PUT',
url: config.urls.nodes + '/' + encodeURIComponent(nodeId),
data: {
status: 'CONNECTING'
},
dataType: 'json'
data: JSON.stringify(entity),
dataType: 'json',
contentType: 'application/json'
}).done(function (response) {
var node = response.node;
@ -173,13 +179,19 @@ nf.ClusterTable = (function () {
* @argument {string} nodeId The node id
*/
var disconnect = function (nodeId) {
var entity = {
'node': {
'nodeId': nodeId,
'status': 'DISCONNECTING'
}
};
$.ajax({
type: 'PUT',
url: config.urls.nodes + '/' + encodeURIComponent(nodeId),
data: {
status: 'DISCONNECTING'
},
dataType: 'json'
data: JSON.stringify(entity),
dataType: 'json',
contentType: 'application/json'
}).done(function (response) {
var node = response.node;
@ -316,52 +328,6 @@ nf.ClusterTable = (function () {
}).fail(nf.Common.handleAjaxError);
};
/**
* Makes the specified node the primary node of the cluster.
*
* @argument {object} item The node item
*/
var makePrimary = function (item) {
$.ajax({
type: 'PUT',
url: config.urls.nodes + '/' + encodeURIComponent(item.nodeId),
data: {
primary: true
},
dataType: 'json'
}).done(function (response) {
var grid = $('#cluster-table').data('gridInstance');
var data = grid.getData();
var node = response.node;
// start the update
data.beginUpdate();
data.updateItem(node.nodeId, node);
// need to find the previous primary node
// get the property grid data
var clusterItems = data.getItems();
$.each(clusterItems, function (i, otherNode) {
// attempt to identify the previous primary node
if (node.nodeId !== otherNode.nodeId && otherNode.primary === true) {
// reset its primary status
otherNode.primary = false;
otherNode.status = 'CONNECTED';
// set the new node state
data.updateItem(otherNode.nodeId, otherNode);
// no need to continue processing
return false;
}
});
// end the update
data.endUpdate();
}).fail(nf.Common.handleAjaxError);
};
return {
/**
* Initializes the cluster list.
@ -423,7 +389,13 @@ nf.ClusterTable = (function () {
// define a custom formatter for the more details column
var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
return '<img src="images/iconDetails.png" title="View Details" class="pointer show-node-details" style="margin-top: 4px;"/>';
var markup = '<img src="images/iconDetails.png" title="View Details" class="pointer show-node-details" style="margin-top: 2px;"/>';
if (dataContext.primary === true) {
markup += '&nbsp;<img src="images/iconPrimary.png" title="Primary Node" style="margin-top: 2px;"/>';
}
return markup;
};
// define a custom formatter for the run status column
@ -431,15 +403,6 @@ nf.ClusterTable = (function () {
return formatNodeAddress(dataContext);
};
// define a custom formatter for the status column
var statusFormatter = function (row, cell, value, columnDef, dataContext) {
if (dataContext.primary === true) {
return value + ', PRIMARY';
} else {
return value;
}
};
// function for formatting the last accessed time
var valueFormatter = function (row, cell, value, columnDef, dataContext) {
return nf.Common.formatValue(value);
@ -450,7 +413,7 @@ nf.ClusterTable = (function () {
{id: 'node', field: 'node', name: 'Node Address', formatter: nodeFormatter, resizable: true, sortable: true},
{id: 'activeThreadCount', field: 'activeThreadCount', name: 'Active Thread Count', resizable: true, sortable: true, defaultSortAsc: false},
{id: 'queued', field: 'queued', name: '<span class="queued-title">Queue</span>&nbsp;/&nbsp;<span class="queued-size-title">Size</span>', resizable: true, sortable: true, defaultSortAsc: false},
{id: 'status', field: 'status', name: 'Status', formatter: statusFormatter, resizable: true, sortable: true},
{id: 'status', field: 'status', name: 'Status', resizable: true, sortable: true},
{id: 'uptime', field: 'nodeStartTime', name: 'Uptime', formatter: valueFormatter, resizable: true, sortable: true, defaultSortAsc: false},
{id: 'heartbeat', field: 'heartbeat', name: 'Last Heartbeat', formatter: valueFormatter, resizable: true, sortable: true, defaultSortAsc: false}
];
@ -483,7 +446,7 @@ nf.ClusterTable = (function () {
} else if (canDisconnect) {
var actions = '<img src="images/iconDisconnect.png" title="Disconnect" class="pointer prompt-for-disconnect" style="margin-top: 2px;"/>';
if (canBecomePrimary) {
actions += '&nbsp;<img src="images/iconPrimary.png" title="Make Primary" class="pointer make-primary" style="margin-top: 2px;"/>';
}
return actions;
} else {
@ -545,8 +508,6 @@ nf.ClusterTable = (function () {
promptForRemoval(item);
} else if (target.hasClass('prompt-for-disconnect')) {
promptForDisconnect(item);
} else if (target.hasClass('make-primary')) {
makePrimary(item);
}
} else if (clusterGrid.getColumns()[args.cell].id === 'moreDetails') {
if (target.hasClass('show-node-details')) {