NIFI-11520: Add a menu to display Flow Analysis report results (#8273)

* NIFI-11520: init ui work for flow analysis UI

* NIFI-11520: use .text() to render user input data

* NIFI-11520: update urls for analysis requests

* NIFI-11520: add WARN enforcement level to ui

* NIFI-11520: ui bug fixes

* fix rule bindings
* use correct count for rule violations

* NIFI-11520: move drawer markup into partial file

* NIFI-11520: remove old recs and policies naming

* NIFI-11520: comments and code cleanup

* NIFI-11520: fix linting errors

* NIFI-11520: restore refresh button logic

* NIFI-11520: remove timer

* NIFI-11520: add checkbox to only show warning violations

* NIFI-11520: style and copy changes

* change copy of violation checkboxes
* show correct details in violation dialog
* add overflow to violations menu

* NIFI-11520: add missing license header

* NIFI-11520: remove unused function

* NIFI-11520: cleanup rule and violation menu handling

* NIFI-11520: remove single use functions

* NIFI-11520: change function name to match established pattern

* NIFI-11520: rename function

* NIFI-11520: remove rule and violation details menu

* NIFI-11520: fix issue causing wrong documentation to be displayed

* NIFI-11520: fix go to button for components in nested process groups

* NIFI-11520: use refresh interval returned from the backend

* NIFI-11520: reload analysis when canvas is refreshed

* NIFI-11520: add violation details dialog with correct message

* NIFI-11520: disabled go to component when root group is violation

* NIFI-11520: remove unused CSS styles

* NIFI-11520: addressing more feedback:

* fix flow analysis drawer button styling
* disable edit rule if user does not have read permission
* fix broken warning list
* add loader and disable check now button while report is running

* NIFI-11520: handle violations without read permission

* NIFI-11520: disable go to component if not processor

* NIFI-11520: remove create analysis button and logic

* NIFI-11520: add pending analysis message

* NIFI-11520: protect against scenario where currentUser not loaded

* NIFI-11520: determine root group based on groupId being null

* NIFI-11520: address review feedback

* simplify go to logic by only showing for processors
* hide go to button instead of disabling

* NIFI-11520: fix hidden state

* NIFI-11520: hide go to based on permissions

This closes #8273
This commit is contained in:
Shane Ardell 2024-04-16 17:34:19 -04:00 committed by GitHub
parent 3f7085fec8
commit 4104cfd78d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1185 additions and 13 deletions

View File

@ -741,6 +741,7 @@
<include>${staging.dir}/css/settings.css</include>
<include>${staging.dir}/css/about.css</include>
<include>${staging.dir}/css/status-history.css</include>
<include>${staging.dir}/css/flow-analysis-drawer.css</include>
</includes>
</aggregation>
<aggregation>
@ -780,6 +781,7 @@
<include>${staging.dir}/css/processor-details.css</include>
<include>${staging.dir}/css/connection-details.css</include>
<include>${staging.dir}/css/status-history.css</include>
<include>${staging.dir}/css/flow-analysis-drawer.css</include>
<include>${staging.dir}/css/summary.css</include>
</includes>
</aggregation>

View File

@ -43,4 +43,5 @@ nf.summary.style.tags=<link rel="stylesheet" href="css/main.css?${project.versio
<link rel="stylesheet" href="css/connection-details.css?${project.version}" type="text/css" />\n\
<link rel="stylesheet" href="css/message-pane.css?${project.version}" type="text/css" />\n\
<link rel="stylesheet" href="css/status-history.css?${project.version}" type="text/css" />\n\
<link rel="stylesheet" href="css/flow-analysis-drawer.css?${project.version}" type="text/css" />\n\
<link rel="stylesheet" href="css/summary.css?${project.version}" type="text/css" />

View File

@ -128,6 +128,7 @@
<jsp:include page="/WEB-INF/partials/canvas/registry-configuration-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/new-registry-client-dialog.jsp"/>
<div id="canvas-container" class="unselectable"></div>
<jsp:include page="/WEB-INF/partials/canvas/flow-analysis-drawer.jsp"/>
<div id="canvas-tooltips">
<div id="processor-tooltips"></div>
<div id="port-tooltips"></div>
@ -145,6 +146,7 @@
<jsp:include page="/WEB-INF/partials/canvas/parameter-provider-configuration.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/processor-configuration.jsp"/>
<jsp:include page="/WEB-INF/partials/processor-details.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/violation-description-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/process-group-configuration.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/override-policy-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/policy-management.jsp"/>

View File

@ -0,0 +1,84 @@
<%--
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" %>
<section id="flow-analysis-drawer">
<div class="flow-analysis-header">
<div id="flow-analysis-loading-container" class="flow-analysis-loading-container"></div>
<div id="flow-analysis-loading-message" class="flow-analysis-loading-message">Rules analysis pending...</div>
</div>
<div class="flow-analysis-flow-guide-container">
<div class="flow-analysis-flow-guide">
<div class="flow-analysis-flow-guide-title">Flow Guide</div>
<div>
<div class="flow-analysis-violations-options">
<div class="nf-checkbox checkbox-unchecked" id="show-only-violations"></div>
<span class="nf-checkbox-label show-only-violations-label">Show enforced violations</span>
</div>
<div class="flow-analysis-warnings-options">
<div class="nf-checkbox checkbox-unchecked" id="show-only-warnings"></div>
<span class="nf-checkbox-label show-only-warnings-label">Show warning violations</span>
</div>
</div>
</div>
<div class="flow-analysis-flow-guide-breadcrumb">NiFi Flow</div>
</div>
<div id="flow-analysis-rules-accordion" class="flow-analysis-rules-accordion">
<div id="required-rules" class="required-rules">
<div>
<div>Enforced Rules <span id="required-rule-count" class="required-rule-count"></span></div>
</div>
<ul id="required-rules-list" class="required-rules-list">
</ul>
</div>
<div id="recommended-rules" class="recommended-rules">
<div>
<div>Warning Rules <span id="recommended-rule-count" class="recommended-rule-count"></span></div>
</div>
<ul id="recommended-rules-list" class="recommended-rules-list"></ul>
</div>
<div id="rule-violations" class="rule-violations">
<div class="rules-violations-header">
<div>Enforced Violations <span id="rule-violation-count" class="rule-violation-count"></span></div>
</div>
<ul id="rule-violations-list" class="rule-violations-list"></ul>
</div>
<div id="rule-warnings" class="rule-warnings">
<div class="rules-warnings-header">
<div>Warning Violations <span id="rule-warning-count" class="rule-warning-count"></span></div>
</div>
<ul id="rule-warnings-list" class="rule-warnings-list"></ul>
</div>
<div class="rule-menu" id="rule-menu">
<ul>
<li class="rule-menu-option" id="rule-menu-view-documentation"><i class="fa fa-info-circle rule-menu-option-icon" aria-hidden="true"></i>View Documentation</li>
<li class="rule-menu-option" id="rule-menu-edit-rule"><i class="fa fa-pencil rule-menu-option-icon" aria-hidden="true"></i>Edit Rule</li>
</ul>
</div>
<div class="violation-menu" id="violation-menu">
<ul>
<li class="violation-menu-option" id="violation-menu-more-info"><i class="fa fa-info-circle violation-menu-option-icon" aria-hidden="true"></i>Violation details</li>
<li class="violation-menu-option" id="violation-menu-go-to"><i class="fa fa-pencil violation-menu-option-icon" aria-hidden="true"></i>Go to component</li>
</ul>
</div>
</div>
</section>

View File

@ -71,6 +71,7 @@
<button id="search-button" ng-click="appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.search.toggleSearchField();"><i class="fa fa-search"></i></button>
<input id="search-field" type="text" placeholder="Search"/>
</div>
<button id="flow-analysis" class="flow-analysis"><i class="fa fa-lightbulb-o flow-analysis-notification-icon"></i></button>
<button id="bulletin-button"><i class="fa fa-sticky-note-o"></i></button>
</div>
</div>

View File

@ -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" %>
<div id="violation-menu-more-info-dialog" layout="column" class="hidden medium-dialog">
<div class="dialog-content">
<div class="violation-info-head">
<div id="violation-name" class="violation-name">Violation</div>
<div id="violation-type-pill" class="violation-type-pill"></div>
</div>
<div id="violation-display-name" class="violation-display-name"></div>
<p id="violation-description" class="violation-description"></p>
<i class="fa fa-book violation-docs-link-icon" aria-hidden="true"></i><a href="" class="violation-docs-link">View Documentation</a>
</div>
</div>

View File

@ -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);

