Repository: nifi Updated Branches: refs/heads/master 23350543f -> 3f60eac54
[NIFI-2599] Enhance Status History Dialog. This closes #894 Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/3f60eac5 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/3f60eac5 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/3f60eac5 Branch: refs/heads/master Commit: 3f60eac54413e90e8137471dcd1e50fa6137ef5a Parents: 2335054 Author: Scott Aslan <[email protected]> Authored: Tue Aug 23 10:40:23 2016 -0400 Committer: Matt Gilman <[email protected]> Committed: Tue Aug 23 10:53:10 2016 -0400 ---------------------------------------------------------------------- .../WEB-INF/partials/status-history-dialog.jsp | 16 +- .../nifi-web-ui/src/main/webapp/css/header.css | 2 +- .../src/main/webapp/css/status-history.css | 14 +- .../main/webapp/js/jquery/modal/jquery.modal.js | 14 +- .../src/main/webapp/js/nf/nf-status-history.js | 1265 +++++++++--------- .../webapp/js/nf/summary/nf-summary-table.js | 1 + 6 files changed, 641 insertions(+), 671 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/status-history-dialog.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/status-history-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/status-history-dialog.jsp index 4b227d0..7daac42 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/status-history-dialog.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/status-history-dialog.jsp @@ -16,16 +16,16 @@ --%> <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> <div id="status-history-dialog" class="hidden large-dialog"> + <div id="status-history-refresh-container"> + <button id="status-history-refresh-button" class="refresh-button pointer fa fa-refresh" title="Refresh"></button> + <div id="status-history-last-refreshed-container" class="last-refreshed-container"> + Last updated: <span id="status-history-last-refreshed"></span> + </div> + <div id="status-history-loading-container" class="loading-container"></div> + </div> <div class="dialog-content"> <div id="status-history-details"></div> - <div id="status-history-refresh-container"> - <button id="status-history-refresh-button" class="refresh-button pointer fa fa-refresh" title="Refresh"></button> - <div id="status-history-last-refreshed-container" class="last-refreshed-container"> - Last updated: <span id="status-history-last-refreshed"></span> - </div> - <div id="status-history-loading-container" class="loading-container"></div> - <div id="status-history-metric-combo"></div> - </div> + <div id="status-history-metric-combo"></div> <div id="status-history-container"> <div id="status-history-chart-container"></div> <div id="status-history-chart-control-container"></div> http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css index c742c2f..24a370b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css @@ -190,7 +190,7 @@ md-toolbar.md-small .md-toolbar-tools { font-style: normal; font-weight: normal; font-size: 12px; - max-width: 250px; + max-width: 130px; text-overflow: ellipsis; line-height: normal; overflow: hidden; http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/status-history.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/status-history.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/status-history.css index ac969c2..e421f61 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/status-history.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/status-history.css @@ -27,9 +27,9 @@ #status-history-refresh-container { position: absolute; - top: 0px; - left: 40%; - right: 0px; + left: 20px; + bottom: 20px; + z-index: 1; } #status-history-loading-container { @@ -57,6 +57,7 @@ #status-history-metric-combo { float: right; + width: 250px; } #status-history-container { @@ -68,7 +69,6 @@ } #status-history-chart-container { - height: 265px; background-color: #fff; overflow: hidden; cursor: default; @@ -86,6 +86,11 @@ left: 0px; right: 60%; bottom: 0px; + overflow-y: auto; +} + +#status-history-dialog > .dialog-content { + overflow: visible; } div.status-history-detail { @@ -132,7 +137,6 @@ div.legend-entry { div.legend-label { font-weight: bold; - width: 240px; overflow: hidden; white-space: nowrap; line-height: normal; http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/modal/jquery.modal.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/modal/jquery.modal.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/modal/jquery.modal.js index 1e73676..82f8bf6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/modal/jquery.modal.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/modal/jquery.modal.js @@ -463,13 +463,13 @@ nfDialog.glasspane = glasspane = 'transparent'; } - //create glass pane overlay - var modalGlassMarkup = '<div data-nf-dialog-parent="' + - dialog.attr('id') + '" class="modal-glass" style="background-color: ' + glasspane + ';"></div>'; - - var modalGlass = $(modalGlassMarkup); - - modalGlass.css('z-index', zIndex - 1).appendTo($('body')); + if (!$('body').find("[data-nf-dialog-parent='" + dialog.attr('id') + "']").is(':visible')) { + //create glass pane overlay + $('<div></div>').attr('data-nf-dialog-parent', dialog.attr('id')).addClass("modal-glass").css({ + "background-color": glasspane, + "z-index": zIndex - 1 + }).appendTo($('body')); + } //persist data attribute dialog.data('nfDialog', nfDialog); http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-status-history.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-status-history.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-status-history.js index 9a67688..17d40d8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-status-history.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-status-history.js @@ -233,6 +233,13 @@ nf.StatusHistory = (function () { } } }); + + $('#status-history-dialog').modal('show'); + }; + + var getChartMinHeight = function () { + return $('#status-history-chart-container').parent().outerHeight() - $('#status-history-chart-control-container').outerHeight() - + parseInt($('#status-history-chart-control-container').css('margin-top'), 10); }; var getChartMaxHeight = function () { @@ -252,748 +259,698 @@ nf.StatusHistory = (function () { * Updates the chart with the specified status history and the selected field. */ var updateChart = function () { - var statusHistory = $('#status-history-dialog').data('status-history'); - // get the selected descriptor - var selectedDescriptor = statusHistory.selectedDescriptor; + var statusHistoryDialog = $('#status-history-dialog'); - // remove current details - $('#status-history-details').empty(); + if (statusHistoryDialog.is(':visible')) { + var statusHistory = statusHistoryDialog.data('status-history'); - // add status history details - var detailsContainer = buildDetailsContainer('Status History'); - d3.map(statusHistory.details).forEach(function (label, value) { - addDetailItem(detailsContainer, label, value); - }); + // get the selected descriptor + var selectedDescriptor = statusHistory.selectedDescriptor; - var margin = { - top: 15, - right: 20, - bottom: 25, - left: 75 - }; + // remove current details + $('#status-history-details').empty(); - // ------------- - // prep the data - // ------------- + // add status history details + var detailsContainer = buildDetailsContainer('Status History'); + d3.map(statusHistory.details).forEach(function (label, value) { + addDetailItem(detailsContainer, label, value); + }); - // available colors - var color = d3.scale.category10(); + var margin = { + top: 15, + right: 20, + bottom: 25, + left: 75 + }; - // determine the available instances - var instanceLabels = []; - $.each(statusHistory.instances, function (_, instance) { - instanceLabels.push(instance.label); - }); + // ------------- + // prep the data + // ------------- - // specify the domain based on the detected instances - color.domain(instanceLabels); + // available colors + var color = d3.scale.category10(); - // data for the chart - var statusData = []; + // determine the available instances + var instanceLabels = []; + $.each(statusHistory.instances, function (_, instance) { + instanceLabels.push(instance.label); + }); - // go through each instance of this status history - $.each(statusHistory.instances, function (_, instance) { - // if this is the first time this instance is being rendered, make it visible - if (nf.Common.isUndefinedOrNull(instances[instance.id])) { - instances[instance.id] = true; - } + // specify the domain based on the detected instances + color.domain(instanceLabels); + + // data for the chart + var statusData = []; + + // go through each instance of this status history + $.each(statusHistory.instances, function (_, instance) { + // if this is the first time this instance is being rendered, make it visible + if (nf.Common.isUndefinedOrNull(instances[instance.id])) { + instances[instance.id] = true; + } - // and convert the model - statusData.push({ - id: instance.id, - label: instance.label, - values: $.map(instance.snapshots, function (d) { - return { - timestamp: d.timestamp, - value: d.statusMetrics[selectedDescriptor.field] - }; - }), - visible: instances[instance.id] === true + // and convert the model + statusData.push({ + id: instance.id, + label: instance.label, + values: $.map(instance.snapshots, function (d) { + return { + timestamp: d.timestamp, + value: d.statusMetrics[selectedDescriptor.field] + }; + }), + visible: instances[instance.id] === true + }); }); - }); - // -------------------------- - // custom time axis formatter - // -------------------------- - - var customTimeFormat = d3.time.format.multi([ - [':%S.%L', function (d) { - return d.getMilliseconds(); - }], - [':%S', function (d) { - return d.getSeconds(); - }], - ['%H:%M', function (d) { - return d.getMinutes(); - }], - ['%H:%M', function (d) { - return d.getHours(); - }], - ['%a %d', function (d) { - return d.getDay() && d.getDate() !== 1; - }], - ['%b %d', function (d) { - return d.getDate() !== 1; - }], - ['%B', function (d) { - return d.getMonth(); - }], - ['%Y', function () { - return true; - }] - ]); - - // ---------- - // main chart - // ---------- + // -------------------------- + // custom time axis formatter + // -------------------------- - var statusHistoryDialog = $('#status-history-dialog'); + var customTimeFormat = d3.time.format.multi([ + [':%S.%L', function (d) { + return d.getMilliseconds(); + }], + [':%S', function (d) { + return d.getSeconds(); + }], + ['%H:%M', function (d) { + return d.getMinutes(); + }], + ['%H:%M', function (d) { + return d.getHours(); + }], + ['%a %d', function (d) { + return d.getDay() && d.getDate() !== 1; + }], + ['%b %d', function (d) { + return d.getDate() !== 1; + }], + ['%B', function (d) { + return d.getMonth(); + }], + ['%Y', function () { + return true; + }] + ]); + + // ---------- + // main chart + // ---------- + + // the container for the main chart + var chartContainer = $('#status-history-chart-container').empty(); + if (chartContainer.hasClass('ui-resizable')) { + chartContainer.resizable('destroy'); + chartContainer.removeAttr( "style" ); + } - // show/center the dialog if necessary - if (!statusHistoryDialog.is(':visible')) { - statusHistoryDialog.modal('show'); - } + // calculate the dimensions + chartContainer.height(getChartMinHeight()); - // the container for the main chart - var chartContainer = $('#status-history-chart-container').empty(); - if (chartContainer.hasClass('ui-resizable')) { - chartContainer.resizable('destroy'); - } + // determine the new width/height + var width = chartContainer.outerWidth() - margin.left - margin.right; + var height = chartContainer.outerHeight() - margin.top - margin.bottom; - // calculate the dimensions - var width = chartContainer.parent().outerWidth() - margin.left - margin.right; - var height = chartContainer.outerHeight() - margin.top - margin.bottom; + maxWidth = $('#status-history-container').width(); + if (width > maxWidth) { + width = maxWidth; + } - maxWidth = getChartMaxWidth(); - if (width > maxWidth) { - width = maxWidth; - } + maxHeight = getChartMaxHeight(); + if (height > maxHeight) { + height = maxHeight; + } - maxHeight = getChartMaxHeight(); - if (height > maxHeight) { - height = maxHeight; - } + // define the x axis for the main chart + var x = d3.time.scale() + .range([0, width]); - // define the x axis for the main chart - var x = d3.time.scale() - .range([0, width]); - - var xAxis = d3.svg.axis() - .scale(x) - .ticks(5) - .tickFormat(customTimeFormat) - .orient('bottom'); - - // define the y axis - var y = d3.scale.linear() - .range([height, 0]); - - var yAxis = d3.svg.axis() - .scale(y) - .tickFormat(formatters[selectedDescriptor.formatter]) - .orient('left'); - - // status line - var line = d3.svg.line() - .interpolate('monotone') - .x(function (d) { - return x(d.timestamp); - }) - .y(function (d) { - return y(d.value); - }); + var xAxis = d3.svg.axis() + .scale(x) + .ticks(5) + .tickFormat(customTimeFormat) + .orient('bottom'); - // build the chart svg - var chartSvg = d3.select('#status-history-chart-container').append('svg') - .attr('style', 'pointer-events: none;') - .attr('width', chartContainer.parent().width()) - .attr('height', chartContainer.innerHeight()); - // define a clip the path - var clipPath = chartSvg.append('defs').append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('width', width) - .attr('height', height); - - // build the chart - var chart = chartSvg.append('g') - .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); - - // determine the min/max date - var minDate = d3.min(statusData, function (d) { - return d3.min(d.values, function (s) { - return s.timestamp; - }); - }); - var maxDate = d3.max(statusData, function (d) { - return d3.max(d.values, function (s) { - return s.timestamp; - }); - }); - addDetailItem(detailsContainer, 'Start', nf.Common.formatDateTime(minDate)); - addDetailItem(detailsContainer, 'End', nf.Common.formatDateTime(maxDate)); - - // determine the x axis range - x.domain([minDate, maxDate]); - - // determine the y axis range - y.domain([getMinValue(statusData), getMaxValue(statusData)]); - - // build the x axis - chart.append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0, ' + height + ')') - .call(xAxis); - - // build the y axis - chart.append('g') - .attr('class', 'y axis') - .call(yAxis) - .append('text') - .attr('transform', 'rotate(-90)') - .attr('y', 6) - .attr('dy', '.71em') - .attr('text-anchor', 'end') - .text(selectedDescriptor.label); - - // build the chart - var status = chart.selectAll('.status') - .data(statusData) - .enter() - .append('g') - .attr('clip-path', 'url(#clip)') - .attr('class', 'status'); - - // draw the lines - status.append('path') - .attr('class', function (d) { - return 'chart-line chart-line-' + d.id; - }) - .attr('d', function (d) { - return line(d.values); - }) - .attr('stroke', function (d) { - return color(d.label); - }) - .classed('hidden', function (d) { - return d.visible === false; - }) - .append('title') - .text(function (d) { - return d.label; - }); + // define the y axis + var y = d3.scale.linear() + .range([height, 0]); + + var yAxis = d3.svg.axis() + .scale(y) + .tickFormat(formatters[selectedDescriptor.formatter]) + .orient('left'); - // draw the control points for each line - status.each(function (d) { - // create a group for the control points - var markGroup = d3.select(this).append('g') - .attr('class', function () { - return 'mark-group mark-group-' + d.id; + + // status line + var line = d3.svg.line() + .interpolate('monotone') + .x(function (d) { + return x(d.timestamp); }) - .classed('hidden', function (d) { - return d.visible === false; + .y(function (d) { + return y(d.value); }); - // draw the control points - markGroup.selectAll('circle.mark') - .data(d.values) + // build the chart svg + var chartSvg = d3.select('#status-history-chart-container').append('svg') + .attr('style', 'pointer-events: none;') + .attr('width', chartContainer.parent().width()) + .attr('height', chartContainer.innerHeight()); + // define a clip the path + var clipPath = chartSvg.append('defs').append('clipPath') + .attr('id', 'clip') + .append('rect') + .attr('width', width) + .attr('height', height); + + // build the chart + var chart = chartSvg.append('g') + .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); + + // determine the min/max date + var minDate = d3.min(statusData, function (d) { + return d3.min(d.values, function (s) { + return s.timestamp; + }); + }); + var maxDate = d3.max(statusData, function (d) { + return d3.max(d.values, function (s) { + return s.timestamp; + }); + }); + addDetailItem(detailsContainer, 'Start', nf.Common.formatDateTime(minDate)); + addDetailItem(detailsContainer, 'End', nf.Common.formatDateTime(maxDate)); + + // determine the x axis range + x.domain([minDate, maxDate]); + + // determine the y axis range + y.domain([getMinValue(statusData), getMaxValue(statusData)]); + + // build the x axis + chart.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0, ' + height + ')') + .call(xAxis); + + // build the y axis + chart.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 6) + .attr('dy', '.71em') + .attr('text-anchor', 'end') + .text(selectedDescriptor.label); + + // build the chart + var status = chart.selectAll('.status') + .data(statusData) .enter() - .append('circle') - .attr('style', 'pointer-events: all;') - .attr('class', 'mark') - .attr('cx', function (v) { - return x(v.timestamp); + .append('g') + .attr('clip-path', 'url(#clip)') + .attr('class', 'status'); + + // draw the lines + status.append('path') + .attr('class', function (d) { + return 'chart-line chart-line-' + d.id; }) - .attr('cy', function (v) { - return y(v.value); + .attr('d', function (d) { + return line(d.values); }) - .attr('fill', function () { + .attr('stroke', function (d) { return color(d.label); }) - .attr('r', 1.5) + .classed('hidden', function (d) { + return d.visible === false; + }) .append('title') - .text(function (v) { - return d.label + ' -- ' + formatters[selectedDescriptor.formatter](v.value); + .text(function (d) { + return d.label; }); - }); - - // ------------- - // control chart - // ------------- - - // the container for the main chart control - var chartControlContainer = $('#status-history-chart-control-container').empty(); - var controlHeight = chartControlContainer.innerHeight() - margin.top - margin.bottom; - - var xControl = d3.time.scale() - .range([0, width]); - - var xControlAxis = d3.svg.axis() - .scale(xControl) - .ticks(5) - .tickFormat(customTimeFormat) - .orient('bottom'); - - var yControl = d3.scale.linear() - .range([controlHeight, 0]); - - var yControlAxis = d3.svg.axis() - .scale(yControl) - .tickValues(y.domain()) - .tickFormat(formatters[selectedDescriptor.formatter]) - .orient('left'); - - // status line - var controlLine = d3.svg.line() - .interpolate('monotone') - .x(function (d) { - return xControl(d.timestamp); - }) - .y(function (d) { - return yControl(d.value); - }); - // build the svg - var controlChartSvg = d3.select('#status-history-chart-control-container').append('svg') - .attr('width', chartContainer.parent().width()) - .attr('height', controlHeight + margin.top + margin.bottom); - - // build the control chart - var control = controlChartSvg.append('g') - .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); - - // increase the y domain slightly - var yControlDomain = y.domain(); - yControlDomain[1] *= 1.04; - - // define the domain for the control chart - xControl.domain(x.domain()); - yControl.domain(yControlDomain); - - // build the control x axis - control.append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0, ' + controlHeight + ')') - .call(xControlAxis); - - // build the control y axis - control.append('g') - .attr('class', 'y axis') - .call(yControlAxis); - - // build the control chart - var controlStatus = control.selectAll('.status') - .data(statusData) - .enter() - .append('g') - .attr('class', 'status'); - - // draw the lines - controlStatus.append('path') - .attr('class', function (d) { - return 'chart-line chart-line-' + d.id; - }) - .attr('d', function (d) { - return controlLine(d.values); - }) - .attr('stroke', function (d) { - return color(d.label); - }) - .classed('hidden', function (d) { - return instances[d.id] === false; - }) - .append('title') - .text(function (d) { - return d.label; + // draw the control points for each line + status.each(function (d) { + // create a group for the control points + var markGroup = d3.select(this).append('g') + .attr('class', function () { + return 'mark-group mark-group-' + d.id; + }) + .classed('hidden', function (d) { + return d.visible === false; + }); + + // draw the control points + markGroup.selectAll('circle.mark') + .data(d.values) + .enter() + .append('circle') + .attr('style', 'pointer-events: all;') + .attr('class', 'mark') + .attr('cx', function (v) { + return x(v.timestamp); + }) + .attr('cy', function (v) { + return y(v.value); + }) + .attr('fill', function () { + return color(d.label); + }) + .attr('r', 1.5) + .append('title') + .text(function (v) { + return d.label + ' -- ' + formatters[selectedDescriptor.formatter](v.value); + }); }); - // ------------------- - // configure the brush - // ------------------- + // update the size of the chart + chartSvg.attr('width', chartContainer.parent().width()) + .attr('height', chartContainer.innerHeight()); - /** - * Updates the axis for the main chart. - * - * @param {array} xDomain The new domain for the x axis - * @param {array} yDomain The new domain for the y axis - */ - var updateAxes = function (xDomain, yDomain) { - // update the domain of the main chart - x.domain(xDomain); - y.domain(yDomain); + // update the size of the clipper + clipPath.attr('width', width) + .attr('height', height); - // update the chart lines - status.selectAll('.chart-line') - .attr('d', function (d) { - return line(d.values); - }); - status.selectAll('circle.mark') - .attr('cx', function (v) { - return x(v.timestamp); - }) - .attr('cy', function (v) { - return y(v.value); - }) - .attr('r', function () { - return brush.empty() ? 1.5 : 4; - }); + // update the position of the x axis + chart.select('.x.axis').attr('transform', 'translate(0, ' + height + ')'); - // update the x axis - chart.select('.x.axis').call(xAxis); - chart.select('.y.axis').call(yAxis); - }; + // ------------- + // control chart + // ------------- - /** - * Handles brush events by updating the main chart according to the context window - * or the control domain if there is no context window. - */ - var brushed = function () { - // determine the new x and y domains - var xContextDomain, yContextDomain; - if (brush.empty()) { - // get the all visible instances - var visibleInstances = $.grep(statusData, function (d) { - return d.visible; - }); + // the container for the main chart control + var chartControlContainer = $('#status-history-chart-control-container').empty(); + var controlHeight = chartControlContainer.innerHeight() - margin.top - margin.bottom; - // determine the appropriate y domain - if (visibleInstances.length === 0) { - yContextDomain = yControl.domain(); - } else { - yContextDomain = [ - d3.min(visibleInstances, function (d) { - return d3.min(d.values, function (s) { - return s.value; - }); - }), - d3.max(visibleInstances, function (d) { - return d3.max(d.values, function (s) { - return s.value; - }); - }) - ]; - } - xContextDomain = xControl.domain(); + var xControl = d3.time.scale() + .range([0, width]); - // clear the current extent - brushExtent = null; - } else { - var extent = brush.extent(); - xContextDomain = [extent[0][0], extent[1][0]]; - yContextDomain = [extent[0][1], extent[1][1]]; + var xControlAxis = d3.svg.axis() + .scale(xControl) + .ticks(5) + .tickFormat(customTimeFormat) + .orient('bottom'); - // hold onto the current brush - brushExtent = extent; - } + var yControl = d3.scale.linear() + .range([controlHeight, 0]); - // update the axes accordingly - updateAxes(xContextDomain, yContextDomain); + var yControlAxis = d3.svg.axis() + .scale(yControl) + .tickValues(y.domain()) + .tickFormat(formatters[selectedDescriptor.formatter]) + .orient('left'); - // update the aggregate statistics according to the new domain - updateAggregateStatistics(); - }; - - // build the brush - var brush = d3.svg.brush() - .x(xControl) - .y(yControl) - .on('brush', brushed); + // status line + var controlLine = d3.svg.line() + .interpolate('monotone') + .x(function (d) { + return xControl(d.timestamp); + }) + .y(function (d) { + return yControl(d.value); + }); - // conditionally set the brush extent - if (nf.Common.isDefinedAndNotNull(brushExtent)) { - brush = brush.extent(brushExtent); - } + // build the svg + var controlChartSvg = d3.select('#status-history-chart-control-container').append('svg') + .attr('width', chartContainer.parent().width()) + .attr('height', controlHeight + margin.top + margin.bottom); + + // build the control chart + var control = controlChartSvg.append('g') + .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); + + // increase the y domain slightly + var yControlDomain = y.domain(); + yControlDomain[1] *= 1.04; + + // define the domain for the control chart + xControl.domain(x.domain()); + yControl.domain(yControlDomain); + + // build the control x axis + control.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0, ' + controlHeight + ')') + .call(xControlAxis); + + // build the control y axis + control.append('g') + .attr('class', 'y axis') + .call(yControlAxis); + + // build the control chart + var controlStatus = control.selectAll('.status') + .data(statusData) + .enter() + .append('g') + .attr('class', 'status'); - // context area - control.append('g') - .attr('class', 'brush') - .call(brush); - - // add expansion to the extent - control.select('rect.extent') - .attr('style', 'pointer-events: all;') - .on('dblclick', function () { - if (!brush.empty()) { - // get the current extent to get the x range - var extent = brush.extent(); + // draw the lines + controlStatus.append('path') + .attr('class', function (d) { + return 'chart-line chart-line-' + d.id; + }) + .attr('d', function (d) { + return controlLine(d.values); + }) + .attr('stroke', function (d) { + return color(d.label); + }) + .classed('hidden', function (d) { + return instances[d.id] === false; + }) + .append('title') + .text(function (d) { + return d.label; + }); - // get the y range (this value does not change from the original y domain) - var yRange = yControl.domain(); + // ------------------- + // configure the brush + // ------------------- + + /** + * Updates the axis for the main chart. + * + * @param {array} xDomain The new domain for the x axis + * @param {array} yDomain The new domain for the y axis + */ + var updateAxes = function (xDomain, yDomain) { + // update the domain of the main chart + x.domain(xDomain); + y.domain(yDomain); - // expand the extent vertically - brush.extent([[extent[0][0], yRange[0]], [extent[1][0], yRange[1]]]); + // update the chart lines + status.selectAll('.chart-line') + .attr('d', function (d) { + return line(d.values); + }); + status.selectAll('circle.mark') + .attr('cx', function (v) { + return x(v.timestamp); + }) + .attr('cy', function (v) { + return y(v.value); + }) + .attr('r', function () { + return brush.empty() ? 1.5 : 4; + }); + + // update the x axis + chart.select('.x.axis').call(xAxis); + chart.select('.y.axis').call(yAxis); + }; + + /** + * Handles brush events by updating the main chart according to the context window + * or the control domain if there is no context window. + */ + var brushed = function () { + // determine the new x and y domains + var xContextDomain, yContextDomain; + if (brush.empty()) { + // get the all visible instances + var visibleInstances = $.grep(statusData, function (d) { + return d.visible; + }); + + // determine the appropriate y domain + if (visibleInstances.length === 0) { + yContextDomain = yControl.domain(); + } else { + yContextDomain = [ + d3.min(visibleInstances, function (d) { + return d3.min(d.values, function (s) { + return s.value; + }); + }), + d3.max(visibleInstances, function (d) { + return d3.max(d.values, function (s) { + return s.value; + }); + }) + ]; + } + xContextDomain = xControl.domain(); - // update the brush control - control.select('.brush').call(brush); + // clear the current extent + brushExtent = null; + } else { + var extent = brush.extent(); + xContextDomain = [extent[0][0], extent[1][0]]; + yContextDomain = [extent[0][1], extent[1][1]]; - // run the brush to update the axes of the main chart - brushed(); + // hold onto the current brush + brushExtent = extent; } - }); - // -------------------- - // aggregate statistics - // -------------------- + // update the axes accordingly + updateAxes(xContextDomain, yContextDomain); - var updateAggregateStatistics = function () { - // locate the instances that have data points within the current brush - var withinBrush = $.map(statusData, function (d) { - var xDomain = x.domain(); - var yDomain = y.domain(); + // update the aggregate statistics according to the new domain + updateAggregateStatistics(); + }; - // copy to avoid modifying the original - var copy = $.extend({}, d); + // build the brush + var brush = d3.svg.brush() + .x(xControl) + .y(yControl) + .on('brush', brushed); - // update the copy to only include values within the brush - return $.extend(copy, { - values: $.grep(d.values, function (s) { - return s.timestamp.getTime() >= xDomain[0].getTime() && s.timestamp.getTime() <= xDomain[1].getTime() && s.value >= yDomain[0] && s.value <= yDomain[1]; - }) - }); - }); + // conditionally set the brush extent + if (nf.Common.isDefinedAndNotNull(brushExtent)) { + brush = brush.extent(brushExtent); + } - // consider visible nodes with data in the brush - var nodes = $.grep(withinBrush, function (d) { - return d.id !== config.nifiInstanceId && d.visible && d.values.length > 0; - }); + // context area + control.append('g') + .attr('class', 'brush') + .call(brush); - var nodeMinValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMinValue(nodes)); - var nodeMeanValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMeanValue(nodes)); - var nodeMaxValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMaxValue(nodes)); + // add expansion to the extent + control.select('rect.extent') + .attr('style', 'pointer-events: all;') + .on('dblclick', function () { + if (!brush.empty()) { + // get the current extent to get the x range + var extent = brush.extent(); - // update the currently displayed min/max/mean - $('#node-aggregate-statistics').text(nodeMinValue + ' / ' + nodeMaxValue + ' / ' + nodeMeanValue); + // get the y range (this value does not change from the original y domain) + var yRange = yControl.domain(); - // only consider the cluster with data in the brush - var cluster = $.grep(withinBrush, function (d) { - return d.id === config.nifiInstanceId && d.visible && d.values.length > 0; - }); + // expand the extent vertically + brush.extent([[extent[0][0], yRange[0]], [extent[1][0], yRange[1]]]); - // determine the cluster values - var clusterMinValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMinValue(cluster)); - var clusterMeanValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMeanValue(cluster)); - var clusterMaxValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMaxValue(cluster)); + // update the brush control + control.select('.brush').call(brush); - // update the cluster min/max/mean - $('#cluster-aggregate-statistics').text(clusterMinValue + ' / ' + clusterMaxValue + ' / ' + clusterMeanValue); - }; + // run the brush to update the axes of the main chart + brushed(); + } + }); - // ---------------- - // build the legend - // ---------------- + // -------------------- + // aggregate statistics + // -------------------- - // identify all nodes and sort - var nodes = $.grep(statusData, function (status) { - return status.id !== config.nifiInstanceId; - }).sort(function (a, b) { - return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; - }); + var updateAggregateStatistics = function () { + // locate the instances that have data points within the current brush + var withinBrush = $.map(statusData, function (d) { + var xDomain = x.domain(); + var yDomain = y.domain(); + + // copy to avoid modifying the original + var copy = $.extend({}, d); - // adds a legend entry for the specified instance - var addLegendEntry = function (legend, instance) { - // create the label and the checkbox - var instanceLabelElement = $('<div></div>').addClass('legend-label').css('color', color(instance.label)).text(instance.label).ellipsis(); - var instanceCheckboxElement = $('<div class="nf-checkbox"></div>').on('click', function () { - // get the line and the control points for this instance (select all for the line to update control and main charts) - var chartLine = d3.selectAll('path.chart-line-' + instance.id); - var markGroup = d3.select('g.mark-group-' + instance.id); - - // determine if it was hidden - var isHidden = markGroup.classed('hidden'); - - // toggle the visibility - chartLine.classed('hidden', function () { - return !isHidden; + // update the copy to only include values within the brush + return $.extend(copy, { + values: $.grep(d.values, function (s) { + return s.timestamp.getTime() >= xDomain[0].getTime() && s.timestamp.getTime() <= xDomain[1].getTime() && s.value >= yDomain[0] && s.value <= yDomain[1]; + }) + }); }); - markGroup.classed('hidden', function () { - return !isHidden; + + // consider visible nodes with data in the brush + var nodes = $.grep(withinBrush, function (d) { + return d.id !== config.nifiInstanceId && d.visible && d.values.length > 0; }); - // update whether its visible - instance.visible = isHidden; + var nodeMinValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMinValue(nodes)); + var nodeMeanValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMeanValue(nodes)); + var nodeMaxValue = nodes.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMaxValue(nodes)); - // record the current status so it persists across refreshes - instances[instance.id] = instance.visible; + // update the currently displayed min/max/mean + $('#node-aggregate-statistics').text(nodeMinValue + ' / ' + nodeMaxValue + ' / ' + nodeMeanValue); - // update the brush - brushed(); - }).addClass(instance.visible ? 'checkbox-checked' : 'checkbox-unchecked'); + // only consider the cluster with data in the brush + var cluster = $.grep(withinBrush, function (d) { + return d.id === config.nifiInstanceId && d.visible && d.values.length > 0; + }); - // add the legend entry - $('<div class="legend-entry"></div>').append(instanceCheckboxElement).append(instanceLabelElement).on('mouseenter', function () { - d3.selectAll('path.chart-line-' + instance.id).classed('over', true); - }).on('mouseleave', function () { - d3.selectAll('path.chart-line-' + instance.id).classed('over', false); - }).appendTo(legend); - }; + // determine the cluster values + var clusterMinValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMinValue(cluster)); + var clusterMeanValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMeanValue(cluster)); + var clusterMaxValue = cluster.length === 0 ? 'NA' : formatters[selectedDescriptor.formatter](getMaxValue(cluster)); - // get the cluster instance - var cluster = $.grep(statusData, function (status) { - return status.id === config.nifiInstanceId; - }); + // update the cluster min/max/mean + $('#cluster-aggregate-statistics').text(clusterMinValue + ' / ' + clusterMaxValue + ' / ' + clusterMeanValue); + }; - // build the cluster container - var clusterDetailsContainer = buildDetailsContainer('NiFi'); + // ---------------- + // build the legend + // ---------------- - // add the total cluster values - addDetailItem(clusterDetailsContainer, 'Min / Max / Mean', '', 'cluster-aggregate-statistics'); + // identify all nodes and sort + var nodes = $.grep(statusData, function (status) { + return status.id !== config.nifiInstanceId; + }).sort(function (a, b) { + return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; + }); - // build the cluster legend - addLegendEntry(clusterDetailsContainer, cluster[0]); + // adds a legend entry for the specified instance + var addLegendEntry = function (legend, instance) { + // create the label and the checkbox + var instanceLabelElement = $('<div></div>').addClass('legend-label').css('color', color(instance.label)).text(instance.label).ellipsis(); + var instanceCheckboxElement = $('<div class="nf-checkbox"></div>').on('click', function () { + // get the line and the control points for this instance (select all for the line to update control and main charts) + var chartLine = d3.selectAll('path.chart-line-' + instance.id); + var markGroup = d3.select('g.mark-group-' + instance.id); + + // determine if it was hidden + var isHidden = markGroup.classed('hidden'); + + // toggle the visibility + chartLine.classed('hidden', function () { + return !isHidden; + }); + markGroup.classed('hidden', function () { + return !isHidden; + }); + + // update whether its visible + instance.visible = isHidden; + + // record the current status so it persists across refreshes + instances[instance.id] = instance.visible; + + // update the brush + brushed(); + }).addClass(instance.visible ? 'checkbox-checked' : 'checkbox-unchecked'); + + // add the legend entry + $('<div class="legend-entry"></div>').append(instanceCheckboxElement).append(instanceLabelElement).on('mouseenter', function () { + d3.selectAll('path.chart-line-' + instance.id).classed('over', true); + }).on('mouseleave', function () { + d3.selectAll('path.chart-line-' + instance.id).classed('over', false); + }).appendTo(legend); + }; + + // get the cluster instance + var cluster = $.grep(statusData, function (status) { + return status.id === config.nifiInstanceId; + }); - // if there are entries to render - if (nodes.length > 0) { // build the cluster container - var nodeDetailsContainer = buildDetailsContainer('Nodes'); + var clusterDetailsContainer = buildDetailsContainer('NiFi'); // add the total cluster values - addDetailItem(nodeDetailsContainer, 'Min / Max / Mean', '', 'node-aggregate-statistics'); + addDetailItem(clusterDetailsContainer, 'Min / Max / Mean', '', 'cluster-aggregate-statistics'); - // add each legend entry - $.each(nodes, function (_, instance) { - addLegendEntry(nodeDetailsContainer, instance); - }); - } + // build the cluster legend + addLegendEntry(clusterDetailsContainer, cluster[0]); - // update the brush - brushed(); + // if there are entries to render + if (nodes.length > 0) { + // build the cluster container + var nodeDetailsContainer = buildDetailsContainer('Nodes'); - // --------------- - // handle resizing - // --------------- + // add the total cluster values + addDetailItem(nodeDetailsContainer, 'Min / Max / Mean', '', 'node-aggregate-statistics'); - var maxWidth, maxHeight, resizeExtent, dialog; - chartContainer.append('<div class="ui-resizable-handle ui-resizable-se"></div>').resizable({ - minWidth: 425, - minHeight: 150, - handles: { - 'se': '.ui-resizable-se' - }, - start: function (e, ui) { - // record the current extent so it can be reset on stop - if (!brush.empty()) { - resizeExtent = brush.extent(); - } - }, - resize: function (e, ui) { - // ----------- - // containment - // ----------- - dialog = $('#status-history-dialog'); - var nfDialog = {}; - if (nf.Common.isDefinedAndNotNull(dialog.data('nf-dialog'))) { - nfDialog = dialog.data('nf-dialog'); - } - nfDialog['min-width'] = (dialog.width()/$(window).width())*100 + '%'; - nfDialog['min-height'] = (dialog.height()/$(window).height())*100 + '%'; - nfDialog.responsive['fullscreen-width'] = dialog.outerWidth() + 'px'; - nfDialog.responsive['fullscreen-height'] = dialog.outerHeight() + 'px'; - - maxWidth = getChartMaxWidth(); - if (ui.helper.width() > maxWidth) { - ui.helper.width(maxWidth); - - nfDialog.responsive['fullscreen-width'] = $(window).width() + 'px'; - nfDialog['min-width'] = '100%'; - } - - maxHeight = getChartMaxHeight(); - if (ui.helper.height() > maxHeight) { - ui.helper.height(maxHeight); - - nfDialog.responsive['fullscreen-height'] = $(window).height() + 'px'; - nfDialog['min-height'] = '100%'; - } - - nfDialog['min-width'] = (parseInt(nfDialog['min-width'], 10) >= 100) ? '100%': nfDialog['min-width']; - nfDialog['min-height'] = (parseInt(nfDialog['min-height'], 10) >= 100) ? '100%': nfDialog['min-height']; - - //persist data attribute - dialog.data('nfDialog', nfDialog); - - // ---------------------- - // status history dialog - // ---------------------- - - dialog.css('min-width', (chartContainer.outerWidth() + $('#status-history-details').outerWidth() + 40)); - dialog.css('min-height', (chartContainer.outerHeight() + - $('#status-history-refresh-container').outerHeight() + $('#status-history-chart-control-container').outerHeight() + - $('.dialog-buttons').outerHeight() + $('.dialog-header').outerHeight() + 40 + 5)); - - dialog.center(); - }, - stop: function () { - - // ---------- - // main chart - // ---------- - - // determine the new width/height - width = chartContainer.outerWidth() - margin.left - margin.right; - height = chartContainer.outerHeight() - margin.top - margin.bottom; - - // update the range - x.range([0, width]); - y.range([height, 0]); - - // update the size of the chart - chartSvg.attr('width', chartContainer.parent().width()) - .attr('height', chartContainer.innerHeight()); - - // update the size of the clipper - clipPath.attr('width', width) - .attr('height', height); - - // update the position of the x axis - chart.select('.x.axis').attr('transform', 'translate(0, ' + height + ')'); - - // ------------- - // control chart - // ------------- + // add each legend entry + $.each(nodes, function (_, instance) { + addLegendEntry(nodeDetailsContainer, instance); + }); + } - // determine the new width/height - controlHeight = chartControlContainer.height() - margin.top - margin.bottom; + // update the brush + brushed(); + + // --------------- + // handle resizing + // --------------- + + var maxWidth, maxHeight, minHeight, resizeExtent, dialog; + chartContainer.append('<div class="ui-resizable-handle ui-resizable-se"></div>').resizable({ + minWidth: 425, + minHeight: 150, + handles: { + 'se': '.ui-resizable-se' + }, + start: function (e, ui) { + // record the current extent so it can be reset on stop + if (!brush.empty()) { + resizeExtent = brush.extent(); + } + }, + resize: function (e, ui) { + // ----------- + // containment + // ----------- + dialog = $('#status-history-dialog'); + var nfDialog = {}; + if (nf.Common.isDefinedAndNotNull(dialog.data('nf-dialog'))) { + nfDialog = dialog.data('nf-dialog'); + } + nfDialog['min-width'] = (dialog.width() / $(window).width()) * 100 + '%'; + nfDialog['min-height'] = (dialog.height() / $(window).height()) * 100 + '%'; + nfDialog.responsive['fullscreen-width'] = dialog.outerWidth() + 'px'; + nfDialog.responsive['fullscreen-height'] = dialog.outerHeight() + 'px'; - // update the range - xControl.range([0, width]); - yControl.range([controlHeight, 0]); + maxWidth = getChartMaxWidth(); + if (ui.helper.width() > maxWidth) { + ui.helper.width(maxWidth); - // update the size of the control chart - controlChartSvg.attr('width', chartContainer.parent().width()) - .attr('height', controlHeight + margin.top + margin.bottom); + nfDialog.responsive['fullscreen-width'] = $(window).width() + 'px'; + nfDialog['min-width'] = '100%'; + } - // update the chart lines - controlStatus.selectAll('.chart-line').attr('d', function (d) { - return controlLine(d.values); - }); + maxHeight = getChartMaxHeight(); + if (ui.helper.height() > maxHeight) { + ui.helper.height(maxHeight); - // update the axes - control.select('.x.axis').call(xControlAxis); - control.select('.y.axis').call(yControlAxis); + nfDialog.responsive['fullscreen-height'] = $(window).height() + 'px'; + nfDialog['min-height'] = '100%'; + } - // restore the extent if necessary - if (nf.Common.isDefinedAndNotNull(resizeExtent)) { - brush.extent(resizeExtent); - } + minHeight = getChartMinHeight(); + if (ui.helper.height() < minHeight) { + ui.helper.height(minHeight); + } - // update the brush - control.select('.brush').call(brush); + nfDialog['min-width'] = (parseInt(nfDialog['min-width'], 10) >= 100) ? '100%' : nfDialog['min-width']; + nfDialog['min-height'] = (parseInt(nfDialog['min-height'], 10) >= 100) ? '100%' : nfDialog['min-height']; - // invoking the brush will trigger appropriate redrawing of the main chart - brushed(); + //persist data attribute + dialog.data('nfDialog', nfDialog); - // reset the resize extent - resizeExtent = null; + // ---------------------- + // status history dialog + // ---------------------- - // resize chart container - chartContainer.width(Math.round( chartContainer.parent().width())); - chartControlContainer.width(Math.round( chartContainer.parent().width())); + dialog.css('min-width', (chartContainer.outerWidth() + $('#status-history-details').outerWidth() + 40)); + dialog.css('min-height', (chartContainer.outerHeight() + + $('#status-history-refresh-container').outerHeight() + $('#status-history-chart-control-container').outerHeight() + + $('.dialog-buttons').outerHeight() + $('.dialog-header').outerHeight() + 40 + 5)); - // toggle scrollable style - nf.Common.toggleScrollable(dialog.find('.dialog-content').get(0)); - } - }); + dialog.center(); + }, + stop: updateChart + }); + } }; /** @@ -1122,9 +1079,17 @@ nf.StatusHistory = (function () { brushExtent = null; descriptor = null; instances = null; - } + }, + open: updateChart } }); + + $(window).on('resize', function (e) { + if (e.target === window) { + updateChart(); + } + nf.Common.toggleScrollable($('#status-history-details').get(0)); + }) }, /** http://git-wip-us.apache.org/repos/asf/nifi/blob/3f60eac5/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary-table.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary-table.js index a43390a..33b8093 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary-table.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary-table.js @@ -276,6 +276,7 @@ nf.SummaryTable = (function () { break; case 'disabled': classes += ' icon icon-enable-false'; + break; case 'invalid': classes += ' fa fa-warning'; break;
