diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp index 9e657437e288..fee7da7fd6e6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp @@ -42,22 +42,57 @@ -
-
- Back pressure object threshold -
+
+
+
+ Back Pressure
Object threshold +
+
+
+ +
-
- +
 
+
+
+  
Size threshold +
+
+
+ +
-
-
- Back pressure data size threshold -
+
+
+
+
+ Load Balance Strategy +
+
+
+
+
+
+
 
+
+
+ Attribute Name +
+
+
+ +
+
-
- +
+
+ Load Balance Compression +
+
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp index f7637a20314b..8ce9684954fe 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp @@ -83,25 +83,60 @@
-
-
- Back pressure object threshold -
+
+
+
+ Back Pressure
Object threshold +
+
+
+ +
+
+
+
 
+
+
+  
Size threshold +
+
+
+ +
+
-
- -
-
-
-
- Back pressure data size threshold -
+
+
+
+
+ Load Balance Strategy +
+
+
+
+
+
+
 
+
+
+ Attribute Name +
+
+
+ +
+
+
+
+
+ Load Balance Compression +
+
+
+
+
-
- -
-
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css index ce944859f555..c58febc4c170 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css @@ -22,7 +22,7 @@ z-index: 1301; display: none; width: 700px; - height: 400px; + height: 500px; } #connection-details div.configuration-tab { @@ -40,4 +40,16 @@ #connection-details div.relationship-name { display: inline-block; line-height: normal; -} \ No newline at end of file +} + +.multi-column-settings { + display: flex; +} + +.multi-column-settings .setting { + flex-grow: 1; +} + +.multi-column-settings .separator { + min-width: 5px; +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css index 15daa76bec86..8b1717bb89df 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css @@ -320,13 +320,25 @@ g.connection path.connection-path.unauthorized { stroke-dasharray: 3,3; } -text.connection-from-run-status, text.connection-to-run-status, text.expiration-icon { +text.connection-from-run-status, text.connection-to-run-status, text.expiration-icon, text.load-balance-icon { fill: #728e9b; font-family: FontAwesome; - font-size: 10px; + font-size: 12px; text-shadow: 0 0 4px rgba(255,255,255,1); } +text.load-balance-icon-active { + fill: #44a3cf +} + +text.load-balance-icon-184 { + transform-origin: 189px 10px 0; +} + +text.load-balance-icon-200 { + transform-origin: 205px 10px 0; +} + text.connection-from-run-status.is-missing-port, text.connection-to-run-status.is-missing-port { fill: #cf9f5d; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js index 2d7938c9c995..eb58df3fca4f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js @@ -23,13 +23,14 @@ 'd3', 'nf.ErrorHandler', 'nf.Common', + 'nf.ClusterSummary', 'nf.Dialog', 'nf.Storage', 'nf.Client', 'nf.CanvasUtils', 'nf.Connection'], - function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { - return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection)); + function ($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { + return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection)); }); } else if (typeof exports === 'object' && typeof module === 'object') { module.exports = (nf.ConnectionConfiguration = @@ -37,6 +38,7 @@ require('d3'), require('nf.ErrorHandler'), require('nf.Common'), + require('nf.ClusterSummary'), require('nf.Dialog'), require('nf.Storage'), require('nf.Client'), @@ -47,13 +49,14 @@ root.d3, root.nf.ErrorHandler, root.nf.Common, + root.nf.ClusterSummary, root.nf.Dialog, root.nf.Storage, root.nf.Client, root.nf.CanvasUtils, root.nf.Connection); } -}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { +}(this, function ($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { 'use strict'; var nfBirdseye; @@ -1007,6 +1010,10 @@ var backPressureObjectThreshold = $('#back-pressure-object-threshold').val(); var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val(); var prioritizers = $('#prioritizer-selected').sortable('toArray'); + var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value; + var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy; + var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : ''; + var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS'; if (validateSettings()) { var d = nfConnection.get(connectionId); @@ -1025,7 +1032,10 @@ 'flowFileExpiration': flowFileExpiration, 'backPressureDataSizeThreshold': backPressureDataSizeThreshold, 'backPressureObjectThreshold': backPressureObjectThreshold, - 'prioritizers': prioritizers + 'prioritizers': prioritizers, + 'loadBalanceStrategy': loadBalanceStrategy, + 'loadBalancePartitionAttribute': loadBalancePartitionAttribute, + 'loadBalanceCompression': loadBalanceCompression } }; @@ -1099,6 +1109,10 @@ if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) { errors.push('Back pressure data size threshold must be specified'); } + if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE' + && nfCommon.isBlank($('#load-balance-partition-attribute').val())) { + errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"'); + } if (errors.length > 0) { nfDialog.showOkDialog({ @@ -1222,6 +1236,32 @@ }] }); + // initialize the load balance strategy combo + $('#load-balance-strategy-combo').combo({ + options: nfCommon.loadBalanceStrategyOptions, + select: function (selectedOption) { + // Show the appropriate configurations + if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') { + $('#load-balance-partition-attribute-setting-separator').show(); + $('#load-balance-partition-attribute-setting').show(); + } else { + $('#load-balance-partition-attribute-setting-separator').hide(); + $('#load-balance-partition-attribute-setting').hide(); + } + if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') { + $('#load-balance-compression-setting').hide(); + } else { + $('#load-balance-compression-setting').show(); + } + } + }); + + + // initialize the load balance compression combo + $('#load-balance-compression-combo').combo({ + options: nfCommon.loadBalanceCompressionOptions + }); + // load the processor prioritizers $.ajax({ type: 'GET', @@ -1393,6 +1433,20 @@ $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold); $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold); + // select the load balance combos + if (nfClusterSummary.isConnectedToCluster()) { + $('#load-balance-strategy-combo').combo('setSelectedOption', { + value: connection.loadBalanceStrategy + }); + $('#load-balance-compression-combo').combo('setSelectedOption', { + value: connection.loadBalanceCompression + }); + $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute); + $('#load-balance-settings').show(); + } else { + $('#load-balance-settings').hide(); + } + // format the connection id nfCommon.populateField('connection-id', connection.id); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js index 145e1a360d84..e6d7b7d24712 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js @@ -60,7 +60,7 @@ // the dimensions for the connection label var dimensions = { - width: 200 + width: 216 }; // width of a backpressure indicator - half of width, left/right padding, left/right border @@ -257,6 +257,16 @@ return false; }; + /** + * Determines whether load-balance is configured for the specified connection. + * + * @param {object} connection + * @return {boolean} Whether load-balance is configured + */ + var isLoadBalanceConfigured = function (connection) { + return nfCommon.isDefinedAndNotNull(connection.loadBalanceStrategy) && 'DO_NOT_LOAD_BALANCE' !== connection.loadBalanceStrategy; + }; + /** * Sorts the specified connections according to the z index. * @@ -886,7 +896,7 @@ connectionFrom.append('text') .attrs({ 'class': 'connection-from-run-status', - 'x': 185, + 'x': 200, 'y': 14 }); } else { @@ -995,7 +1005,7 @@ connectionTo.append('text') .attrs({ 'class': 'connection-to-run-status', - 'x': 185, + 'x': 200, 'y': 14 }); } else { @@ -1195,11 +1205,23 @@ 'class': 'size' }); + // load balance icon + // x is set dynamically to slide to right, depending on whether expiration icon is shown. + queued.append('text') + .attrs({ + 'class': 'load-balance-icon', + 'y': 14 + }) + .text(function () { + return '\uf042'; + }) + .append('title'); + // expiration icon queued.append('text') .attrs({ 'class': 'expiration-icon', - 'x': 185, + 'x': 200, 'y': 14 }) .text(function () { @@ -1341,6 +1363,52 @@ } }); + // determine whether or not to show the load-balance icon + connectionLabelContainer.select('text.load-balance-icon') + .classed('hidden', function () { + if (d.permissions.canRead) { + return !isLoadBalanceConfigured(d.component); + } else { + return true; + } + }).classed('load-balance-icon-active fa-rotate-90', function (d) { + return d.permissions.canRead && d.component.loadBalanceStatus === 'LOAD_BALANCE_ACTIVE'; + + }).classed('load-balance-icon-184', function() { + return d.permissions.canRead && isExpirationConfigured(d.component); + + }).classed('load-balance-icon-200', function() { + return d.permissions.canRead && !isExpirationConfigured(d.component); + + }).attr('x', function() { + return d.permissions.canRead && isExpirationConfigured(d.component) ? 184 : 200; + + }).select('title').text(function () { + if (d.permissions.canRead) { + var loadBalanceStrategy = nfCommon.getComboOptionText(nfCommon.loadBalanceStrategyOptions, d.component.loadBalanceStrategy); + if ('PARTITION_BY_ATTRIBUTE' === d.component.loadBalanceStrategy) { + loadBalanceStrategy += ' (' + d.component.loadBalancePartitionAttribute + ')' + } + + var loadBalanceCompression = 'no compression'; + switch (d.component.loadBalanceCompression) { + case 'COMPRESS_ATTRIBUTES_ONLY': + loadBalanceCompression = '\'Attribute\' compression'; + break; + case 'COMPRESS_ATTRIBUTES_AND_CONTENT': + loadBalanceCompression = '\'Attribute and content\' compression'; + break; + } + var loadBalanceStatus = 'LOAD_BALANCE_ACTIVE' === d.component.loadBalanceStatus ? ' Actively balancing...' : ''; + return 'Load Balance is configured' + + ' with \'' + loadBalanceStrategy + '\' strategy' + + ' and ' + loadBalanceCompression + '.' + + loadBalanceStatus; + } else { + return ''; + } + }); + // determine whether or not to show the expiration icon connectionLabelContainer.select('text.expiration-icon') .classed('hidden', function () { 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 c07b95d9cbf6..8dca0d8557dd 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 @@ -1191,6 +1191,42 @@ MILLIS_PER_MINUTE: 60000, MILLIS_PER_SECOND: 1000, + /** + * Constants for combo options. + */ + loadBalanceStrategyOptions: [{ + text: 'Do not load balance', + value: 'DO_NOT_LOAD_BALANCE', + description: 'Do not load balance FlowFiles between nodes in the cluster.' + }, { + text: 'Partition by attribute', + value: 'PARTITION_BY_ATTRIBUTE', + description: 'Determine which node to send a given FlowFile to based on the value of a user-specified FlowFile Attribute.' + + ' All FlowFiles that have the same value for said Attribute will be sent to the same node in the cluster.' + }, { + text: 'Round robin', + value: 'ROUND_ROBIN', + description: 'FlowFiles will be distributed to nodes in the cluster in a Round-Robin fashion.' + }, { + text: 'Single node', + value: 'SINGLE_NODE', + description: 'All FlowFiles will be sent to the same node. Which node they are sent to is not defined.' + }], + + loadBalanceCompressionOptions: [{ + text: 'Do not compress', + value: 'DO_NOT_COMPRESS', + description: 'FlowFiles will not be compressed' + }, { + text: 'Compress attributes only', + value: 'COMPRESS_ATTRIBUTES_ONLY', + description: 'FlowFiles\' attributes will be compressed, but the FlowFiles\' contents will not be' + }, { + text: 'Compress attributes and content', + value: 'COMPRESS_ATTRIBUTES_AND_CONTENT', + description: 'FlowFiles\' attributes and content will be compressed' + }], + /** * Formats the specified duration. * @@ -1650,7 +1686,22 @@ */ getComponentName: function (entity) { return entity.permissions.canRead === true ? entity.component.name : entity.id; + }, + + /** + * Find the corresponding combo option text from a combo option values. + * + * @param {object} options The combo option array + * @param {string} value The target value + * @returns {string} The matched option text or undefined if not found + */ + getComboOptionText: function (options, value) { + var matchedOption = options.find(function (option) { + return option.value === value; + }); + return nfCommon.isDefinedAndNotNull(matchedOption) ? matchedOption.text : undefined; } + }; return nfCommon; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js index a6538a905c21..b1632b33973f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js @@ -21,20 +21,23 @@ if (typeof define === 'function' && define.amd) { define(['jquery', 'nf.Common', + 'nf.ClusterSummary', 'nf.ErrorHandler'], - function ($, nfCommon, nfErrorHandler) { - return (nf.ConnectionDetails = factory($, nfCommon, nfErrorHandler)); + function ($, nfCommon, nfClusterSummary, nfErrorHandler) { + return (nf.ConnectionDetails = factory($, nfCommon, nfClusterSummary, nfErrorHandler)); }); } else if (typeof exports === 'object' && typeof module === 'object') { module.exports = (nf.ConnectionDetails = factory(require('jquery'), require('nf.Common'), + require('nf.ClusterSummary'), require('nf.ErrorHandler'))); } else { nf.ConnectionDetails = factory(root.$, root.nf.Common, + root.nf.ClusterSummary, root.nf.ErrorHandler); } -}(this, function ($, nfCommon, nfErrorHandler) { +}(this, function ($, nfCommon, nfClusterSummary, nfErrorHandler) { 'use strict'; /** @@ -427,9 +430,12 @@ $('#read-only-relationship-names').css('border-width', '0').empty(); // clear the connection settings - $('#read-only-flow-file-expiration').text(''); - $('#read-only-back-pressure-object-threshold').text(''); - $('#read-only-back-pressure-data-size-threshold').text(''); + nfCommon.clearField('read-only-flow-file-expiration'); + nfCommon.clearField('read-only-back-pressure-object-threshold'); + nfCommon.clearField('read-only-back-pressure-data-size-threshold'); + nfCommon.clearField('read-only-load-balance-strategy'); + nfCommon.clearField('read-only-load-balance-partition-attribute'); + nfCommon.clearField('read-only-load-balance-compression'); $('#read-only-prioritizers').empty(); }, open: function () { @@ -446,6 +452,7 @@ * @argument {string} connectionId The connection id */ showDetails: function (groupId, connectionId) { + // get the group details var groupXhr = $.ajax({ type: 'GET', @@ -521,6 +528,27 @@ nfCommon.populateField('read-only-flow-file-expiration', connection.flowFileExpiration); nfCommon.populateField('read-only-back-pressure-object-threshold', connection.backPressureObjectThreshold); nfCommon.populateField('read-only-back-pressure-data-size-threshold', connection.backPressureDataSizeThreshold); + nfCommon.populateField('read-only-load-balance-strategy', nfCommon.getComboOptionText(nfCommon.loadBalanceStrategyOptions, connection.loadBalanceStrategy)); + nfCommon.populateField('read-only-load-balance-partition-attribute', connection.loadBalancePartitionAttribute); + nfCommon.populateField('read-only-load-balance-compression', nfCommon.getComboOptionText(nfCommon.loadBalanceCompressionOptions, connection.loadBalanceCompression)); + + // Show the appropriate load-balance configurations + if (nfClusterSummary.isConnectedToCluster()) { + if (connection.loadBalanceStrategy === 'PARTITION_BY_ATTRIBUTE') { + $('#read-only-load-balance-partition-attribute-setting').show(); + } else { + $('#read-only-load-balance-partition-attribute-setting').hide(); + } + if (connection.loadBalanceStrategy === 'DO_NOT_LOAD_BALANCE') { + $('#read-only-load-balance-compression-setting').hide(); + } else { + $('#read-only-load-balance-compression-setting').show(); + } + $('#read-only-load-balance-settings').show(); + } else { + $('#read-only-load-balance-settings').hide(); + } + // prioritizers if (nfCommon.isDefinedAndNotNull(connection.prioritizers) && connection.prioritizers.length > 0) {