diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
index c401e4b440..f1d087c979 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
@@ -741,6 +741,7 @@
${staging.dir}/css/settings.css
${staging.dir}/css/about.css
${staging.dir}/css/status-history.css
+ ${staging.dir}/css/flow-analysis-drawer.css
@@ -780,6 +781,7 @@
${staging.dir}/css/processor-details.css
${staging.dir}/css/connection-details.css
${staging.dir}/css/status-history.css
+ ${staging.dir}/css/flow-analysis-drawer.css
${staging.dir}/css/summary.css
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
index a45a07a430..9f3cc38b0d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
@@ -43,4 +43,5 @@ nf.summary.style.tags= \n\
\n\
\n\
+ \n\
\ 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/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
index 51e1e08160..152435b891 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
@@ -128,6 +128,7 @@
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp
new file mode 100644
index 0000000000..23dc2d10e1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp
@@ -0,0 +1,28 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+
\ 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/css/canvas.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
index 8042c22a13..240dd14765 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
@@ -53,3 +53,4 @@
@import url(status-history.css);
@import url(../fonts/flowfont/flowfont.css);
@import url(../assets/font-awesome/css/font-awesome.css);
+@import url(flow-analysis-drawer.css);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css
new file mode 100644
index 0000000000..50299bd982
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.flow-analysis {
+ background-color: #E3E8EB;
+ padding: 0;
+}
+
+.flow-analysis:hover,
+.flow-analysis.opened {
+ background-color: #FFFFFF;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o {
+ padding: 4px;
+ width: 14px;
+ height: 14px;
+ border-radius: 15px;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o.recommendations {
+ border: 1px solid #176e83;
+ border-radius: 15px;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o.violations {
+ border: 0;
+ padding: 4px;
+ color: white;
+ width: 14px;
+ height: 14px;
+ background-color: #ba554a;
+ border-radius: 15px;
+}
+
+#flow-analysis-drawer {
+ background-color: rgba(249, 250, 251, 0.9);
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
+ height: calc(100vh - 115px);
+ overflow-y: scroll;
+ padding: 20px;
+ position: fixed;
+ right: -400px;
+ transition: right 0.3s ease-in-out;
+ width: 341px;
+ z-index: 2;
+}
+
+#flow-analysis-drawer.opened {
+ right: 0;
+}
+
+.flow-analysis-header {
+ align-items: center;
+ display: flex;
+ width: 100%;
+}
+
+.flow-analysis-loading-container {
+ background-color: transparent;
+ float: left;
+ height: 16px;
+ margin-left: 0;
+ margin-right: 5px;
+ margin-top: 0;
+ width: 16px;
+}
+
+.flow-analysis-loading-message {
+ display: none;
+}
+
+.flow-analysis-flow-guide-title {
+ color: #728e9b;
+ font-family: Roboto Slab;
+ font-size: 16px;
+ font-stretch: normal;
+ font-style: normal;
+ font-weight: 500;
+ letter-spacing: normal;
+ line-height: normal;
+ margin-bottom: 5px;
+}
+
+.flow-analysis-flow-guide {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.flow-analysis-violations-options {
+ align-items: center;
+ display: flex;
+ margin-bottom: 4px;
+}
+
+.flow-analysis-refresh {
+ align-items: center;
+ display: flex;
+}
+
+.flow-analysis-flow-guide-container {
+ margin-top: 22px;
+}
+
+#flow-analysis-drawer .flow-analysis-rules-accordion {
+ margin-top: 10px;
+ padding: 0 0 50px 0;
+}
+
+#flow-analysis-drawer
+ .flow-analysis-rules-accordion
+ .ui-accordion-header,
+#flow-analysis-drawer .rules-violations-header,
+#flow-analysis-drawer .rules-warnings-header {
+ background-color: transparent;
+ border: 0;
+ color: black;
+ display: flex;
+ font-family: Roboto Slab;
+ font-size: 12px;
+ font-weight: 500;
+ justify-content: space-between;
+ padding: 10px 0;
+ border-bottom: 1px solid #ddd;
+ align-items: center;
+}
+
+.flow-analysis-rules-accordion .ui-accordion-header-icon {
+ order: 2;
+}
+
+#flow-analysis-drawer .recs-poliicies-accordion-header-text {
+ color: #262626;
+ font-family: Roboto Slab;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.01px;
+ margin: 6px 5px 6px 0;
+}
+
+#flow-analysis-drawer .recommended-rules-list,
+#flow-analysis-drawer .required-rules-list,
+#flow-analysis-drawer .rule-warnings-list {
+ padding: 0;
+ background-color: transparent;
+ border: 0;
+}
+
+#flow-analysis-drawer .ui-icon {
+ background-image: none;
+ text-indent: 0;
+}
+
+.required-rule-count,
+.recommended-rule-count,
+.rule-violation-count,
+.rule-warning-count {
+ font-family: Roboto;
+ font-size: 14px;
+ font-weight: 400;
+}
+
+#flow-analysis-drawer .rules-list-item {
+ border-bottom: 1px solid #ddd;
+}
+
+#flow-analysis-drawer .rules-list-rule-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0 10px 10px;
+ font-family: Roboto;
+ font-size: 12px;
+ font-weight: normal;
+ font-stretch: normal;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: normal;
+ text-align: left;
+ color: #262626;
+}
+
+#flow-analysis-drawer .show-only-violations-label,
+#flow-analysis-drawer .show-only-warnings-label {
+ font-family: Roboto;
+ font-size: 13px;
+ font-weight: normal;
+ font-stretch: normal;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: normal;
+ text-align: left;
+ color: #262626;
+}
+
+#flow-analysis-drawer .rules-list-item-menu-target {
+ font-size: 14px;
+ color: #054849;
+}
+
+#flow-analysis-drawer .rule-menu-btn,
+#flow-analysis-drawer .violation-menu-btn {
+ border: 0;
+}
+
+#flow-analysis-drawer .rule-menu,
+#flow-analysis-drawer .violation-menu {
+ width: 200px;
+ box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.3);
+ border: solid 1px #004849;
+ background-color: #fff;
+}
+
+#flow-analysis-drawer .rule-menu-option,
+#flow-analysis-drawer .violation-menu-option {
+ padding: 3px 0 3px 10px;
+ background-color: #fff;
+ font-family: Roboto;
+ font-size: 13px;
+ font-weight: normal;
+ font-stretch: normal;
+ font-style: normal;
+ line-height: 1.15;
+ letter-spacing: normal;
+ text-align: left;
+ color: #262626;
+ display: flex;
+ align-items: center;
+ margin: 3px 0;
+}
+
+#flow-analysis-drawer .rule-menu-option.hidden,
+#flow-analysis-drawer .violation-menu-option.hidden {
+ display: none;
+}
+
+#flow-analysis-drawer .rule-menu-option:hover:not(.disabled),
+#flow-analysis-drawer .violation-menu-option:hover:not(.disabled) {
+ background-color: #dce3e6;
+ color: #262626;
+ cursor: pointer;
+}
+
+#flow-analysis-drawer .violation-menu-option.disabled,
+#flow-analysis-drawer .rule-menu-option.disabled,
+#flow-analysis-drawer .violation-menu-option-icon,
+#flow-analysis-drawer .rule-menu-option-icon {
+ float: none;
+}
+
+#flow-analysis-drawer .rule-menu-option-icon,
+#flow-analysis-drawer .violation-menu-option-icon {
+ color: #004849;
+ font-size: 18px;
+ margin-right: 10px;
+}
+
+/* Rule Information Modal */
+.rule-info-head,
+.violation-info-head {
+ margin-top: 20px;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 5px;
+}
+
+.rule-name,
+.violation-name {
+ align-self: flex-end;
+ font-family: Roboto Slab;
+ font-size: 12px;
+ letter-spacing: 0.3px;
+ text-align: left;
+ color: #262626;
+}
+
+.rule-type-pill,
+.violation-type-pill {
+ padding: 5px 10px;
+ border-radius: 12px;
+ font-family: Roboto;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.12px;
+}
+
+.rule-type-pill.enforce,
+.violation-type-pill.enforce {
+ background-color: #ba554a;
+ color: #fff;
+}
+
+.rule-type-pill.warn,
+.violation-type-pill.warn {
+ border: solid 1px #176e83;
+ background-color: #fff;
+}
+
+.rule-name,
+.violation-name {
+ font-family: Roboto Slab;
+ font-size: 12px;
+ line-height: normal;
+}
+
+.rule-display-name,
+.violation-display-name {
+ font-family: Roboto;
+ font-size: 13px;
+ font-weight: 500;
+ color: #775351;
+ margin-bottom: 20px;
+}
+
+.rule-description,
+.violation-description {
+ font-family: Roboto;
+ font-size: 13px;
+ color: #262626;
+ margin: 20px 0;
+}
+
+.fa.rule-docs-link-icon,
+.fa.violation-docs-link-icon {
+ margin: 0 5px 0 0;
+ font-family: FontAwesome;
+ font-size: 16px;
+ color: #004849;
+}
+
+.rule-docs-link,
+.violation-docs-link {
+ font-family: Roboto;
+ font-size: 13px;
+ color: #004849;
+}
+
+/* Violations */
+
+.rule-violations-count {
+ font-family: Roboto;
+ font-size: 12px;
+ font-weight: 500;
+ color: #ba554a;
+ margin-left: 10px;
+}
+
+.rule-recommendations-count {
+ font-family: Roboto;
+ font-size: 12px;
+ font-weight: 500;
+ color: #176e83;
+ margin-left: 10px;
+}
+
+.rule-violations-list,
+.rule-warnings-list,
+.rule-recommendations-list {
+ list-style: none;
+}
+
+.rule-violations-list li,
+.rule-warnings-list li,
+.rule-recommendations-list li {
+ border-bottom: 1px solid #ddd;
+}
+
+.violation-menu-btn,
+.recommendation-menu-btn {
+ margin-left: auto;
+ border: 0;
+}
+
+.violation-list-item,
+.recommendation-list-item,
+.rule-violations-list-item-wrapper,
+.warning-list-item {
+ padding: 10px 0 10px 15px;
+ display: flex;
+ align-items: center;
+ position: relative;
+}
+
+.violation-list-item-name::before,
+.warning-list-item-name::before,
+.recommendation-list-item-name::before {
+ content: '';
+ position: absolute;
+ left: 9px;
+ top: 13px;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+}
+
+.violation-list-item-name::before,
+.rule-violations-list-item-wrapper:before {
+ background-color: #ba554a;
+}
+
+.recommendation-list-item-name::before,
+.rule-warnings-list-item-name::before,
+.warning-list-item-name::before,
+.recommendation-list-item-name::before {
+ background-color: transparent;
+ border: 1px solid #176e83;
+}
+
+.violation-list-item-wrapper,
+.recommendation-list-item-wrapper,
+.warning-list-item-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: calc(100% - 5px);
+ margin-left: 5px;
+}
+
+.violation-list-item-name,
+.recommendation-list-item-name,
+.warning-list-item-name {
+ font-family: Roboto;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.rule-violations-list-item-name,
+.rule-warnings-list-item-name {
+ font-family: Roboto;
+ font-size: 12px;
+ font-weight: 500;
+ margin-top: 11px;
+}
+
+.rule-violations-list-item-name {
+ color: #ba554a;
+}
+
+.rule-warnings-list-item-name {
+ color: #176e83;
+}
+
+.violation-list-item-id,
+.recommendation-list-item-id {
+ font-size: 11px;
+ line-height: normal;
+ color: #606060;
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-flow-status-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-flow-status-controller.js
index 58e99759ac..5ac793a650 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-flow-status-controller.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-flow-status-controller.js
@@ -29,9 +29,10 @@
'nf.Settings',
'nf.ParameterContexts',
'nf.ProcessGroup',
- 'nf.ProcessGroupConfiguration'],
- function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration) {
- return (nf.ng.Canvas.FlowStatusCtrl = factory($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration));
+ 'nf.ProcessGroupConfiguration',
+ 'nf.Shell'],
+ function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration, nfShell) {
+ return (nf.ng.Canvas.FlowStatusCtrl = factory($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration, nfShell));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ng.Canvas.FlowStatusCtrl =
@@ -45,7 +46,8 @@
require('nf.Settings'),
require('nf.ParameterContexts'),
require('nf.ProcessGroup'),
- require('nf.ProcessGroupConfiguration')));
+ require('nf.ProcessGroupConfiguration'),
+ require('nf.Shell')));
} else {
nf.ng.Canvas.FlowStatusCtrl = factory(root.$,
root.nf.Common,
@@ -57,9 +59,10 @@
root.nf.Settings,
root.nf.ParameterContexts,
root.nf.ProcessGroup,
- root.nf.ProcessGroupConfiguration);
+ root.nf.ProcessGroupConfiguration,
+ root.nf.Shell);
}
-}(this, function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration) {
+}(this, function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration, nfShell) {
'use strict';
return function (serviceProvider) {
@@ -69,10 +72,13 @@
search: 'Search',
urls: {
search: '../nifi-api/flow/search-results',
- status: '../nifi-api/flow/status'
+ status: '../nifi-api/flow/status',
+ flowAnalysis: '../nifi-api/controller/analyze-flow'
}
};
+ var previousRulesResponse = {};
+
function FlowStatusCtrl() {
this.connectedNodesCount = "-";
this.clusterConnectionWarning = false;
@@ -400,6 +406,561 @@
}
}
+ /**
+ * The flow analysis controller.
+ */
+
+ this.flowAnalysis = {
+
+ /**
+ * Create the list of rule violations
+ */
+ buildRuleViolationsList: function(rules, violationsAndRecs) {
+ var ruleViolationCountEl = $('#rule-violation-count');
+ var ruleViolationListEl = $('#rule-violations-list');
+ var ruleWarningCountEl = $('#rule-warning-count');
+ var ruleWarningListEl = $('#rule-warnings-list');
+ var violations = violationsAndRecs.filter(function (violation) {
+ return violation.enforcementPolicy === 'ENFORCE'
+ });
+ var warnings = violationsAndRecs.filter(function (violation) {
+ return violation.enforcementPolicy === 'WARN'
+ });
+ ruleViolationCountEl.empty().text('(' + violations.length + ')');
+ ruleWarningCountEl.empty().text('(' + warnings.length + ')');
+ ruleViolationListEl.empty();
+ ruleWarningListEl.empty();
+ violations.forEach(function(violation) {
+ var rule = rules.find(function(rule) {
+ return rule.id === violation.ruleId;
+ });
+ // create DOM elements
+ var violationListItemEl = $(' ');
+ var violationEl = $('
');
+ var violationListItemWrapperEl = $('
');
+ var violationRuleEl = $('
');
+ var violationListItemNameEl = $('
');
+ var violationListItemIdEl = $(' ');
+ var violationInfoButtonEl = $('');
+
+ // add text content and button data
+ $(violationRuleEl).text(rule.name);
+ violation.subjectPermissionDto.canRead ? $(violationListItemNameEl).text(violation.subjectDisplayName) : $(violationListItemNameEl).text('Unauthorized').addClass('unauthorized');
+ $(violationListItemIdEl).text(violation.subjectId);
+ $(violationListItemEl).append(violationRuleEl).append(violationListItemWrapperEl);
+ $(violationInfoButtonEl).data('violationInfo', violation);
+
+ // build list DOM structure
+ violationListItemWrapperEl.append(violationListItemNameEl).append(violationListItemIdEl);
+ violationEl.append(violationListItemWrapperEl).append(violationInfoButtonEl);
+ violationListItemEl.append(violationRuleEl).append(violationEl)
+ ruleViolationListEl.append(violationListItemEl);
+ });
+
+ warnings.forEach(function(warning) {
+ var rule = rules.find(function(rule) {
+ return rule.id === warning.ruleId;
+ });
+ // create DOM elements
+ var warningListItemEl = $(' ');
+ var warningEl = $('
');
+ var warningListItemWrapperEl = $('
');
+ var warningRuleEl = $('
');
+ var warningListItemNameEl = $('
');
+ var warningListItemIdEl = $(' ');
+ var warningInfoButtonEl = $('');
+
+ // add text content and button data
+ $(warningRuleEl).text(rule.name);
+ warning.subjectPermissionDto.canRead ? $(warningListItemNameEl).text(warning.subjectDisplayName) : $(warningListItemNameEl).text('Unauthorized').addClass('unauthorized');
+ $(warningListItemIdEl).text(warning.subjectId);
+ $(warningListItemEl).append(warningRuleEl).append(warningListItemWrapperEl);
+ $(warningInfoButtonEl).data('violationInfo', warning);
+
+ // build list DOM structure
+ warningListItemWrapperEl.append(warningListItemNameEl).append(warningListItemIdEl);
+ warningEl.append(warningListItemWrapperEl).append(warningInfoButtonEl);
+ warningListItemEl.append(warningRuleEl).append(warningEl)
+ ruleWarningListEl.append(warningListItemEl);
+ });
+ },
+
+ /**
+ *
+ * Render a new list when it differs from the previous violations response
+ */
+ buildRuleViolations: function(rules, violations) {
+ if (Object.keys(previousRulesResponse).length !== 0) {
+ var previousRulesResponseSorted = _.sortBy(previousRulesResponse.ruleViolations, 'subjectId');
+ var violationsSorted = _.sortBy(violations, 'subjectId');
+ if (!_.isEqual(previousRulesResponseSorted, violationsSorted)) {
+ this.buildRuleViolationsList(rules, violations);
+ }
+ } else {
+ this.buildRuleViolationsList(rules, violations);
+ }
+ },
+
+ /**
+ * Create the list of flow policy rules
+ */
+ buildRuleList: function(ruleType, violationsMap, rec) {
+ var requiredRulesListEl = $('#required-rules-list');
+ var recommendedRulesListEl = $('#recommended-rules-list');
+ var rule = $(' ').append($(rec.requirement).append(rec.requirementInfoButton))
+ var violationsListEl = '';
+ var violationCountEl = '';
+
+ var violations = violationsMap.get(rec.id);
+ if (!!violations) {
+ if (violations.length === 1) {
+ violationCountEl = '' + violations.length + ' ' + ruleType + '
';
+ } else {
+ violationCountEl = '' + violations.length + ' ' + ruleType + 's
';
+ }
+ violationsListEl = $('');
+ violations.forEach(function(violation) {
+ // create DOM elements
+ var violationListItemEl = $(' ');
+ var violationWrapperEl = $('
');
+ var violationNameEl = $('
');
+ var violationIdEl = $(' ');
+ var violationInfoButtonEl = $('');
+
+ // add text content and button data
+ violation.subjectPermissionDto.canRead ? violationNameEl.text(violation.subjectDisplayName) : violationNameEl.text('Unauthorized');
+ violationIdEl.text(violation.subjectId);
+
+ // build list DOM structure
+ violationListItemEl.append(violationWrapperEl);
+ violationWrapperEl.append(violationNameEl).append(violationIdEl)
+ violationInfoButtonEl.data('violationInfo', violation);
+ (violationsListEl).append(violationListItemEl.append(violationInfoButtonEl));
+ });
+ rule.append(violationCountEl).append(violationsListEl);
+ }
+ ruleType === 'violation' ? requiredRulesListEl.append(rule) : recommendedRulesListEl.append(rule);
+ },
+
+ /**
+ * Loads the current status of the flow.
+ */
+ loadFlowPolicies: function () {
+ var flowAnalysisCtrl = this;
+ var requiredRulesListEl = $('#required-rules-list');
+ var recommendedRulesListEl = $('#recommended-rules-list');
+ var requiredRuleCountEl = $('#required-rule-count');
+ var recommendedRuleCountEl = $('#recommended-rule-count');
+ var flowAnalysisLoader = $('#flow-analysis-loading-container');
+ var flowAnalysisLoadMessage = $('#flow-analysis-loading-message');
+
+ var groupId = nfCanvasUtils.getGroupId();
+ if (groupId !== 'root') {
+ $.ajax({
+ type: 'GET',
+ url: '../nifi-api/flow/flow-analysis/results/' + groupId,
+ dataType: 'json',
+ context: this
+ }).done(function (response) {
+ var recommendations = [];
+ var requirements = [];
+ var requirementsTotal = 0;
+ var recommendationsTotal = 0;
+
+ if (!_.isEqual(previousRulesResponse, response)) {
+ // clear previous accordion content
+ requiredRulesListEl.empty();
+ recommendedRulesListEl.empty();
+ flowAnalysisCtrl.buildRuleViolations(response.rules, response.ruleViolations);
+
+ if (response.flowAnalysisPending) {
+ flowAnalysisLoader.addClass('ajax-loading');
+ flowAnalysisLoadMessage.show();
+ } else {
+ flowAnalysisLoader.removeClass('ajax-loading');
+ flowAnalysisLoadMessage.hide();
+ }
+
+ // For each ruleViolations:
+ // * group violations by ruleId
+ // * build DOM elements
+ // * get the ruleId and find the matching rule id
+ // * append violation list to matching rule list item
+ var violationsMap = new Map();
+ response.ruleViolations.forEach(function(violation) {
+ if (violationsMap.has(violation.ruleId)){
+ violationsMap.get(violation.ruleId).push(violation);
+ } else {
+ violationsMap.set(violation.ruleId, [violation]);
+ }
+ });
+
+ // build list of recommendations
+ response.rules.forEach(function(rule) {
+ if (rule.enforcementPolicy === 'WARN') {
+ var requirement = '
';
+ var requirementName = $('
').text(rule.name);
+ var requirementInfoButton = '';
+ recommendations.push(
+ {
+ 'requirement': $(requirement).append(requirementName),
+ 'requirementInfoButton': $(requirementInfoButton).data('ruleInfo', rule),
+ 'id': rule.id
+ }
+ )
+ recommendationsTotal++;
+ }
+ });
+
+ // add class to notification icon for recommended rules
+ var hasRecommendations = response.ruleViolations.findIndex(function(violation) {
+ return violation.enforcementPolicy === 'WARN';
+ });
+ if (hasRecommendations !== -1) {
+ $('#flow-analysis .flow-analysis-notification-icon ').addClass('recommendations');
+ } else {
+ $('#flow-analysis .flow-analysis-notification-icon ').removeClass('recommendations');
+ }
+
+ // build list of requirements
+ recommendedRuleCountEl.empty().append('(' + recommendationsTotal + ')');
+ recommendations.forEach(function(rec) {
+ flowAnalysisCtrl.buildRuleList('recommendation', violationsMap, rec);
+ });
+
+ response.rules.forEach(function(rule) {
+ if (rule.enforcementPolicy === 'ENFORCE') {
+ var requirement = '
';
+ var requirementName = $('
').text(rule.name);
+ var requirementInfoButton = '';
+ requirements.push(
+ {
+ 'requirement': $(requirement).append(requirementName),
+ 'requirementInfoButton': $(requirementInfoButton).data('ruleInfo', rule),
+ 'id': rule.id
+ }
+ )
+ requirementsTotal++;
+ }
+ });
+
+ // add class to notification icon for required rules
+ var hasViolations = response.ruleViolations.findIndex(function(violation) {
+ return violation.enforcementPolicy === 'ENFORCE';
+ })
+ if (hasViolations !== -1) {
+ $('#flow-analysis .flow-analysis-notification-icon ').addClass('violations');
+ } else {
+ $('#flow-analysis .flow-analysis-notification-icon ').removeClass('violations');
+ }
+
+ requiredRuleCountEl.empty().append('(' + requirementsTotal + ')');
+
+ // build violations
+ requirements.forEach(function(rec) {
+ flowAnalysisCtrl.buildRuleList('violation', violationsMap, rec);
+ });
+
+ $('#required-rules').accordion('refresh');
+ $('#recommended-rules').accordion('refresh');
+ // report the updated status
+ previousRulesResponse = response;
+
+ // setup rule menu handling
+ flowAnalysisCtrl.setRuleMenuHandling();
+
+ // setup violation menu handling
+ flowAnalysisCtrl.setViolationMenuHandling(response.rules);
+ }
+ }).fail(nfErrorHandler.handleAjaxError);
+ }
+ },
+
+ /**
+ * Set event bindings for rule menus
+ */
+ setRuleMenuHandling: function() {
+ $('.rule-menu-btn').click(function(event) {
+ // stop event from immediately bubbling up to document and triggering closeRuleWindow
+ event.stopPropagation();
+ // unbind previously bound rule data that may still exist
+ unbindRuleMenuHandling();
+
+ var ruleInfo = $(this).data('ruleInfo');
+ $('#violation-menu').hide();
+ $('#rule-menu').show();
+ $('#rule-menu').position({
+ my: "left top",
+ at: "left top",
+ of: event
+ });
+
+ // rule menu bindings
+ if (nfCommon.canAccessController()) {
+ $('#rule-menu-edit-rule').removeClass('disabled');
+ $('#rule-menu-edit-rule .rule-menu-option-icon').removeClass('disabled');
+ $('#rule-menu-edit-rule').on('click', openRuleDetailsDialog);
+ } else {
+ $('#rule-menu-edit-rule').addClass('disabled');
+ $('#rule-menu-edit-rule .rule-menu-option-icon').addClass('disabled');
+ }
+ $('#rule-menu-view-documentation').on('click', viewRuleDocumentation);
+ $(document).on('click', closeRuleWindow);
+
+ function viewRuleDocumentation(e) {
+ nfShell.showPage('../nifi-docs/documentation?' + $.param({
+ select: ruleInfo.type,
+ group: ruleInfo.bundle.group,
+ artifact: ruleInfo.bundle.artifact,
+ version: ruleInfo.bundle.version
+ })).done(function () {});
+ $("#rule-menu").hide();
+ unbindRuleMenuHandling();
+ }
+
+ function closeRuleWindow(e) {
+ if ($(e.target).parents("#rule-menu").length === 0) {
+ $("#rule-menu").hide();
+ unbindRuleMenuHandling();
+ }
+ }
+
+ function openRuleDetailsDialog() {
+ $('#rule-menu').hide();
+ nfSettings.showSettings().done(function() {
+ nfSettings.selectFlowAnalysisRule(ruleInfo.id);
+ });
+ unbindRuleMenuHandling();
+ }
+
+ function unbindRuleMenuHandling() {
+ $('#rule-menu-edit-rule').off("click");
+ $('#rule-menu-view-documentation').off("click");
+ $(document).unbind('click', closeRuleWindow);
+ }
+
+ });
+ },
+
+ /**
+ * Set event bindings for violation menus
+ */
+ setViolationMenuHandling: function(rules) {
+ $('.violation-menu-btn').click(function(event) {
+ // stop event from immediately bubbling up to document and triggering closeViolationWindow
+ event.stopPropagation();
+ var violationInfo = $(this).data('violationInfo');
+ $('#rule-menu').hide();
+ $('#violation-menu').show();
+ $('#violation-menu').position({
+ my: "left top",
+ at: "left top",
+ of: event
+ });
+
+ // violation menu bindings
+ if (violationInfo.subjectPermissionDto.canRead) {
+ $('#violation-menu-more-info').removeClass('disabled');
+ $('#violation-menu-more-info .violation-menu-option-icon').removeClass('disabled');
+ $('#violation-menu-more-info').on( "click", openRuleViolationMoreInfoDialog);
+ } else {
+ $('#violation-menu-more-info').addClass('disabled');
+ $('#violation-menu-more-info .violation-menu-option-icon').addClass('disabled');
+ }
+
+ if (violationInfo.subjectComponentType === 'PROCESSOR' && violationInfo.subjectPermissionDto.canRead) {
+ $('#violation-menu-go-to').removeClass('hidden');
+ $('#violation-menu-go-to').on('click', goToComponent);
+ } else {
+ $('#violation-menu-go-to').addClass('hidden');
+ }
+ $(document).on('click', closeViolationWindow);
+
+ function closeViolationWindow(e) {
+ if ($(e.target).parents("#violation-menu").length === 0) {
+ $("#violation-menu").hide();
+ unbindViolationMenuHandling();
+ }
+ }
+
+ function openRuleViolationMoreInfoDialog() {
+ var rule = rules.find(function(rule){
+ return rule.id === violationInfo.ruleId;
+ });
+ $('#violation-menu').hide();
+ $('#violation-type-pill').empty()
+ .removeClass()
+ .addClass(violationInfo.enforcementPolicy.toLowerCase() + ' violation-type-pill')
+ .append(violationInfo.enforcementPolicy);
+ $('#violation-description').empty().append(violationInfo.violationMessage);
+ $('#violation-menu-more-info-dialog').modal( "show" );
+ $('.violation-docs-link').click(function () {
+ // open the documentation for this flow analysis rule
+ nfShell.showPage('../nifi-docs/documentation?' + $.param({
+ select: rule.type,
+ group: rule.bundle.group,
+ artifact: rule.bundle.artifact,
+ version: rule.bundle.version
+ })).done(function () {});
+ });
+ unbindViolationMenuHandling();
+ }
+
+ function goToComponent() {
+ $('#violation-menu').hide();
+ nfCanvasUtils.showComponent(violationInfo.groupId, violationInfo.subjectId);
+ unbindViolationMenuHandling();
+ }
+
+ function unbindViolationMenuHandling() {
+ $('#violation-menu-more-info').off("click");
+ $('#violation-menu-go-to').off("click");
+ $(document).unbind('click', closeViolationWindow);
+ }
+ });
+ },
+
+ /**
+ * Initialize the flow analysis controller.
+ */
+ init: function () {
+ var flowAnalysisCtrl = this;
+ var drawer = $('#flow-analysis-drawer');
+ var requiredRulesEl = $('#required-rules');
+ var recommendedRulesEl = $('#recommended-rules');
+ var flowAnalysisRefreshIntervalSeconds = nfCommon.getAutoRefreshInterval();
+
+ $('#flow-analysis').click(function () {
+ $(this).toggleClass('opened');
+ drawer.toggleClass('opened');
+ });
+ requiredRulesEl.accordion({
+ collapsible: true,
+ active: false,
+ icons: {
+ "header": "fa fa-chevron-down",
+ "activeHeader": "fa fa-chevron-up"
+ }
+ });
+
+ recommendedRulesEl.accordion({
+ collapsible: true,
+ active: false,
+ icons: {
+ "header": "fa fa-chevron-down",
+ "activeHeader": "fa fa-chevron-up"
+ }
+ });
+ $('#rule-menu').hide();
+ $('#violation-menu').hide();
+ $('#rule-menu-more-info-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Rule Information',
+ buttons: [{
+ buttonText: 'OK',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {}
+ }
+ });
+ $('#violation-menu-more-info-dialog').modal({
+ scrollableContentStyle: 'scrollable',
+ headerText: 'Violation Information',
+ buttons: [{
+ buttonText: 'OK',
+ color: {
+ base: '#728E9B',
+ hover: '#004849',
+ text: '#ffffff'
+ },
+ handler: {
+ click: function () {
+ $(this).modal('hide');
+ }
+ }
+ }],
+ handler: {
+ close: function () {}
+ }
+ });
+
+ this.loadFlowPolicies();
+ setInterval(this.loadFlowPolicies.bind(this), flowAnalysisRefreshIntervalSeconds * 1000);
+
+ this.toggleOnlyViolations(false);
+ this.toggleOnlyWarnings(false);
+ // handle show only violations checkbox
+ $('#show-only-violations').on('change', function(event) {
+ var isChecked = $(this).hasClass('checkbox-checked');
+ flowAnalysisCtrl.toggleOnlyViolations(isChecked);
+ });
+
+ $('#show-only-warnings').on('change', function(event) {
+ var isChecked = $(this).hasClass('checkbox-checked');
+ flowAnalysisCtrl.toggleOnlyWarnings(isChecked);
+ });
+ },
+
+ /**
+ * Show/hide violations menu
+ */
+ toggleOnlyViolations: function(isViolationsChecked) {
+ var requiredRulesEl = $('#required-rules');
+ var recommendedRulesEl = $('#recommended-rules');
+ var ruleViolationsEl = $('#rule-violations');
+
+ var isWarningsChecked = $('#show-only-warnings').hasClass(
+ 'checkbox-checked'
+ );
+
+ isViolationsChecked
+ ? ruleViolationsEl.show()
+ : ruleViolationsEl.hide();
+ if (isViolationsChecked || isWarningsChecked) {
+ requiredRulesEl.hide();
+ recommendedRulesEl.hide();
+ } else {
+ requiredRulesEl.show();
+ recommendedRulesEl.show();
+ }
+ this.loadFlowPolicies();
+ },
+
+ /**
+ * Show/hide warnings menu
+ */
+ toggleOnlyWarnings: function (isWarningsChecked) {
+ var requiredRulesEl = $('#required-rules');
+ var recommendedRulesEl = $('#recommended-rules');
+ var ruleWarningsEl = $('#rule-warnings');
+ var isViolationsChecked = $('#show-only-violations').hasClass(
+ 'checkbox-checked'
+ );
+
+ isWarningsChecked
+ ? ruleWarningsEl.show()
+ : ruleWarningsEl.hide();
+ if (isWarningsChecked || isViolationsChecked) {
+ requiredRulesEl.hide();
+ recommendedRulesEl.hide();
+ } else {
+ requiredRulesEl.show();
+ recommendedRulesEl.show();
+ }
+ this.loadFlowPolicies();
+ },
+ }
+
/**
* The bulletins controller.
*/
@@ -468,6 +1029,7 @@
*/
init: function () {
this.search.init();
+ this.flowAnalysis.init();
},
/**
@@ -684,6 +1246,14 @@
*/
updateBulletins: function (response) {
this.bulletins.update(response);
+ },
+
+ /**
+ * Reloads flow analysis rules
+ *
+ */
+ reloadFlowPolicies: function () {
+ this.flowAnalysis.loadFlowPolicies();
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index 458f6d84f0..f8f6b99298 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -1223,6 +1223,7 @@
*/
reload: function () {
nfCanvasUtils.reload();
+ nfNgBridge.injector.get('flowStatusCtrl').reloadFlowPolicies();
},
/**
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
index 8e5afb46a5..c120b6f5a5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
@@ -333,7 +333,7 @@
// get the auto refresh interval
var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10);
-
+ nfCommon.setAutoRefreshInterval(autoRefreshIntervalSeconds);
// record whether we can configure the authorizer
nfCanvas.setManagedAuthorizer(configDetails.supportsManagedAuthorizer);
nfCanvas.setConfigurableAuthorizer(configDetails.supportsConfigurableAuthorizer);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
index b59b3c070b..72874d4005 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
@@ -464,11 +464,11 @@
value: 'ENFORCE',
description: 'Treat violations of this rule as errors the correction of which is mandatory.'
}
-// , {
-// text: 'Warn',
-// value: 'WARN',
-// description: 'Treat violations of by this rule as warnings the correction of which is recommended but not mandatory.'
-// }
+ , {
+ text: 'Warn',
+ value: 'WARN',
+ description: 'Treat violations of by this rule as warnings the correction of which is recommended but not mandatory.'
+ }
],
selectedOption: {
value: flowAnalysisRule['enforcementPolicy']
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 3c6d0b1115..a3e37d2855 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
@@ -173,6 +173,7 @@
var nfCommon = {
ANONYMOUS_USER_TEXT: 'Anonymous user',
+ autoRefreshInterval: null,
config: {
sensitiveText: 'Sensitive value set',
@@ -1895,6 +1896,24 @@
});
return sortedAuthorizedParameterContexts.concat(sortedUnauthorizedParameterContexts);
+ },
+
+ /**
+ * Sets the global auto refresh value
+ *
+ * @param {integer} interval in seconds The numeric value for the auto refresh interval
+ */
+ setAutoRefreshInterval: function (interval) {
+ nfCommon.config.autoRefreshInterval = interval;
+ },
+
+ /**
+ * Gets the global auto refresh value
+ *
+ * @returns {integer}
+ */
+ getAutoRefreshInterval: function () {
+ return nfCommon.config.autoRefreshInterval;
}
};