diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/cluster.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/cluster.css
index a4224be56d..c71077d0b5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/cluster.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/cluster.css
@@ -27,8 +27,8 @@
overflow: auto;
}
-#cluster-header-and-filter {
- height: 98px;
+#cluster-filter-controls {
+ z-index: 1;
}
#cluster-header-text {
@@ -39,6 +39,10 @@
margin-bottom: 30px;
}
+#cluster-tabs-content {
+ top: 80px;
+}
+
#cluster-refresh-container {
position: absolute;
bottom: 0px;
@@ -63,17 +67,27 @@
padding-top: 10px;
}
-/* counters table */
+/* cluster table */
-#cluster-table {
+.cluster-tabbed-table {
position: absolute;
- top: 98px;
+ top: 70px;
left: 0px;
bottom: 47px;
right: 0px;
min-height: 150px;
}
+#cluster-tabs-content .slick-cell.cell-right {
+ text-align: right;
+ padding-right: 16px;
+ padding-left: 2px;
+}
+
+#cluster-tabs-content .slick-header-column.header-right {
+ text-align: right;
+}
+
span.sorted {
text-decoration: underline;
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
index e9a9e0e024..8bc169b302 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
@@ -24,13 +24,261 @@ nf.ClusterTable = (function () {
*/
var config = {
primaryNode: 'Primary Node',
- clusterCoorindator: 'Cluster Coordinator',
+ clusterCoordinator: 'Cluster Coordinator',
urls: {
cluster: '../nifi-api/controller/cluster',
- nodes: '../nifi-api/controller/cluster/nodes'
- }
+ nodes: '../nifi-api/controller/cluster/nodes',
+ systemDiagnostics: '../nifi-api/system-diagnostics'
+ },
+ data: [{
+ name: 'cluster',
+ update: refreshClusterData,
+ isAuthorized: nf.Common.canAccessController
+ },{
+ name: 'systemDiagnostics',
+ update: refreshSystemDiagnosticsData,
+ isAuthorized: nf.Common.canAccessSystem
+ }
+ ]
};
+ var commonTableOptions = {
+ forceFitColumns: true,
+ enableTextSelectionOnCells: true,
+ enableCellNavigation: false,
+ enableColumnReorder: false,
+ autoEdit: false,
+ rowHeight: 24
+ };
+
+ var nodesTab = {
+ name: 'Nodes',
+ data: {
+ dataSet: 'cluster',
+ update: updateNodesTableData
+ },
+ tabContentId: 'cluster-nodes-tab-content',
+ tableId: 'cluster-nodes-table',
+ tableColumnModel: createNodeTableColumnModel,
+ tableIdColumn: 'nodeId',
+ tableOptions: commonTableOptions,
+ tableOnClick: nodesTableOnClick,
+ init: commonTableInit,
+ onSort: sort,
+ onTabSelected: onSelectTab,
+ filterOptions: [{
+ text: 'by address',
+ value: 'address'
+ }, {
+ text: 'by status',
+ value: 'status'
+ }]
+ };
+
+ var jvmTab = {
+ name: 'JVM',
+ data: {
+ dataSet: 'systemDiagnostics',
+ update: updateJvmTableData
+ },
+ tabContentId: 'cluster-jvm-tab-content',
+ tableId: 'cluster-jvm-table',
+ tableColumnModel: [
+ {id: 'node', field: 'node', name: 'Node Address', sortable: true, resizable: true},
+ {id: 'heapMax', field: 'maxHeap', name: 'Heap Max', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'heapTotal', field: 'totalHeap', name: 'Heap Total', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'heapUsed', field: 'usedHeap', name: 'Heap Used', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'heapUtilPct', field: 'heapUtilization', name: 'Heap Utilization', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'nonHeapTotal', field: 'totalNonHeap', name: 'Non-Heap Total', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'nonHeapUsed', field: 'usedNonHeap', name: 'Non-Heap Used', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'gcOldGen', field: 'gcOldGen', name: 'G1 Old Generation', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'gcNewGen', field: 'gcNewGen', name: 'G1 Young Generation', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'}
+ ],
+ tableIdColumn: 'id',
+ tableOptions: commonTableOptions,
+ tableOnClick: null,
+ init: commonTableInit,
+ onSort: sort,
+ onTabSelected: onSelectTab,
+ filterOptions: [{
+ text: 'by address',
+ value: 'node'
+ }]
+ };
+
+ var systemTab = {
+ name: 'System',
+ data: {
+ dataSet: 'systemDiagnostics',
+ update: updateSystemTableData
+ },
+ tabContentId: 'cluster-system-tab-content',
+ tableId: 'cluster-system-table',
+ tableColumnModel: [
+ {id: 'node', field: 'node', name: 'Node Address', sortable: true, resizable: true},
+ {id: 'processors', field: 'processors', name: 'Processors', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'processorLoadAverage', field: 'processorLoadAverage', name: 'Processor Load Average', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'totalThreads', field: 'totalThreads', name: 'Total Threads', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'daemonThreads', field: 'daemonThreads', name: 'Daemon Threads', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'}
+ ],
+ tableIdColumn: 'id',
+ tableOptions: commonTableOptions,
+ tableOnClick: null,
+ init: commonTableInit,
+ onSort: sort,
+ onTabSelected: onSelectTab,
+ filterOptions: [{
+ text: 'by address',
+ value: 'node'
+ }]
+ };
+
+ var flowFileTab = {
+ name: 'FlowFile Storage',
+ data: {
+ dataSet: 'systemDiagnostics',
+ update: updateFlowFileTableData
+ },
+ tabContentId: 'cluster-flowfile-tab-content',
+ tableId: 'cluster-flowfile-table',
+ tableColumnModel: [
+ {id: 'node', field: 'node', name: 'Node Address', sortable: true, resizable: true},
+ {id: 'ffRepoTotal', field: 'ffRepoTotal', name: 'Total Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'ffRepoUsed', field: 'ffRepoUsed', name: 'Used Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'ffRepoFree', field: 'ffRepoFree', name: 'Free Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'ffStoreUtil', field: 'ffRepoUtil', name: 'Utilization', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'}
+ ],
+ tableIdColumn: 'id',
+ tableOptions: commonTableOptions,
+ tableOnClick: null,
+ init: commonTableInit,
+ onSort: sort,
+ onTabSelected: onSelectTab,
+ filterOptions: [{
+ text: 'by address',
+ value: 'node'
+ }]
+ };
+
+ var contentTab = {
+ name: 'Content Storage',
+ data: {
+ dataSet: 'systemDiagnostics',
+ update: updateContentTableData
+ },
+ tabContentId: 'cluster-content-tab-content',
+ tableId: 'cluster-content-table',
+ tableColumnModel: [
+ {id: 'node', field: 'node', name: 'Node Address', sortable: true, resizable: true},
+ {id: 'contentRepoId', field: 'contentRepoId', name: 'Content Repository', sortable: true, resizable: true},
+ {id: 'contentRepoTotal', field: 'contentRepoTotal', name: 'Total Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'contentRepoUsed', field: 'contentRepoUsed', name: 'Used Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'contentRepoFree', field: 'contentRepoFree', name: 'Free Space', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'},
+ {id: 'contentRepoUtil', field: 'contentRepoUtil', name: 'Utilization', sortable: true, resizable: true, cssClass: 'cell-right', headerCssClass: 'header-right'}
+ ],
+ tableIdColumn: 'id',
+ tableOptions: commonTableOptions,
+ tableOnClick: null,
+ init: commonTableInit,
+ onSort: sort,
+ onTabSelected: onSelectTab,
+ filterOptions: [{
+ text: 'by address',
+ value: 'node'
+ }, {
+ text: 'by repository',
+ value: 'contentRepoId'
+ }]
+ };
+
+ var clusterTabs = [nodesTab, systemTab, jvmTab, flowFileTab, contentTab];
+ var tabsByName = {};
+ var dataSetHandlers = {};
+
+ /**
+ * Click handler for the Nodes table options.
+ */
+ function nodesTableOnClick (e, args, target, item) {
+ if (nodesTab.grid.getColumns()[args.cell].id === 'actions') {
+ if (target.hasClass('prompt-for-connect')) {
+ promptForConnect(item);
+ } else if (target.hasClass('prompt-for-removal')) {
+ promptForRemoval(item);
+ } else if (target.hasClass('prompt-for-disconnect')) {
+ promptForDisconnect(item);
+ }
+ } else if (nodesTab.grid.getColumns()[args.cell].id === 'moreDetails') {
+ if (target.hasClass('show-node-details')) {
+ showNodeDetails(item);
+ }
+ }
+ }
+
+ /**
+ * Creates the Slick Grid column model for the Nodes table.
+ */
+ function createNodeTableColumnModel () {
+ var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
+ return '
';
+ };
+
+ // define a custom formatter for the run status column
+ var nodeFormatter = function (row, cell, value, columnDef, dataContext) {
+ return formatNodeAddress(dataContext);
+ };
+
+ // function for formatting the last accessed time
+ var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+ return nf.Common.formatValue(value);
+ };
+
+ // define a custom formatter for the status column
+ var statusFormatter = function (row, cell, value, columnDef, dataContext) {
+ return formatNodeStatus(dataContext);
+ };
+
+ var columnModel = [
+ {id: 'moreDetails', name: ' ', sortable: false, resizable: false, formatter: moreDetailsFormatter, width: 50, maxWidth: 50},
+ {id: 'formattedNodeAddress', field: 'formattedNodeAddress', 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: '
Queue /
Size', resizable: true, sortable: true, defaultSortAsc: false},
+ {id: 'status', field: 'status', name: 'Status', formatter: statusFormatter, 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}
+ ];
+
+ // only allow the admin to modify the cluster
+ if (nf.Common.canModifyController()) {
+ var actionFormatter = function (row, cell, value, columnDef, dataContext) {
+ var canDisconnect = false;
+ var canConnect = false;
+
+ // determine if this node is already the primary
+ var isPrimary = dataContext.primary;
+
+ // determine the current status
+ if (dataContext.status === 'CONNECTED' || dataContext.status === 'CONNECTING') {
+ canDisconnect = true;
+ } else if (dataContext.status === 'DISCONNECTED') {
+ canConnect = true;
+ }
+
+ // return the appropriate markup
+ if (canConnect) {
+ return '
';
+ } else if (canDisconnect) {
+ return '
';
+ } else {
+ return '
';
+ }
+ };
+
+ columnModel.push({id: 'actions', label: ' ', formatter: actionFormatter, resizable: false, sortable: false, width: 80, maxWidth: 80});
+ }
+
+ return columnModel;
+ }
+
var prevColumn, count;
/**
@@ -39,7 +287,7 @@ nf.ClusterTable = (function () {
* @param {object} sortDetails
* @param {object} data
*/
- var sort = function (sortDetails, data) {
+ function sort (sortDetails, dataView, tab) {
// defines a function for sorting
var comparer = function (a, b) {
if (sortDetails.columnId === 'heartbeat' || sortDetails.columnId === 'uptime') {
@@ -51,27 +299,40 @@ nf.ClusterTable = (function () {
var bSplit = b[sortDetails.columnId].split(/ \/ /);
var mod = count % 4;
if (mod < 2) {
- $('#cluster-table span.queued-title').addClass('sorted');
+ $('#cluster-nodes-table span.queued-title').addClass('sorted');
var aCount = nf.Common.parseCount(aSplit[0]);
var bCount = nf.Common.parseCount(bSplit[0]);
return aCount - bCount;
} else {
- $('#cluster-table span.queued-size-title').addClass('sorted');
+ $('#cluster-nodes-table span.queued-size-title').addClass('sorted');
var aSize = nf.Common.parseSize(aSplit[1]);
var bSize = nf.Common.parseSize(bSplit[1]);
return aSize - bSize;
}
+ } else if (sortDetails.columnId === 'maxHeap' || sortDetails.columnId === 'totalHeap' || sortDetails.columnId === 'usedHeap'
+ || sortDetails.columnId === 'totalNonHeap' || sortDetails.columnId === 'usedNonHeap'
+ || sortDetails.columnId === 'ffRepoTotal' || sortDetails.columnId === 'ffRepoUsed'
+ || sortDetails.columnId === 'ffRepoFree' || sortDetails.columnId === 'contentRepoTotal'
+ || sortDetails.columnId === 'contentRepoUsed' || sortDetails.columnId === 'contentRepoFree') {
+ var aSize = nf.Common.parseSize(a[sortDetails.columnId]);
+ var bSize = nf.Common.parseSize(b[sortDetails.columnId]);
+ return aSize - bSize;
+ } else if (sortDetails.columnId === 'totalThreads' || sortDetails.columnId === 'daemonThreads'
+ || sortDetails.columnId === 'processors') {
+ var aCount = nf.Common.parseCount(a[sortDetails.columnId]);
+ var bCount = nf.Common.parseCount(b[sortDetails.columnId]);
+ return aCount - bCount;
+ } else if (sortDetails.columnId === 'gcOldGen' || sortDetails.columnId === 'gcNewGen') {
+ var aSplit = a[sortDetails.columnId].split(/ /);
+ var bSplit = b[sortDetails.columnId].split(/ /);
+ var aCount = nf.Common.parseCount(aSplit[0]);
+ var bCount = nf.Common.parseCount(bSplit[0]);
+ return aCount - bCount;
} else if (sortDetails.columnId === 'status') {
- var aString = nf.Common.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
- if (a.roles.includes(config.primaryNode)) {
- aString += ', PRIMARY';
- }
- var bString = nf.Common.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
- if (b.roles.includes(config.primaryNode)) {
- bString += ', PRIMARY';
- }
- return aString === bString ? 0 : aString > bString ? 1 : -1;
- } else if (sortDetails.columnId === 'node') {
+ var aStatus = formatNodeStatus(a);
+ var bStatus = formatNodeStatus(b);
+ return aStatus === bStatus ? 0 : aStatus > bStatus ? 1 : -1;
+ } else if (sortDetails.columnId === 'formattedNodeAddress') {
var aNode = formatNodeAddress(a);
var bNode = formatNodeAddress(b);
return aNode === bNode ? 0 : aNode > bNode ? 1 : -1;
@@ -83,8 +344,8 @@ nf.ClusterTable = (function () {
};
// remove previous sort indicators
- $('#cluster-table span.queued-title').removeClass('sorted');
- $('#cluster-table span.queued-size-title').removeClass('sorted');
+ $('#cluster-nodes-table span.queued-title').removeClass('sorted');
+ $('#cluster-nodes-table span.queued-size-title').removeClass('sorted');
// update/reset the count as appropriate
if (prevColumn !== sortDetails.columnId) {
@@ -94,14 +355,14 @@ nf.ClusterTable = (function () {
}
// perform the sort
- data.sort(comparer, sortDetails.sortAsc);
+ dataView.sort(comparer, sortDetails.sortAsc);
// record the previous table and sorted column
prevColumn = sortDetails.columnId;
};
/**
- * Formats the address for the specified noe.
+ * Formats the address for the specified node.
*
* @param {object} node
* @returns {string}
@@ -110,6 +371,23 @@ nf.ClusterTable = (function () {
return nf.Common.escapeHtml(node.address) + ':' + nf.Common.escapeHtml(node.apiPort);
};
+ /**
+ * Formats the status for the specified node.
+ *
+ * @param {object} node
+ * @returns {string}
+ */
+ var formatNodeStatus = function (node) {
+ var markup = node.status;
+ if (node.roles.includes(config.primaryNode)) {
+ markup += ', PRIMARY';
+ }
+ if (node.roles.includes(config.clusterCoordinator)) {
+ markup += ', COORDINATOR';
+ }
+ return markup;
+ }
+
/**
* Prompts to verify node connection.
*
@@ -138,7 +416,6 @@ nf.ClusterTable = (function () {
'status': 'CONNECTING'
}
};
-
$.ajax({
type: 'PUT',
url: config.urls.nodes + '/' + encodeURIComponent(nodeId),
@@ -149,7 +426,7 @@ nf.ClusterTable = (function () {
var node = response.node;
// update the node in the table
- var clusterGrid = $('#cluster-table').data('gridInstance');
+ var clusterGrid = $('#cluster-nodes-table').data('gridInstance');
var clusterData = clusterGrid.getData();
clusterData.updateItem(node.nodeId, node);
}).fail(nf.Common.handleAjaxError);
@@ -194,7 +471,7 @@ nf.ClusterTable = (function () {
var node = response.node;
// update the node in the table
- var clusterGrid = $('#cluster-table').data('gridInstance');
+ var clusterGrid = $('#cluster-nodes-table').data('gridInstance');
var clusterData = clusterGrid.getData();
clusterData.updateItem(node.nodeId, node);
}).fail(nf.Common.handleAjaxError);
@@ -228,7 +505,7 @@ nf.ClusterTable = (function () {
dataType: 'json'
}).done(function () {
// get the table and update the row accordingly
- var clusterGrid = $('#cluster-table').data('gridInstance');
+ var clusterGrid = $('#cluster-nodes-table').data('gridInstance');
var clusterData = clusterGrid.getData();
clusterData.deleteItem(nodeId);
}).fail(nf.Common.handleAjaxError);
@@ -247,22 +524,36 @@ nf.ClusterTable = (function () {
* Applies the filter found in the filter expression text field.
*/
var applyFilter = function () {
- // get the dataview
- var clusterGrid = $('#cluster-table').data('gridInstance');
+ var visibleTab = getSelectedTab();
+ if (!visibleTab) {
+ return;
+ }
+
+ var grid = visibleTab.grid;
// ensure the grid has been initialized
- if (nf.Common.isDefinedAndNotNull(clusterGrid)) {
- var clusterData = clusterGrid.getData();
+ if (nf.Common.isDefinedAndNotNull(grid)) {
+ var gridData = grid.getData();
// update the search criteria
- clusterData.setFilterArgs({
+ gridData.setFilterArgs({
searchString: getFilterText(),
property: $('#cluster-filter-type').combo('getSelectedOption').value
});
- clusterData.refresh();
+ gridData.refresh();
}
};
+ var getSelectedTab = function () {
+ var selectedTab = null;
+ clusterTabs.forEach(function (tab) {
+ if ($('#' + tab.tableId).is(':visible')) {
+ selectedTab = tab;
+ }
+ });
+ return selectedTab;
+ }
+
/**
* Performs the filtering.
*
@@ -283,10 +574,40 @@ nf.ClusterTable = (function () {
return false;
}
+ var searchText = item[args.property];
+ if (args.property === 'address') {
+ searchText = formatNodeAddress(item);
+ } else if (args.property === 'status') {
+ searchText = formatNodeStatus(item);
+ }
+
// perform the filter
- return item[args.property].search(filterExp) >= 0;
+ return searchText.search(filterExp) >= 0;
};
+ /**
+ * Updates count of displayed and total rows.
+ */
+ function updateFilterStats (selectedTab) {
+ if (!selectedTab) {
+ selectedTab = getSelectedTab();
+ }
+ if (selectedTab.dataView) {
+ var displayedRows = selectedTab.dataView.getLength();
+ var totalRows = selectedTab.rowCount;
+ $('#displayed-rows').text(displayedRows);
+ $('#total-rows').text(totalRows);
+ }
+ }
+
+ /**
+ * Clears any existing table filter.
+ */
+ var clearFilter = function () {
+ $('#cluster-filter').val('');
+ applyFilter();
+ }
+
/**
* Show the node details.
*
@@ -320,7 +641,282 @@ nf.ClusterTable = (function () {
$('#node-details-dialog').modal('show');
}).fail(nf.Common.handleAjaxError);
};
-
+
+ /**
+ * Applies system diagnostics data to the JVM tab.
+ */
+ function updateJvmTableData (systemDiagnosticsResponse) {
+ if (nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics)
+ && nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots)) {
+
+ var jvmTableRows = [];
+ systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots.forEach(function (nodeSnapshot) {
+ var snapshot = nodeSnapshot.snapshot;
+ jvmTableRows.push({
+ id: nodeSnapshot.nodeId,
+ node: nodeSnapshot.address + ':' + nodeSnapshot.apiPort,
+ address: nodeSnapshot.address,
+ maxHeap: snapshot.maxHeap,
+ totalHeap: snapshot.totalHeap,
+ usedHeap: snapshot.usedHeap,
+ heapUtilization: snapshot.heapUtilization,
+ maxNonHeap: snapshot.maxNonHeap,
+ totalNonHeap: snapshot.totalNonHeap,
+ usedNonHeap: snapshot.usedNonHeap,
+ gcOldGen: snapshot.garbageCollection[0].collectionCount + ' times (' +
+ snapshot.garbageCollection[0].collectionTime + ')',
+ gcNewGen: snapshot.garbageCollection[1].collectionCount + ' times (' +
+ snapshot.garbageCollection[1].collectionTime + ')'
+ });
+ });
+ jvmTab.rowCount = jvmTableRows.length;
+ jvmTab.dataView.setItems(jvmTableRows);
+ jvmTab.dataView.reSort();
+ jvmTab.grid.invalidate();
+ } else {
+ jvmTab.rowCount = 0;
+ }
+ }
+
+ /**
+ * Applies system diagnostics data to the System tab.
+ */
+ function updateSystemTableData (systemDiagnosticsResponse) {
+ if (nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics)
+ && nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots)) {
+
+ var systemTableRows = [];
+ systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots.forEach(function (nodeSnapshot) {
+ var snapshot = nodeSnapshot.snapshot;
+ systemTableRows.push({
+ id: nodeSnapshot.nodeId,
+ node: nodeSnapshot.address + ':' + nodeSnapshot.apiPort,
+ address: nodeSnapshot.address,
+ processors: snapshot.availableProcessors,
+ processorLoadAverage: snapshot.processorLoadAverage,
+ totalThreads: snapshot.totalThreads,
+ daemonThreads: snapshot.daemonThreads
+ });
+ });
+ systemTab.rowCount = systemTableRows.length;
+ systemTab.dataView.setItems(systemTableRows);
+ systemTab.dataView.reSort();
+ systemTab.grid.invalidate();
+ } else {
+ systemTab.rowCount = 0;
+ }
+ }
+
+ /**
+ * Applies system diagnostics data to the FlowFile Storage tab.
+ */
+ function updateFlowFileTableData (systemDiagnosticsResponse) {
+ if (nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics)
+ && nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots)) {
+
+ var flowFileTableRows = [];
+ systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots.forEach(function (nodeSnapshot) {
+ var snapshot = nodeSnapshot.snapshot;
+ flowFileTableRows.push({
+ id: nodeSnapshot.nodeId,
+ node: nodeSnapshot.address + ':' + nodeSnapshot.apiPort,
+ address: nodeSnapshot.address,
+ ffRepoTotal: snapshot.flowFileRepositoryStorageUsage.totalSpace,
+ ffRepoUsed: snapshot.flowFileRepositoryStorageUsage.usedSpace,
+ ffRepoFree: snapshot.flowFileRepositoryStorageUsage.freeSpace,
+ ffRepoUtil: snapshot.flowFileRepositoryStorageUsage.utilization
+ });
+ });
+ flowFileTab.rowCount = flowFileTableRows.length;
+ flowFileTab.dataView.setItems(flowFileTableRows);
+ flowFileTab.dataView.reSort();
+ flowFileTab.grid.invalidate();
+ } else {
+ flowFileTab.rowCount = 0;
+ }
+ }
+
+ /**
+ * Applies system diagnostics data to the Content Storage tab.
+ */
+ function updateContentTableData (systemDiagnosticsResponse) {
+ if (nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics)
+ && nf.Common.isDefinedAndNotNull(systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots)) {
+
+ var contentStorageTableRows = [];
+ systemDiagnosticsResponse.systemDiagnostics.nodeSnapshots.forEach(function (nodeSnapshot) {
+ var snapshot = nodeSnapshot.snapshot;
+ snapshot.contentRepositoryStorageUsage.forEach(function (contentRepoUsage) {
+ contentStorageTableRows.push({
+ id: nodeSnapshot.nodeId + ':' + contentRepoUsage.identifier,
+ address: nodeSnapshot.address,
+ node: nodeSnapshot.address + ':' + nodeSnapshot.apiPort,
+ contentRepoId: contentRepoUsage.identifier,
+ contentRepoTotal: contentRepoUsage.totalSpace,
+ contentRepoUsed: contentRepoUsage.usedSpace,
+ contentRepoFree: contentRepoUsage.freeSpace,
+ contentRepoUtil: contentRepoUsage.utilization
+ });
+ });
+ });
+
+ contentTab.rowCount = contentStorageTableRows.length;
+ contentTab.dataView.setItems(contentStorageTableRows);
+ contentTab.dataView.reSort();
+ contentTab.grid.invalidate();
+ } else {
+ contentTab.rowCount = 0;
+ }
+ }
+
+ /**
+ * Loads system diagnostics data for the cluster.
+ */
+ function refreshSystemDiagnosticsData () {
+ var systemDiagnosticsUri = config.urls.systemDiagnostics
+ var loadPromise = $.ajax({
+ type: 'GET',
+ url: systemDiagnosticsUri,
+ data: {
+ nodewise: true
+ },
+ dataType: 'json'
+ }).done(function (systemDiagnosticsResponse) {
+ var handlers = dataSetHandlers['systemDiagnostics'];
+ handlers.forEach(function (handler) {
+ handler(systemDiagnosticsResponse);
+ });
+ }).fail(nf.Common.handleAjaxError);
+ return loadPromise;
+ };
+
+ /**
+ * Generic initialization for Slick Grid tables
+ */
+ function commonTableInit (tab) {
+ var dataView = new Slick.Data.DataView({
+ inlineFilters: false
+ });
+ dataView.setItems([], tab.tableIdColumn);
+
+ dataView.setFilterArgs({
+ searchString: getFilterText(),
+ property: $('#cluster-filter-type').combo('getSelectedOption').value
+ });
+ dataView.setFilter(filter);
+
+ // initialize the sort
+ tab.onSort({
+ columnId: tab.tableIdColumn,
+ sortAsc: true
+ }, dataView);
+
+ // initialize the grid
+ var columnModel = tab.tableColumnModel;
+ if (typeof columnModel === 'function') {
+ columnModel = columnModel();
+ }
+ var grid = new Slick.Grid('#' + tab.tableId, dataView, columnModel, tab.tableOptions);
+ grid.setSelectionModel(new Slick.RowSelectionModel());
+ grid.setSortColumn(tab.tableIdColumn, true);
+ grid.onSort.subscribe(function (e, args) {
+ tab.onSort({
+ columnId: args.sortCol.field,
+ sortAsc: args.sortAsc
+ }, dataView, tab);
+ });
+
+ // wire up the dataview to the grid
+ dataView.onRowCountChanged.subscribe(function (e, args) {
+ grid.updateRowCount();
+ grid.render();
+ updateFilterStats(tab);
+ });
+ dataView.onRowsChanged.subscribe(function (e, args) {
+ grid.invalidateRows(args.rows);
+ grid.render();
+ });
+
+ // click events
+ if (tab.tableOnClick) {
+ grid.onClick.subscribe(function (e, args) {
+ var target = $(e.target);
+ var item = dataView.getItem(args.row);
+ tab.tableOnClick(e, args, target, item);
+ });
+ }
+
+ // hold onto an instance of the grid
+ $('#' + tab.tableId).data('gridInstance', grid);
+ tab.dataView = dataView;
+ tab.grid = grid;
+ };
+
+ /**
+ * Apply the cluster nodes data set to the table.
+ */
+ function updateNodesTableData (clusterResponse) {
+ var cluster = clusterResponse.cluster;
+
+ // ensure there are groups specified
+ if (nf.Common.isDefinedAndNotNull(cluster.nodes)) {
+ var clusterGrid = nodesTab.grid;
+ var clusterData = clusterGrid.getData();
+
+ // set the items
+ nodesTab.rowCount = cluster.nodes.length;
+ clusterData.setItems(cluster.nodes);
+ clusterData.reSort();
+ clusterGrid.invalidate();
+
+ // update the stats last refreshed timestamp
+ $('#cluster-last-refreshed').text(cluster.generated);
+ } else {
+ $('#total-nodes').text('0');
+ }
+ }
+
+ /**
+ * Refreshes cluster data sets from the server.
+ */
+ function refreshClusterData () {
+ var clusterNodesDataPromise = $.ajax({
+ type: 'GET',
+ url: config.urls.cluster,
+ dataType: 'json'
+ }).done(function (response) {
+ var handlers = dataSetHandlers['cluster'];
+ handlers.forEach(function (handler) {
+ handler(response);
+ });
+ }).fail(nf.Common.handleAjaxError);
+ return clusterNodesDataPromise;
+ }
+
+ /**
+ * Event handler triggered when the user switches tabs.
+ */
+ function onSelectTab (tab) {
+ // Resize table
+ var tabGrid = tab.grid;
+ if (nf.Common.isDefinedAndNotNull(tabGrid)) {
+ tabGrid.resizeCanvas();
+ }
+
+ // Clear filter text
+ clearFilter();
+
+ // Reset filter options
+ $('#cluster-filter-type').combo({
+ options: tab.filterOptions,
+ select: function (option) {
+ applyFilter();
+ }
+ });
+
+ updateFilterStats(tab);
+ }
+
return {
/**
* Initializes the cluster list.
@@ -358,17 +954,51 @@ nf.ClusterTable = (function () {
applyFilter();
});
- // filter type
- $('#cluster-filter-type').combo({
- options: [{
- text: 'by address',
- value: 'address'
- }, {
- text: 'by status',
- value: 'status'
- }],
- select: function (option) {
- applyFilter();
+ // Authorize data sets
+ var dataSetAuthorized = {};
+ config.data = config.data.filter(function (dataSetConfig) {
+ dataSetConfig.authorized = dataSetConfig.isAuthorized();
+ dataSetAuthorized[dataSetConfig.name] = dataSetConfig.authorized;
+ if (dataSetConfig.authorized) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ // Filter tabs to authorized data sets
+ clusterTabs = clusterTabs.filter(function (tab) {
+ var tabDataSet = tab.data.dataSet;
+ if (dataSetAuthorized[tabDataSet]) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ clusterTabs.forEach(function (tab) {
+ tabsByName[tab.name] = tab;
+ var dataSetHandlerList = dataSetHandlers[tab.data.dataSet];
+ if (dataSetHandlerList) {
+ dataSetHandlers[tab.data.dataSet] = dataSetHandlerList.concat([tab.data.update]);
+ } else {
+ dataSetHandlers[tab.data.dataSet] = [tab.data.update];
+ }
+ });
+
+ // Initialize tab set
+ $('#cluster-tabs').tabbs({
+ tabStyle: 'tab',
+ selectedTabStyle: 'selected-tab',
+ scrollableTabContentStyle: 'scrollable',
+ tabs: clusterTabs,
+ select: function () {
+ var tab = $(this).text();
+ var selectedTab = tabsByName[tab];
+ if (selectedTab) {
+ selectedTab.onTabSelected(selectedTab);
+ } else {
+ console.error('Failed to match tab: ', tab, tabsByName);
+ }
}
});
@@ -377,190 +1007,41 @@ nf.ClusterTable = (function () {
nf.ClusterTable.resetTableSize();
});
- // define a custom formatter for the more details column
- var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
- return '
';
- };
-
- // define a custom formatter for the run status column
- var nodeFormatter = function (row, cell, value, columnDef, dataContext) {
- return formatNodeAddress(dataContext);
- };
-
- // function for formatting the last accessed time
- var valueFormatter = function (row, cell, value, columnDef, dataContext) {
- return nf.Common.formatValue(value);
- };
-
- // define a custom formatter for the status column
- var statusFormatter = function (row, cell, value, columnDef, dataContext) {
- var markup = value;
- if (dataContext.roles.includes(config.primaryNode)) {
- value += ', PRIMARY';
- }
- if (dataContext.roles.includes(config.clusterCoorindator)) {
- value += ', COORDINATOR';
- }
- return value;
- };
-
- var columnModel = [
- {id: 'moreDetails', name: ' ', sortable: false, resizable: false, formatter: moreDetailsFormatter, width: 50, maxWidth: 50},
- {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: '
Queue /
Size', resizable: true, sortable: true, defaultSortAsc: false},
- {id: 'status', field: 'status', name: 'Status', formatter: statusFormatter, 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}
- ];
-
- // only allow the admin to modify the cluster
- if (nf.Common.canModifyController()) {
- // function for formatting the actions column
- var actionFormatter = function (row, cell, value, columnDef, dataContext) {
- var canDisconnect = false;
- var canConnect = false;
-
- // determine the current status
- if (dataContext.status === 'CONNECTED' || dataContext.status === 'CONNECTING') {
- canDisconnect = true;
- } else if (dataContext.status === 'DISCONNECTED') {
- canConnect = true;
- }
-
- // return the appropriate markup
- if (canConnect) {
- return '
';
- } else if (canDisconnect) {
- return '
';
- } else {
- return '
';
- }
- };
-
- columnModel.push({id: 'actions', label: ' ', formatter: actionFormatter, resizable: false, sortable: false, width: 80, maxWidth: 80});
- }
-
- var clusterOptions = {
- forceFitColumns: true,
- enableTextSelectionOnCells: true,
- enableCellNavigation: false,
- enableColumnReorder: false,
- autoEdit: false,
- rowHeight: 24
- };
-
- // initialize the dataview
- var clusterData = new Slick.Data.DataView({
- inlineFilters: false
- });
- clusterData.setItems([], 'nodeId');
- clusterData.setFilterArgs({
- searchString: getFilterText(),
- property: $('#cluster-filter-type').combo('getSelectedOption').value
- });
- clusterData.setFilter(filter);
-
- // initialize the sort
- sort({
- columnId: 'node',
- sortAsc: true
- }, clusterData);
-
- // initialize the grid
- var clusterGrid = new Slick.Grid('#cluster-table', clusterData, columnModel, clusterOptions);
- clusterGrid.setSelectionModel(new Slick.RowSelectionModel());
- clusterGrid.setSortColumn('node', true);
- clusterGrid.onSort.subscribe(function (e, args) {
- sort({
- columnId: args.sortCol.id,
- sortAsc: args.sortAsc
- }, clusterData);
- });
-
- // configure a click listener
- clusterGrid.onClick.subscribe(function (e, args) {
- var target = $(e.target);
-
- // get the node at this row
- var item = clusterData.getItem(args.row);
-
- // determine the desired action
- if (clusterGrid.getColumns()[args.cell].id === 'actions') {
- if (target.hasClass('prompt-for-connect')) {
- promptForConnect(item);
- } else if (target.hasClass('prompt-for-removal')) {
- promptForRemoval(item);
- } else if (target.hasClass('prompt-for-disconnect')) {
- promptForDisconnect(item);
- }
- } else if (clusterGrid.getColumns()[args.cell].id === 'moreDetails') {
- if (target.hasClass('show-node-details')) {
- showNodeDetails(item);
- }
+ // initialize tabs
+ clusterTabs.forEach(function (tab) {
+ try {
+ tab.init(tab);
+ } catch (ex) {
+ console.error('Failed to initialize tab', tab, ex);
}
});
-
- // wire up the dataview to the grid
- clusterData.onRowCountChanged.subscribe(function (e, args) {
- clusterGrid.updateRowCount();
- clusterGrid.render();
-
- // update the total number of displayed processors
- $('#displayed-nodes').text(args.current);
- });
- clusterData.onRowsChanged.subscribe(function (e, args) {
- clusterGrid.invalidateRows(args.rows);
- clusterGrid.render();
- });
-
- // hold onto an instance of the grid
- $('#cluster-table').data('gridInstance', clusterGrid);
-
- // initialize the number of displayed items
- $('#displayed-nodes').text('0');
},
/**
* Update the size of the grid based on its container's current size.
*/
resetTableSize: function () {
- var clusterGrid = $('#cluster-table').data('gridInstance');
- if (nf.Common.isDefinedAndNotNull(clusterGrid)) {
- clusterGrid.resizeCanvas();
- }
+ clusterTabs.forEach(function (tab) {
+ if (tab && tab.grid) {
+ tab.grid.resizeCanvas();
+ }
+ });
},
/**
* Load the processor cluster table.
*/
loadClusterTable: function () {
- return $.ajax({
- type: 'GET',
- url: config.urls.cluster,
- dataType: 'json'
- }).done(function (response) {
- var cluster = response.cluster;
-
- // ensure there are groups specified
- if (nf.Common.isDefinedAndNotNull(cluster.nodes)) {
- var clusterGrid = $('#cluster-table').data('gridInstance');
- var clusterData = clusterGrid.getData();
-
- // set the items
- clusterData.setItems(cluster.nodes);
- clusterData.reSort();
- clusterGrid.invalidate();
-
- // update the stats last refreshed timestamp
- $('#cluster-last-refreshed').text(cluster.generated);
-
- // update the total number of processors
- $('#total-nodes').text(cluster.nodes.length);
- } else {
- $('#total-nodes').text('0');
- }
- }).fail(nf.Common.handleAjaxError);
+ var updateDataDeferreds = config.data.map(function (dataSetSpec) {
+ var dataSetDeferred = dataSetSpec.update();
+ return dataSetDeferred;
+ });
+ var aggregateDeferred = $.when.apply($, updateDataDeferreds);
+ aggregateDeferred = aggregateDeferred.done(function (aggregateResult) {
+ updateFilterStats(nodesTab);
+ });
+ return aggregateDeferred;
}
};
+
}());
\ 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/cluster/nf-cluster.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js
index 8d4da554bb..2e988cbcf8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js
@@ -128,17 +128,17 @@ nf.Cluster = (function () {
$('#cluster-table').css('bottom', 127);
$('#cluster-refresh-container').css('margin', 40);
}
-
- // configure the initial grid height
- nf.ClusterTable.resetTableSize();
};
// set the initial size
setBodySize();
- // create the counters table
+ // create the cluster table
nf.ClusterTable.init();
+ // resize to fit
+ nf.ClusterTable.resetTableSize();
+
// load the table
nf.ClusterTable.loadClusterTable().done(function () {
// once the table is initialized, finish initializing the page
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 96a3d681b0..898a5d85fb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -337,6 +337,19 @@ nf.Common = (function () {
}
},
+ /**
+ * Determines whether the current user can access system diagnostics.
+ *
+ * @returns {boolean}
+ */
+ canAccessSystem: function () {
+ if (nf.Common.isDefinedAndNotNull(nf.Common.currentUser)) {
+ return nf.Common.currentUser.systemPermissions.canRead === true;
+ } else {
+ return false;
+ }
+ },
+
/**
* Adds a mouse over effect for the specified selector using
* the specified styles.