View File

@ -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;
}

View File

@ -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 = $('<li></li>');
var violationEl = $('<div class="violation-list-item"></div>');
var violationListItemWrapperEl = $('<div class="violation-list-item-wrapper"></div>');
var violationRuleEl = $('<div class="rule-violations-list-item-name"></div>');
var violationListItemNameEl = $('<div class="violation-list-item-name"></div>');
var violationListItemIdEl = $('<span class="violation-list-item-id"></span>');
var violationInfoButtonEl = $('<button class="violation-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" aria-hidden="true"></i></button>');
// 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 = $('<li></li>');
var warningEl = $('<div class="warning-list-item"></div>');
var warningListItemWrapperEl = $('<div class="warning-list-item-wrapper"></div>');
var warningRuleEl = $('<div class="rule-warnings-list-item-name"></div>');
var warningListItemNameEl = $('<div class="warning-list-item-name"></div>');
var warningListItemIdEl = $('<span class="warning-list-item-id"></span>');
var warningInfoButtonEl = $('<button class="violation-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" aria-hidden="true"></i></button>');
// 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 = $('<li class="rules-list-item"></li>').append($(rec.requirement).append(rec.requirementInfoButton))
var violationsListEl = '';
var violationCountEl = '';
var violations = violationsMap.get(rec.id);
if (!!violations) {
if (violations.length === 1) {
violationCountEl = '<div class="rule-' + ruleType + 's-count">' + violations.length + ' ' + ruleType + '</div>';
} else {
violationCountEl = '<div class="rule-' + ruleType + 's-count">' + violations.length + ' ' + ruleType + 's</div>';
}
violationsListEl = $('<ul class="rule-' + ruleType + 's-list"></ul>');
violations.forEach(function(violation) {
// create DOM elements
var violationListItemEl = $('<li class="' + ruleType + '-list-item"></li>');
var violationWrapperEl = $('<div class="' + ruleType + '-list-item-wrapper"></div>');
var violationNameEl = $('<div class="' + ruleType + '-list-item-name"></div>');
var violationIdEl = $('<span class="' + ruleType + '-list-item-id"></span>');
var violationInfoButtonEl = $('<button class="violation-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" aria-hidden="true"></i></button>');
// 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 = '<div class="rules-list-rule-info"></div>';
var requirementName = $('<div></div>').text(rule.name);
var requirementInfoButton = '<button class="rule-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" aria-hidden="true"></i></button>';
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 = '<div class="rules-list-rule-info"></div>';
var requirementName = $('<div></div>').text(rule.name);
var requirementInfoButton = '<button class="rule-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" aria-hidden="true"></i></button>';
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();
}
}

View File

@ -1223,6 +1223,7 @@
*/
reload: function () {
nfCanvasUtils.reload();
nfNgBridge.injector.get('flowStatusCtrl').reloadFlowPolicies();
},
/**

View File

@ -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);

View File

@ -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']

View File

@ -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;
}
};