diff options
author | 2020-02-10 23:00:13 +0000 | |
---|---|---|
committer | 2020-02-10 23:00:13 +0000 | |
commit | 45341fa5e6e64e86286254d49ca951c43726ab94 (patch) | |
tree | 6bf7eeb9143b1a23aa823f924f0922de11854d88 /mod/designtool/designtool-web/src/main/webapp/js/nf/canvas | |
parent | e0734cbb649157811a8a00dbb8e2dad2cbe28782 (diff) | |
parent | 9507f2f8d2ec616f01f5ee8825106300b95e8ddc (diff) |
Merge "Add DCAE MOD design tool project"
Diffstat (limited to 'mod/designtool/designtool-web/src/main/webapp/js/nf/canvas')
6 files changed, 9132 insertions, 0 deletions
diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js new file mode 100644 index 0000000..0005837 --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js @@ -0,0 +1,288 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'nf.Common'], + function ($, nfCommon) { + return (nf.ng.BreadcrumbsCtrl = factory($, nfCommon)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ng.BreadcrumbsCtrl = + factory(require('jquery'), + require('nf.Common'))); + } else { + nf.ng.BreadcrumbsCtrl = factory(root.$, + root.nf.Common); + } +}(this, function ($, nfCommon) { + 'use strict'; + + return function (serviceProvider) { + 'use strict'; + + function BreadcrumbsCtrl() { + this.breadcrumbs = []; + } + + BreadcrumbsCtrl.prototype = { + constructor: BreadcrumbsCtrl, + + /** + * Register the breadcrumbs controller. + */ + register: function () { + if (serviceProvider.breadcrumbsCtrl === undefined) { + serviceProvider.register('breadcrumbsCtrl', breadcrumbsCtrl); + } + }, + + /** + * Generate the breadcrumbs. + * + * @param {object} breadcrumbEntity The breadcrumb + */ + generateBreadcrumbs: function (breadcrumbEntity) { + var label = breadcrumbEntity.id; + if (breadcrumbEntity.permissions.canRead) { + label = breadcrumbEntity.breadcrumb.name; + } + + this.breadcrumbs.unshift($.extend({ + 'label': label + }, breadcrumbEntity)); + + if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.parentBreadcrumb)) { + this.generateBreadcrumbs(breadcrumbEntity.parentBreadcrumb); + } + }, + + /** + * Updates the version control information for the specified process group. + * + * @param processGroupId + * @param versionControlInformation + */ + updateVersionControlInformation: function (processGroupId, versionControlInformation) { + $.each(this.breadcrumbs, function (_, breadcrumbEntity) { + if (breadcrumbEntity.id === processGroupId) { + breadcrumbEntity.breadcrumb.versionControlInformation = versionControlInformation; + return false; + } + }); + }, + + /** + * Reset the breadcrumbs. + */ + resetBreadcrumbs: function () { + this.breadcrumbs = []; + }, + + /** + * Whether this crumb is tracking. + * + * @param breadcrumbEntity + * @returns {*} + */ + isTracking: function (breadcrumbEntity) { + return nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState); + }, + + /** + * Returns the class string to use for the version control of the specified breadcrumb. + * + * @param breadcrumbEntity + * @returns {string} + */ + getVersionControlClass: function (breadcrumbEntity) { + if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState)) { + var vciState = breadcrumbEntity.versionedFlowState; + if (vciState === 'SYNC_FAILURE') { + console.log("it is been sync failed..000"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return 'breadcrumb-version-control-gray fa fa-question' + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { + console.log("it is been locally modified and stale...000"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return 'breadcrumb-version-control-red fa fa-exclamation-circle'; + } else if (vciState === 'STALE') { + console.log("it is been stale...000"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return 'breadcrumb-version-control-red fa fa-arrow-circle-up'; + } else if (vciState === 'LOCALLY_MODIFIED') { + console.log("it is been locally modified...000"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return 'breadcrumb-version-control-gray fa fa-asterisk'; + } else { + $('#environmentType').prop('disabled', false); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + return 'breadcrumb-version-control-green fa fa-check'; + } + } else { + console.log("it is NOT been version controlled...000"); + $('#environmentType').prop('disabled', true); + return ''; + } + }, + + /** + * Gets the content for the version control tooltip for the specified breadcrumb. + * + * @param breadcrumbEntity + */ + getVersionControlTooltip: function (breadcrumbEntity) { + if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.versionedFlowState) && breadcrumbEntity.permissions.canRead) { + return nfCommon.getVersionControlTooltip(breadcrumbEntity.breadcrumb.versionControlInformation); + } else { + return 'This Process Group is not under version control.' + } + }, + + /** + * Get the breadcrumbs. + */ + getBreadcrumbs: function () { + return this.breadcrumbs; + }, + + /** + * Update the breadcrumbs css. + * + * @param {object} style The style to be applied. + */ + updateBreadcrumbsCss: function (style) { + $('#breadcrumbs').css(style); + }, + + /** + * Reset initial scroll position. + */ + resetScrollPosition: function () { + var title = $('#data-flow-title-container'); + var titlePosition = title.position(); + var titleWidth = title.outerWidth(); + var titleRight = titlePosition.left + titleWidth; + + var padding = $('#breadcrumbs-right-border').width(); + var viewport = $('#data-flow-title-viewport'); + var viewportWidth = viewport.width(); + var viewportRight = viewportWidth - padding; + + // if the title's right is past the viewport's right, shift accordingly + if (titleRight > viewportRight) { + // adjust the position + title.css('left', (titlePosition.left - (titleRight - viewportRight)) + 'px'); + } else { + title.css('left', '10px'); + } + }, + + /** + * Registers a scroll event on the `element` + * + * @param {object} element The element event listener will be registered upon. + */ + registerMouseWheelEvent: function (element) { + // mousewheel -> IE, Chrome + // DOMMouseScroll -> FF + // wheel -> FF, IE + + // still having issues with this in IE :/ + element.on('DOMMouseScroll mousewheel', function (evt, d) { + if (nfCommon.isUndefinedOrNull(evt.originalEvent)) { + return; + } + + var title = $('#data-flow-title-container'); + var titlePosition = title.position(); + var titleWidth = title.outerWidth(); + var titleRight = titlePosition.left + titleWidth; + + var padding = $('#breadcrumbs-right-border').width(); + var viewport = $('#data-flow-title-viewport'); + var viewportWidth = viewport.width(); + var viewportRight = viewportWidth - padding; + + // if the width of the title is larger than the viewport + if (titleWidth > viewportWidth) { + var adjust = false; + + var delta = 0; + + //Chrome and Safari both have evt.originalEvent.detail defined but + //evt.originalEvent.wheelDelta holds the correct value so we must + //check for evt.originalEvent.wheelDelta first! + if (nfCommon.isDefinedAndNotNull(evt.originalEvent.wheelDelta)) { + delta = evt.originalEvent.wheelDelta; + } else if (nfCommon.isDefinedAndNotNull(evt.originalEvent.detail)) { + delta = -evt.originalEvent.detail; + } + + // determine the increment + if (delta > 0 && titleRight > viewportRight) { + var increment = -25; + adjust = true; + } else if (delta < 0 && (titlePosition.left - padding) < 0) { + increment = 25; + + // don't shift too far + if (titlePosition.left + increment > padding) { + increment = padding - titlePosition.left; + } + + adjust = true; + } + + if (adjust) { + // adjust the position + title.css('left', (titlePosition.left + increment) + 'px'); + } + } + }); + } + } + + var breadcrumbsCtrl = new BreadcrumbsCtrl(); + breadcrumbsCtrl.register(); + return breadcrumbsCtrl; + } +})); diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js new file mode 100644 index 0000000..0f4b953 --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js @@ -0,0 +1,1150 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'Slick', + 'nf.Client', + 'nf.Birdseye', + 'nf.Storage', + 'nf.Graph', + 'nf.CanvasUtils', + 'nf.ErrorHandler', + 'nf.FilteredDialogCommon', + 'nf.Dialog', + 'nf.Common'], + function ($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon) { + return (nf.ng.ProcessorComponent = factory($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ng.ProcessorComponent = + factory(require('jquery'), + require('Slick'), + require('nf.Client'), + require('nf.Birdseye'), + require('nf.Storage'), + require('nf.Graph'), + require('nf.CanvasUtils'), + require('nf.ErrorHandler'), + require('nf.FilteredDialogCommon'), + require('nf.Dialog'), + require('nf.Common'))); + } else { + nf.ng.ProcessorComponent = factory(root.$, + root.Slick, + root.nf.Client, + root.nf.Birdseye, + root.nf.Storage, + root.nf.Graph, + root.nf.CanvasUtils, + root.nf.ErrorHandler, + root.nf.FilteredDialogCommon, + root.nf.Dialog, + root.nf.Common); + } +}(this, function ($, Slick, nfClient, nfBirdseye, nfStorage, nfGraph, nfCanvasUtils, nfErrorHandler, nfFilteredDialogCommon, nfDialog, nfCommon) { + 'use strict'; + + return function (serviceProvider) { + 'use strict'; + + + var latestResponse; + + var processorTypesData; + + + /** + * Filters the processor type table. + */ + var applyFilter = function () { + // get the dataview + var processorTypesGrid = $('#processor-types-table').data('gridInstance'); + + // ensure the grid has been initialized + if (nfCommon.isDefinedAndNotNull(processorTypesGrid)) { + var processorTypesDataForFilter = processorTypesGrid.getData(); + + // update the search criteria + processorTypesDataForFilter.setFilterArgs({ + searchString: getFilterText() + }); + processorTypesDataForFilter.refresh(); + + // update the buttons to possibly trigger the disabled state + $('#new-processor-dialog').modal('refreshButtons'); + + // update the selection if possible + if (processorTypesDataForFilter.getLength() > 0) { + nfFilteredDialogCommon.choseFirstRow(processorTypesGrid); + // make the first row visible + processorTypesGrid.scrollRowToTop(0); + } + } + }; + + /** + * Determines if the item matches the filter. + * + * @param {object} item The item to filter. + * @param {object} args The filter criteria. + * @returns {boolean} Whether the item matches the filter. + */ + var matchesRegex = function (item, args) { + if (args.searchString === '') { + return true; + } + + try { + // perform the row filtering + var filterExp = new RegExp(args.searchString, 'i'); + } catch (e) { + // invalid regex + return false; + } + + // determine if the item matches the filter + var matchesLabel = item['label'].search(filterExp) >= 0; + var matchesTags = item['tags'].search(filterExp) >= 0; + return matchesLabel || matchesTags; + }; + + /** + * Performs the filtering. + * + * @param {object} item The item subject to filtering. + * @param {object} args Filter arguments. + * @returns {Boolean} Whether or not to include the item. + */ + var filter = function (item, args) { + // determine if the item matches the filter + var matchesFilter = matchesRegex(item, args); + + // determine if the row matches the selected tags + var matchesTags = true; + if (matchesFilter) { + var tagFilters = $('#processor-tag-cloud').tagcloud('getSelectedTags'); + var hasSelectedTags = tagFilters.length > 0; + if (hasSelectedTags) { + matchesTags = matchesSelectedTags(tagFilters, item['tags']); + } + } + + // determine if the row matches the selected source group + var matchesGroup = true; + if (matchesFilter && matchesTags) { + var bundleGroup = $('#processor-bundle-group-combo').combo('getSelectedOption'); + if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') { + matchesGroup = (item.bundle.group === bundleGroup.value); + } + } + + // determine if this row should be visible + var matches = matchesFilter && matchesTags && matchesGroup; + + // if this row is currently selected and its being filtered + if (matches === false && $('#selected-processor-type').text() === item['type']) { + // clear the selected row + $('#processor-type-description').attr('title', '').text(''); + $('#processor-type-name').attr('title', '').text(''); + $('#processor-type-bundle').attr('title', '').text(''); + $('#selected-processor-name').text(''); + $('#selected-processor-type').text('').removeData('bundle'); + + // clear the active cell the it can be reselected when its included + var processTypesGrid = $('#processor-types-table').data('gridInstance'); + processTypesGrid.resetActiveCell(); + } + + return matches; + }; + + /** + * Determines if the specified tags match all the tags selected by the user. + * + * @argument {string[]} tagFilters The tag filters. + * @argument {string} tags The tags to test. + */ + var matchesSelectedTags = function (tagFilters, tags) { + var selectedTags = []; + $.each(tagFilters, function (_, filter) { + selectedTags.push(filter); + }); + + // normalize the tags + var normalizedTags = tags.toLowerCase(); + + var matches = true; + $.each(selectedTags, function (i, selectedTag) { + if (normalizedTags.indexOf(selectedTag) === -1) { + matches = false; + return false; + } + }); + + return matches; + }; + + /** + * Get the text out of the filter field. If the filter field doesn't + * have any text it will contain the text 'filter list' so this method + * accounts for that. + */ + var getFilterText = function () { + return $('#processor-type-filter').val(); + }; + + /** + * Resets the filtered processor types. + */ + var resetProcessorDialog = function () { + + //********* REPLICATED A BLOCK OF CODE to get logic of autoloading processor ---STARTING FROM HERE----******************** + + // initialize the processor type table + var processorTypesColumns = [ + { + id: 'type', + name: 'Type', + field: 'label', + formatter: nfCommon.typeFormatter, + sortable: true, + resizable: true + }, + { + id: 'version', + name: 'Version', + field: 'version', + formatter: nfCommon.typeVersionFormatter, + sortable: true, + resizable: true + }, + { + id: 'tags', + name: 'Tags', + field: 'tags', + sortable: true, + resizable: true, + formatter: nfCommon.genericValueFormatter + } + ]; + + var processorTypesOptions = { + forceFitColumns: true, + enableTextSelectionOnCells: true, + enableCellNavigation: true, + enableColumnReorder: false, + autoEdit: false, + multiSelect: false, + rowHeight: 24 + }; + + // initialize the dataview + processorTypesData = new Slick.Data.DataView({ + inlineFilters: false + }); + processorTypesData.setItems([]); + processorTypesData.setFilterArgs({ + searchString: getFilterText() + }); + processorTypesData.setFilter(filter); + + // initialize the sort + nfCommon.sortType({ + columnId: 'type', + sortAsc: true + }, processorTypesData); + + // initialize the grid + var processorTypesGrid = new Slick.Grid('#processor-types-table', processorTypesData, processorTypesColumns, processorTypesOptions); + processorTypesGrid.setSelectionModel(new Slick.RowSelectionModel()); + processorTypesGrid.registerPlugin(new Slick.AutoTooltips()); + processorTypesGrid.setSortColumn('type', true); + processorTypesGrid.onSort.subscribe(function (e, args) { + nfCommon.sortType({ + columnId: args.sortCol.field, + sortAsc: args.sortAsc + }, processorTypesData); + }); + processorTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) { + if ($.isArray(args.rows) && args.rows.length === 1) { + var processorTypeIndex = args.rows[0]; + var processorType = processorTypesGrid.getDataItem(processorTypeIndex); + + // set the processor type description + if (nfCommon.isDefinedAndNotNull(processorType)) { + if (nfCommon.isBlank(processorType.description)) { + $('#processor-type-description') + .attr('title', '') + .html('<span class="unset">No description specified</span>'); + } else { + $('#processor-type-description') + .width($('#processor-description-container').innerWidth() - 1) + .html(processorType.description) + .ellipsis(); + } + + var bundle = nfCommon.formatBundle(processorType.bundle); + var type = nfCommon.formatType(processorType); + + // populate the dom + $('#processor-type-name').text(type).attr('title', type); + $('#processor-type-bundle').text(bundle).attr('title', bundle); + $('#selected-processor-name').text(processorType.label); + $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle); + + // refresh the buttons based on the current selection + $('#new-processor-dialog').modal('refreshButtons'); + } + } + }); + processorTypesGrid.onViewportChanged.subscribe(function (e, args) { + nfCommon.cleanUpTooltips($('#processor-types-table'), 'div.view-usage-restriction'); + }); + + // wire up the dataview to the grid + processorTypesData.onRowCountChanged.subscribe(function (e, args) { + processorTypesGrid.updateRowCount(); + processorTypesGrid.render(); + + // update the total number of displayed processors + $('#displayed-processor-types').text(args.current); + }); + processorTypesData.onRowsChanged.subscribe(function (e, args) { + processorTypesGrid.invalidateRows(args.rows); + processorTypesGrid.render(); + }); + processorTypesData.syncGridSelection(processorTypesGrid, false); + + // hold onto an instance of the grid + $('#processor-types-table').data('gridInstance', processorTypesGrid).on('mouseenter', 'div.slick-cell', function (e) { + var usageRestriction = $(this).find('div.view-usage-restriction'); + if (usageRestriction.length && !usageRestriction.data('qtip')) { + var rowId = $(this).find('span.row-id').text(); + + // get the status item + var item = processorTypesData.getItemById(rowId); + + // show the tooltip + if (item.restricted === true) { + var restrictionTip = $('<div></div>'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:')); + } else { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { + content: restrictionTip, + position: { + container: $('#summary'), + at: 'bottom right', + my: 'top left', + adjust: { + x: 4, + y: 4 + } + } + })); + } + } + }); + + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + + // load the available processor types, this select is shown in the + // new processor dialog when a processor is dragged onto the screen + $.ajax({ + type: 'GET', + url:'../nifi-api/flow/processor-types', + // url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.processorTypes, + dataType: 'json' + }).done(function (response) { + console.log(response); + var tags = []; + var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); + + // begin the update + processorTypesData.beginUpdate(); + + // go through each processor type + $.each(response.processorTypes, function (i, documentedType) { + var type = documentedType.type; + + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(explicitRestriction.explanation) + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(documentedType.usageRestriction) + }); + } + } + + // record the group + groups.add(documentedType.bundle.group); + + // create the row for the processor type + processorTypesData.addItem({ + id: i, + label: nfCommon.substringAfterLast(type, '.'), + type: type, + bundle: documentedType.bundle, + description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, + usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, + tags: documentedType.tags.join(', ') + }); + + // count the frequency of each tag for this type + $.each(documentedType.tags, function (i, tag) { + tags.push(tag.toLowerCase()); + }); + }); + + // end the update + processorTypesData.endUpdate(); + + // resort + processorTypesData.reSort(); + processorTypesGrid.invalidate(); + + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + + // set the total number of processors + $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length); + + // create the tag cloud + $('#processor-tag-cloud').tagcloud({ + tags: tags, + select: applyFilter, + remove: applyFilter + }); + + // build the combo options + var options = [{ + text: 'all groups', + value: '' + }]; + groups.each(function (group) { + options.push({ + text: group, + value: group + }); + }); + + // initialize the bundle group combo + $('#processor-bundle-group-combo').combo({ + options: options, + select: applyFilter + }); + }).fail(nfErrorHandler.handleAjaxError); + +//************* REPLICATED CODE---ENDS HERE------****************** + + + + // clear the selected tag cloud + $('#processor-tag-cloud').tagcloud('clearSelectedTags'); + + // reset the group combo + $('#processor-bundle-group-combo').combo('setSelectedOption', { + value: '' + }); + + // clear any filter strings + $('#processor-type-filter').val(''); + + // reapply the filter + applyFilter(); + + // clear the selected row + $('#processor-type-description').attr('title', '').text(''); + $('#processor-type-name').attr('title', '').text(''); + $('#processor-type-bundle').attr('title', '').text(''); + $('#selected-processor-name').text(''); + $('#selected-processor-type').text('').removeData('bundle'); + + // unselect any current selection + var processTypesGrid = $('#processor-types-table').data('gridInstance'); + processTypesGrid.setSelectedRows([]); + processTypesGrid.resetActiveCell(); + }; + + /** + * Create the processor and add to the graph. + * + * @argument {string} name The processor name. + * @argument {string} processorType The processor type. + * @argument {object} bundle The processor bundle. + * @argument {object} pt The point that the processor was dropped. + */ + var createProcessor = function (name, processorType, bundle, pt) { + var processorEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'type': processorType, + 'bundle': bundle, + 'name': name, + 'position': { + 'x': pt.x, + 'y': pt.y + } + } + }; + + // create a new processor of the defined type + $.ajax({ + type: 'POST', + url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/processors', + data: JSON.stringify(processorEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // add the processor to the graph + nfGraph.add({ + 'processors': [response] + }, { + 'selectAll': true + }); + + // update component visibility + nfGraph.updateVisibility(); + + // update the birdseye + nfBirdseye.refresh(); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Whether the specified item is selectable. + * + * @param item process type + */ + var isSelectable = function (item) { + return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions); + }; + + function ProcessorComponent() { + + this.icon = 'icon icon-processor'; + + this.hoverIcon = 'icon icon-processor-add'; + + /** + * The processor component's modal. + */ + this.modal = { + + /** + * The processor component modal's filter. + */ + filter: { + + /** + * Initialize the filter. + */ + init: function () { + // initialize the processor type table + var processorTypesColumns = [ + { + id: 'type', + name: 'Type', + field: 'label', + formatter: nfCommon.typeFormatter, + sortable: true, + resizable: true + }, + { + id: 'version', + name: 'Version', + field: 'version', + formatter: nfCommon.typeVersionFormatter, + sortable: true, + resizable: true + }, + { + id: 'tags', + name: 'Tags', + field: 'tags', + sortable: true, + resizable: true, + formatter: nfCommon.genericValueFormatter + } + ]; + + var processorTypesOptions = { + forceFitColumns: true, + enableTextSelectionOnCells: true, + enableCellNavigation: true, + enableColumnReorder: false, + autoEdit: false, + multiSelect: false, + rowHeight: 24 + }; + + // initialize the dataview + processorTypesData = new Slick.Data.DataView({ + inlineFilters: false + }); + processorTypesData.setItems([]); + processorTypesData.setFilterArgs({ + searchString: getFilterText() + }); + processorTypesData.setFilter(filter); + + // initialize the sort + nfCommon.sortType({ + columnId: 'type', + sortAsc: true + }, processorTypesData); + + // initialize the grid + var processorTypesGrid = new Slick.Grid('#processor-types-table', processorTypesData, processorTypesColumns, processorTypesOptions); + processorTypesGrid.setSelectionModel(new Slick.RowSelectionModel()); + processorTypesGrid.registerPlugin(new Slick.AutoTooltips()); + processorTypesGrid.setSortColumn('type', true); + processorTypesGrid.onSort.subscribe(function (e, args) { + nfCommon.sortType({ + columnId: args.sortCol.field, + sortAsc: args.sortAsc + }, processorTypesData); + }); + processorTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) { + if ($.isArray(args.rows) && args.rows.length === 1) { + var processorTypeIndex = args.rows[0]; + var processorType = processorTypesGrid.getDataItem(processorTypeIndex); + + // set the processor type description + if (nfCommon.isDefinedAndNotNull(processorType)) { + if (nfCommon.isBlank(processorType.description)) { + $('#processor-type-description') + .attr('title', '') + .html('<span class="unset">No description specified</span>'); + } else { + $('#processor-type-description') + .width($('#processor-description-container').innerWidth() - 1) + .html(processorType.description) + .ellipsis(); + } + + var bundle = nfCommon.formatBundle(processorType.bundle); + var type = nfCommon.formatType(processorType); + + // populate the dom + $('#processor-type-name').text(type).attr('title', type); + $('#processor-type-bundle').text(bundle).attr('title', bundle); + $('#selected-processor-name').text(processorType.label); + $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle); + + // refresh the buttons based on the current selection + $('#new-processor-dialog').modal('refreshButtons'); + } + } + }); + processorTypesGrid.onViewportChanged.subscribe(function (e, args) { + nfCommon.cleanUpTooltips($('#processor-types-table'), 'div.view-usage-restriction'); + }); + + // wire up the dataview to the grid + processorTypesData.onRowCountChanged.subscribe(function (e, args) { + processorTypesGrid.updateRowCount(); + processorTypesGrid.render(); + + // update the total number of displayed processors + $('#displayed-processor-types').text(args.current); + }); + processorTypesData.onRowsChanged.subscribe(function (e, args) { + processorTypesGrid.invalidateRows(args.rows); + processorTypesGrid.render(); + }); + processorTypesData.syncGridSelection(processorTypesGrid, false); + + // hold onto an instance of the grid + $('#processor-types-table').data('gridInstance', processorTypesGrid).on('mouseenter', 'div.slick-cell', function (e) { + var usageRestriction = $(this).find('div.view-usage-restriction'); + if (usageRestriction.length && !usageRestriction.data('qtip')) { + var rowId = $(this).find('span.row-id').text(); + + // get the status item + var item = processorTypesData.getItemById(rowId); + + // show the tooltip + if (item.restricted === true) { + var restrictionTip = $('<div></div>'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:')); + } else { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { + content: restrictionTip, + position: { + container: $('#summary'), + at: 'bottom right', + my: 'top left', + adjust: { + x: 4, + y: 4 + } + } + })); + } + } + }); + + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + + // load the available processor types, this select is shown in the + // new processor dialog when a processor is dragged onto the screen + $.ajax({ + type: 'GET', + url:'../nifi-api/flow/processor-types', +// url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.processorTypes, + dataType: 'json' + }).done(function (response) { + console.log(response); + var tags = []; + var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); + + // begin the update + processorTypesData.beginUpdate(); + + // go through each processor type + $.each(response.processorTypes, function (i, documentedType) { + var type = documentedType.type; + + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(explicitRestriction.explanation) + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(documentedType.usageRestriction) + }); + } + } + + // record the group + groups.add(documentedType.bundle.group); + + // create the row for the processor type + processorTypesData.addItem({ + id: i, + label: nfCommon.substringAfterLast(type, '.'), + type: type, + bundle: documentedType.bundle, + description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, + usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, + tags: documentedType.tags.join(', ') + }); + + // count the frequency of each tag for this type + $.each(documentedType.tags, function (i, tag) { + tags.push(tag.toLowerCase()); + }); + }); + + // end the update + processorTypesData.endUpdate(); + + // resort + processorTypesData.reSort(); + processorTypesGrid.invalidate(); + + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + + // set the total number of processors + $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length); + + // create the tag cloud + $('#processor-tag-cloud').tagcloud({ + tags: tags, + select: applyFilter, + remove: applyFilter + }); + + // build the combo options + var options = [{ + text: 'all groups', + value: '' + }]; + groups.each(function (group) { + options.push({ + text: group, + value: group + }); + }); + + // initialize the bundle group combo + $('#processor-bundle-group-combo').combo({ + options: options, + select: applyFilter + }); + }).fail(nfErrorHandler.handleAjaxError); + } + }, + + /** + * Gets the modal element. + * + * @returns {*|jQuery|HTMLElement} + */ + getElement: function () { + return $('#new-processor-dialog'); + }, + + /** + * Initialize the modal. + */ + init: function () { + this.filter.init(); + + // configure the new processor dialog + this.getElement().modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Add Processor', + handler: { + resize: function () { + $('#processor-type-description') + .width($('#processor-description-container').innerWidth() - 1) + .text($('#processor-type-description').attr('title')) + .ellipsis(); + } + } + }); + }, + + /** + * Updates the modal config. + * + * @param {string} name The name of the property to update. + * @param {object|array} config The config for the `name`. + */ + update: function (name, config) { + this.getElement().modal(name, config); + }, + + /** + * Show the modal + */ + show: function () { + this.getElement().modal('show'); + }, + + /** + * Hide the modal + */ + hide: function () { + this.getElement().modal('hide'); + } + }; + } + + ProcessorComponent.prototype = { + constructor: ProcessorComponent, + + /** + * Gets the component. + * + * @returns {*|jQuery|HTMLElement} + */ + getElement: function () { + return $('#processor-component'); + }, + + /** + * Enable the component. + */ + enabled: function () { + this.getElement().attr('disabled', false); + }, + + /** + * Disable the component. + */ + disabled: function () { + this.getElement().attr('disabled', true); + }, + + /** + * Handler function for when component is dropped on the canvas. + * + * @argument {object} pt The point that the component was dropped + */ + dropHandler: function (pt) { + this.promptForProcessorType(pt); + }, + + /** + * The drag icon for the toolbox component. + * + * @param event + * @returns {*|jQuery|HTMLElement} + */ + dragIcon: function (event) { + return $('<div class="icon icon-processor-add"></div>'); + }, + + /** + * Prompts the user to select the type of new processor to create. + * + * @argument {object} pt The point that the processor was dropped + */ + promptForProcessorType: function (pt) { + var processorComponent = this; + + // handles adding the selected processor at the specified point + var addProcessor = function () { + // get the type of processor currently selected + var name = $('#selected-processor-name').text(); + var processorType = $('#selected-processor-type').text(); + var bundle = $('#selected-processor-type').data('bundle'); + + // ensure something was selected + if (name === '' || processorType === '') { + nfDialog.showOkDialog({ + headerText: 'Add Processor', + dialogContent: 'The type of processor to create must be selected.' + }); + } else { + // create the new processor + createProcessor(name, processorType, bundle, pt); + } + + // hide the dialog + processorComponent.modal.hide(); + }; + + // get the grid reference + var grid = $('#processor-types-table').data('gridInstance'); + var dataview = grid.getData(); + + // add the processor when its double clicked in the table + var gridDoubleClick = function (e, args) { + var processorType = grid.getDataItem(args.row); + + if (isSelectable(processorType)) { + $('#selected-processor-name').text(processorType.label); + $('#selected-processor-type').text(processorType.type).data('bundle', processorType.bundle); + + addProcessor(); + } + }; + + // register a handler for double click events + grid.onDblClick.subscribe(gridDoubleClick); + + // update the button model + this.modal.update('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + var selected = grid.getSelectedRows(); + + if (selected.length > 0) { + // grid configured with multi-select = false + var item = grid.getDataItem(selected[0]); + return isSelectable(item) === false; + } else { + return dataview.getLength() === 0; + } + }, + handler: { + click: addProcessor + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $('#new-processor-dialog').modal('hide'); + } + } + }]); + + // set a new handler for closing the the dialog + this.modal.update('setCloseHandler', function () { + // remove the handler + grid.onDblClick.unsubscribe(gridDoubleClick); + + // clear the current filters + resetProcessorDialog(); + }); + + // show the dialog + this.modal.show(); + + var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN]; + + // setup the filter + $('#processor-type-filter').off('keyup').on('keyup', function (e) { + var code = e.keyCode ? e.keyCode : e.which; + + // ignore navigation keys + if ($.inArray(code, navigationKeys) !== -1) { + return; + } + + if (code === $.ui.keyCode.ENTER) { + var selected = grid.getSelectedRows(); + + if (selected.length > 0) { + // grid configured with multi-select = false + var item = grid.getDataItem(selected[0]); + if (isSelectable(item)) { + addProcessor(); + } + } + } else { + applyFilter(); + } + }); + + // setup row navigation + nfFilteredDialogCommon.addKeydownListener('#processor-type-filter', grid, dataview); + + // adjust the grid canvas now that its been rendered + grid.resizeCanvas(); + + // auto select the first row if possible + if (dataview.getLength() > 0) { + nfFilteredDialogCommon.choseFirstRow(grid); + } + + // set the initial focus + $('#processor-type-filter').focus() + } + }; + + var processorComponent = new ProcessorComponent(); + return processorComponent; + }; +})); diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js new file mode 100644 index 0000000..0cdb1a1 --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js @@ -0,0 +1,1587 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'd3', + 'nf.ErrorHandler', + 'nf.Common', + 'nf.Dialog', + 'nf.Storage', + 'nf.Client', + 'nf.CanvasUtils', + 'nf.Connection'], + function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { + return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ConnectionConfiguration = + factory(require('jquery'), + require('d3'), + require('nf.ErrorHandler'), + require('nf.Common'), + require('nf.Dialog'), + require('nf.Storage'), + require('nf.Client'), + require('nf.CanvasUtils'), + require('nf.Connection'))); + } else { + nf.ConnectionConfiguration = factory(root.$, + root.d3, + root.nf.ErrorHandler, + root.nf.Common, + root.nf.Dialog, + root.nf.Storage, + root.nf.Client, + root.nf.CanvasUtils, + root.nf.Connection); + } +}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { + 'use strict'; + + var nfBirdseye; + var nfGraph; + + var defaultBackPressureObjectThreshold; + var defaultBackPressureDataSizeThreshold; + + var CONNECTION_OFFSET_Y_INCREMENT = 75; + var CONNECTION_OFFSET_X_INCREMENT = 200; + + var config = { + urls: { + api: '../nifi-api', + prioritizers: '../nifi-api/flow/prioritizers' + } + }; + + /** + * Removes the temporary if necessary. + */ + var removeTempEdge = function () { + d3.select('path.connector').remove(); + }; + + /** + * Activates dialog's button model refresh on a connection relationships change. + */ + var addDialogRelationshipsChangeListener = function() { + // refresh button model when a relationship selection changes + $('div.available-relationship').bind('change', function() { + $('#connection-configuration').modal('refreshButtons'); + }); + } + + /** + * Initializes the source in the new connection dialog. + * + * @argument {selection} source The source + */ + var initializeSourceNewConnectionDialog = function (source) { + // handle the selected source + if (nfCanvasUtils.isProcessor(source)) { + return $.Deferred(function (deferred) { + // initialize the source processor + initializeSourceProcessor(source).done(function (processor) { + if (!nfCommon.isEmpty(processor.relationships)) { + // populate the available connections + $.each(processor.relationships, function (i, relationship) { + createRelationshipOption(relationship.name); + }); + + // resolve the deferred + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(processor.name) + '\' does not support any relationships.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } else { + return $.Deferred(function (deferred) { + // determine how to initialize the source + var connectionSourceDeferred; + if (nfCanvasUtils.isInputPort(source)) { + connectionSourceDeferred = initializeSourceInputPort(source); + } else if (nfCanvasUtils.isRemoteProcessGroup(source)) { + connectionSourceDeferred = initializeSourceRemoteProcessGroup(source); + } else if (nfCanvasUtils.isProcessGroup(source)) { + connectionSourceDeferred = initializeSourceProcessGroup(source); + } else { + connectionSourceDeferred = initializeSourceFunnel(source); + } + + // finish initialization when appropriate + connectionSourceDeferred.done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } + }; + + /** + * Initializes the source when the source is an input port. + * + * @argument {selection} source The source + */ + var initializeSourceInputPort = function (source) { + return $.Deferred(function (deferred) { + // get the input port data + var inputPortData = source.datum(); + var inputPortName = inputPortData.permissions.canRead ? inputPortData.component.name : inputPortData.id; + + // populate the port information + $('#input-port-source').show(); + $('#input-port-source-name').text(inputPortName).attr('title', inputPortName); + + // populate the connection source details + $('#connection-source-id').val(inputPortData.id); + $('#connection-source-component-id').val(inputPortData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source when the source is an input port. + * + * @argument {selection} source The source + */ + var initializeSourceFunnel = function (source) { + return $.Deferred(function (deferred) { + // get the funnel data + var funnelData = source.datum(); + + // populate the port information + $('#funnel-source').show(); + + // populate the connection source details + $('#connection-source-id').val(funnelData.id); + $('#connection-source-component-id').val(funnelData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source when the source is a processor. + * + * @argument {selection} source The source + */ + var initializeSourceProcessor = function (source) { + return $.Deferred(function (deferred) { + // get the processor data + var processorData = source.datum(); + var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id; + var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor'; + + // populate the source processor information + $('#processor-source').show(); + $('#processor-source-name').text(processorName).attr('title', processorName); + $('#processor-source-type').text(processorType).attr('title', processorType); + + // populate the connection source details + $('#connection-source-id').val(processorData.id); + $('#connection-source-component-id').val(processorData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // show the available relationships + $('#relationship-names-container').show(); + + deferred.resolve(processorData.component); + }); + }; + + /** + * Initializes the source when the source is a process group. + * + * @argument {selection} source The source + */ + var initializeSourceProcessGroup = function (source) { + return $.Deferred(function (deferred) { + // get the process group data + var processGroupData = source.datum(); + + $.ajax({ + type: 'GET', + url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id), + dataType: 'json' + }).done(function (response) { + var processGroup = response.processGroupFlow; + var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id; + var processGroupContents = processGroup.flow; + + // show the output port options + var options = []; + $.each(processGroupContents.outputPorts, function (i, outputPort) { + // require explicit access to the output port as it's the source of the connection + if (outputPort.permissions.canRead && outputPort.permissions.canWrite) { + var component = outputPort.component; + options.push({ + text: component.name, + value: component.id, + description: nfCommon.escapeHtml(component.comments) + }); + } + }); + + // only proceed if there are output ports + if (!nfCommon.isEmpty(options)) { + $('#output-port-source').show(); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#output-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-source-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-source-component-id').val(processGroup.id); + + // populate the group details + $('#connection-source-group-id').val(processGroup.id); + $('#connection-source-group-name').text(processGroupName); + + deferred.resolve(); + } else { + var message = '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any output ports.'; + if (nfCommon.isEmpty(processGroupContents.outputPorts) === false) { + message = 'Not authorized for any output ports in \'' + nfCommon.escapeHtml(processGroupName) + '\'.'; + } + + // there are no output ports for this process group + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: message + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source when the source is a remote process group. + * + * @argument {selection} source The source + */ + var initializeSourceRemoteProcessGroup = function (source) { + return $.Deferred(function (deferred) { + // get the remote process group data + var remoteProcessGroupData = source.datum(); + + $.ajax({ + type: 'GET', + url: remoteProcessGroupData.uri, + dataType: 'json' + }).done(function (response) { + var remoteProcessGroup = response.component; + var remoteProcessGroupContents = remoteProcessGroup.contents; + + // only proceed if there are output ports + if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) { + $('#output-port-source').show(); + + // show the output port options + var options = []; + $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) { + options.push({ + text: outputPort.name, + value: outputPort.id, + disabled: outputPort.exists === false, + description: nfCommon.escapeHtml(outputPort.comments) + }); + }); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#output-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-source-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-source-component-id').val(remoteProcessGroup.id); + + // populate the group details + $('#connection-source-group-id').val(remoteProcessGroup.id); + $('#connection-source-group-name').text(remoteProcessGroup.name); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any output ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + var initializeDestinationNewConnectionDialog = function (destination) { + if (nfCanvasUtils.isOutputPort(destination)) { + return initializeDestinationOutputPort(destination); + } else if (nfCanvasUtils.isProcessor(destination)) { + return $.Deferred(function (deferred) { + initializeDestinationProcessor(destination).done(function (processor) { + // Need to add the destination relationships because we need to + // provide this to wire up the publishers and subscribers correctly + // for a given connection since processors can have multiple + // relationships + $.each(processor.relationships, function (i, relationship) { + createRelationshipOption(relationship.name); + }); + + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) { + return initializeDestinationRemoteProcessGroup(destination); + } else if (nfCanvasUtils.isFunnel(destination)) { + return initializeDestinationFunnel(destination); + } else { + return initializeDestinationProcessGroup(destination); + } + }; + + var initializeDestinationOutputPort = function (destination) { + return $.Deferred(function (deferred) { + var outputPortData = destination.datum(); + var outputPortName = outputPortData.permissions.canRead ? outputPortData.component.name : outputPortData.id; + + $('#output-port-destination').show(); + $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName); + + // populate the connection destination details + $('#connection-destination-id').val(outputPortData.id); + $('#connection-destination-component-id').val(outputPortData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(); + }).promise(); + }; + + var initializeDestinationFunnel = function (destination) { + return $.Deferred(function (deferred) { + var funnelData = destination.datum(); + + $('#funnel-destination').show(); + + // populate the connection destination details + $('#connection-destination-id').val(funnelData.id); + $('#connection-destination-component-id').val(funnelData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(); + }).promise(); + }; + + var initializeDestinationProcessor = function (destination) { + return $.Deferred(function (deferred) { + var processorData = destination.datum(); + var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id; + var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor'; + + $('#processor-destination').show(); + $('#processor-destination-name').text(processorName).attr('title', processorName); + $('#processor-destination-type').text(processorType).attr('title', processorType); + + // populate the connection destination details + $('#connection-destination-id').val(processorData.id); + $('#connection-destination-component-id').val(processorData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(processorData.component); + }).promise(); + }; + + /** + * Initializes the destination when the destination is a process group. + * + * @argument {selection} destination The destination + */ + var initializeDestinationProcessGroup = function (destination) { + return $.Deferred(function (deferred) { + var processGroupData = destination.datum(); + + $.ajax({ + type: 'GET', + url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id), + dataType: 'json' + }).done(function (response) { + var processGroup = response.processGroupFlow; + var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id; + var processGroupContents = processGroup.flow; + + // show the input port options + var options = []; + $.each(processGroupContents.inputPorts, function (i, inputPort) { + options.push({ + text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id, + value: inputPort.id, + description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null + }); + }); + + // only proceed if there are output ports + if (!nfCommon.isEmpty(options)) { + $('#input-port-destination').show(); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#input-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-destination-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-destination-component-id').val(processGroup.id); + + // populate the group details + $('#connection-destination-group-id').val(processGroup.id); + $('#connection-destination-group-name').text(processGroupName); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any input ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source when the source is a remote process group. + * + * @argument {selection} destination The destination + * @argument {object} connectionDestination The connection destination object + */ + var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) { + return $.Deferred(function (deferred) { + var remoteProcessGroupData = destination.datum(); + + $.ajax({ + type: 'GET', + url: remoteProcessGroupData.uri, + dataType: 'json' + }).done(function (response) { + var remoteProcessGroup = response.component; + var remoteProcessGroupContents = remoteProcessGroup.contents; + + // only proceed if there are output ports + if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) { + $('#input-port-destination').show(); + + // show the input port options + var options = []; + $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) { + options.push({ + text: inputPort.name, + value: inputPort.id, + disabled: inputPort.exists === false, + description: nfCommon.escapeHtml(inputPort.comments) + }); + }); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#input-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-destination-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-destination-component-id').val(remoteProcessGroup.id); + + // populate the group details + $('#connection-destination-group-id').val(remoteProcessGroup.id); + $('#connection-destination-group-name').text(remoteProcessGroup.name); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any input ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source panel for groups. + * + * @argument {selection} source The source of the connection + */ + var initializeSourceReadOnlyGroup = function (source) { + return $.Deferred(function (deferred) { + var sourceData = source.datum(); + var sourceName = sourceData.permissions.canRead ? sourceData.component.name : sourceData.id; + + // populate the port information + $('#read-only-output-port-source').show(); + + // populate the component information + $('#connection-source-component-id').val(sourceData.id); + + // populate the group details + $('#connection-source-group-id').val(sourceData.id); + $('#connection-source-group-name').text(sourceName); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source in the existing connection dialog. + * + * @argument {selection} source The source + */ + var initializeSourceEditConnectionDialog = function (source) { + if (nfCanvasUtils.isProcessor(source)) { + return initializeSourceProcessor(source); + } else if (nfCanvasUtils.isInputPort(source)) { + return initializeSourceInputPort(source); + } else if (nfCanvasUtils.isFunnel(source)) { + return initializeSourceFunnel(source); + } else { + return initializeSourceReadOnlyGroup(source); + } + }; + + /** + * Initializes the destination in the existing connection dialog. + * + * @argument {selection} destination The destination + * @argument {object} connectionDestination The connection destination object + */ + var initializeDestinationEditConnectionDialog = function (destination, connectionDestination) { + if (nfCanvasUtils.isProcessor(destination)) { + return initializeDestinationProcessor(destination); + } else if (nfCanvasUtils.isOutputPort(destination)) { + return initializeDestinationOutputPort(destination); + } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) { + return initializeDestinationRemoteProcessGroup(destination, connectionDestination); + } else if (nfCanvasUtils.isFunnel(destination)) { + return initializeDestinationFunnel(destination); + } else { + return initializeDestinationProcessGroup(destination); + } + }; + + /** + * Creates an option for the specified relationship name. + * + * @argument {string} name The relationship name + */ + var createRelationshipOption = function (name) { + var nameSplit = name.split(":"); + var nameLabel = name; + + if (nameSplit.length > 1) { + // Example: publishes:data_transformation_format:1.0.0:message_router:stream_publish_url + var pubSub = nameSplit[0]; + pubSub = pubSub.charAt(0).toUpperCase() + pubSub.slice(1); + nameLabel = pubSub + " " + nameSplit[1] + "/" + nameSplit[2] + " on " + nameSplit[4]; + } + + var relationshipLabel = $('<div class="relationship-name nf-checkbox-label ellipsis"></div>').text(nameLabel); + var relationshipValue = $('<span class="relationship-name-value hidden"></span>').text(name); + return $('<div class="available-relationship-container"><div class="available-relationship nf-checkbox checkbox-unchecked"></div>' + + '</div>').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names'); + }; + + /** + * Adds a new connection. + * + * @argument {array} selectedRelationships The selected relationships + */ + var addConnection = function (selectedRelationships) { + // get the connection details + var sourceId = $('#connection-source-id').val(); + var destinationId = $('#connection-destination-id').val(); + + // get the selection components + var sourceComponentId = $('#connection-source-component-id').val(); + var source = d3.select('#id-' + sourceComponentId); + var destinationComponentId = $('#connection-destination-component-id').val(); + var destination = d3.select('#id-' + destinationComponentId); + + // get the source/destination data + var sourceData = source.datum(); + var destinationData = destination.datum(); + + // add bend points if we're dealing with a self loop + var bends = []; + if (sourceComponentId === destinationComponentId) { + var rightCenter = { + x: sourceData.position.x + (sourceData.dimensions.width), + y: sourceData.position.y + (sourceData.dimensions.height / 2) + }; + + var xOffset = nfConnection.config.selfLoopXOffset; + var yOffset = nfConnection.config.selfLoopYOffset; + bends.push({ + 'x': (rightCenter.x + xOffset), + 'y': (rightCenter.y - yOffset) + }); + bends.push({ + 'x': (rightCenter.x + xOffset), + 'y': (rightCenter.y + yOffset) + }); + } else { + var existingConnections = []; + + // get all connections for the source component + var connectionsForSourceComponent = nfConnection.getComponentConnections(sourceComponentId); + $.each(connectionsForSourceComponent, function (_, connectionForSourceComponent) { + // get the id for the source/destination component + var connectionSourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionForSourceComponent); + var connectionDestinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent); + + // if the connection is between these same components, consider it for collisions + if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) || + (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) { + + // record all connections between these two components in question + existingConnections.push(connectionForSourceComponent); + } + }); + + // if there are existing connections between these components, ensure the new connection won't collide + if (existingConnections.length > 0) { + var avoidCollision = false; + $.each(existingConnections, function (_, existingConnection) { + // only consider multiple connections with no bend points a collision, the existance of + // bend points suggests that the user has placed the connection into a desired location + if (nfCommon.isEmpty(existingConnection.bends)) { + avoidCollision = true; + return false; + } + }); + + // if we need to avoid a collision + if (avoidCollision === true) { + // determine the middle of the source/destination components + var sourceMiddle = [sourceData.position.x + (sourceData.dimensions.width / 2), sourceData.position.y + (sourceData.dimensions.height / 2)]; + var destinationMiddle = [destinationData.position.x + (destinationData.dimensions.width / 2), destinationData.position.y + (destinationData.dimensions.height / 2)]; + + // detect if the line is more horizontal or vertical + var slope = ((sourceMiddle[1] - destinationMiddle[1]) / (sourceMiddle[0] - destinationMiddle[0])); + var isMoreHorizontal = slope <= 1 && slope >= -1; + + // determines if the specified coordinate collides with another connection + var collides = function (x, y) { + var collides = false; + $.each(existingConnections, function (_, existingConnection) { + if (!nfCommon.isEmpty(existingConnection.bends)) { + if (isMoreHorizontal) { + // horizontal lines are adjusted in the y space + if (existingConnection.bends[0].y === y) { + collides = true; + return false; + } + } else { + // vertical lines are adjusted in the x space + if (existingConnection.bends[0].x === x) { + collides = true; + return false; + } + } + } + }); + return collides; + }; + + // find the mid point on the connection + var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2; + var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2; + + // attempt to position this connection so it doesn't collide + var xStep = isMoreHorizontal ? 0 : CONNECTION_OFFSET_X_INCREMENT; + var yStep = isMoreHorizontal ? CONNECTION_OFFSET_Y_INCREMENT : 0; + var positioned = false; + while (positioned === false) { + // consider above and below, then increment and try again (if necessary) + if (collides(xCandidate - xStep, yCandidate - yStep) === false) { + bends.push({ + 'x': (xCandidate - xStep), + 'y': (yCandidate - yStep) + }); + positioned = true; + } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) { + bends.push({ + 'x': (xCandidate + xStep), + 'y': (yCandidate + yStep) + }); + positioned = true; + } + + if (isMoreHorizontal) { + yStep += CONNECTION_OFFSET_Y_INCREMENT; + } else { + xStep += CONNECTION_OFFSET_X_INCREMENT; + } + } + } + } + } + + // determine the source group id + var sourceGroupId = $('#connection-source-group-id').val(); + var destinationGroupId = $('#connection-destination-group-id').val(); + + // determine the source and destination types + var sourceType = nfCanvasUtils.getConnectableTypeForSource(source); + var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination); + + // get the settings + var connectionName = $('#connection-name').val(); + var flowFileExpiration = $('#flow-file-expiration').val(); + var backPressureObjectThreshold = $('#back-pressure-object-threshold').val(); + var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val(); + var prioritizers = $('#prioritizer-selected').sortable('toArray'); + var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value; + var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy; + var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : ''; + var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS'; + + if (validateSettings()) { + var connectionEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'name': connectionName, + 'source': { + 'id': sourceId, + 'groupId': sourceGroupId, + 'type': sourceType + }, + 'destination': { + 'id': destinationId, + 'groupId': destinationGroupId, + 'type': destinationType + }, + 'selectedRelationships': selectedRelationships, + 'flowFileExpiration': flowFileExpiration, + 'backPressureDataSizeThreshold': backPressureDataSizeThreshold, + 'backPressureObjectThreshold': backPressureObjectThreshold, + 'bends': bends, + 'prioritizers': prioritizers, + 'loadBalanceStrategy': loadBalanceStrategy, + 'loadBalancePartitionAttribute': loadBalancePartitionAttribute, + 'loadBalanceCompression': loadBalanceCompression + } + }; + + // create the new connection + $.ajax({ + type: 'POST', + url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections', + data: JSON.stringify(connectionEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // add the connection + nfGraph.add({ + 'connections': [response] + }, { + 'selectAll': true + }); + + // reload the connections source/destination components + nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId); + + // update component visibility + nfGraph.updateVisibility(); + + // update the birdseye + nfBirdseye.refresh(); + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + }); + } + }; + + /** + * Updates an existing connection. + * + * @argument {array} selectedRelationships The selected relationships + */ + var updateConnection = function (selectedRelationships) { + // get the connection details + var connectionId = $('#connection-id').text(); + var connectionUri = $('#connection-uri').val(); + + // get the source details + var sourceComponentId = $('#connection-source-component-id').val(); + + // get the destination details + var destinationComponentId = $('#connection-destination-component-id').val(); + var destination = d3.select('#id-' + destinationComponentId); + var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination); + + // get the destination details + var destinationId = $('#connection-destination-id').val(); + var destinationGroupId = $('#connection-destination-group-id').val(); + + // get the settings + var connectionName = $('#connection-name').val(); + var flowFileExpiration = $('#flow-file-expiration').val(); + var backPressureObjectThreshold = $('#back-pressure-object-threshold').val(); + var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val(); + var prioritizers = $('#prioritizer-selected').sortable('toArray'); + var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value; + var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy; + var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : ''; + var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS'; + + if (validateSettings()) { + var d = nfConnection.get(connectionId); + var connectionEntity = { + 'revision': nfClient.getRevision(d), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'id': connectionId, + 'name': connectionName, + 'destination': { + 'id': destinationId, + 'groupId': destinationGroupId, + 'type': destinationType + }, + 'selectedRelationships': selectedRelationships, + 'flowFileExpiration': flowFileExpiration, + 'backPressureDataSizeThreshold': backPressureDataSizeThreshold, + 'backPressureObjectThreshold': backPressureObjectThreshold, + 'prioritizers': prioritizers, + 'loadBalanceStrategy': loadBalanceStrategy, + 'loadBalancePartitionAttribute': loadBalancePartitionAttribute, + 'loadBalanceCompression': loadBalanceCompression + } + }; + + // update the connection + return $.ajax({ + type: 'PUT', + url: connectionUri, + data: JSON.stringify(connectionEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // update this connection + nfConnection.set(response); + + // reload the connections source/destination components + nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId); + }).fail(function (xhr, status, error) { + if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: nfCommon.escapeHtml(xhr.responseText), + }); + } else { + nfErrorHandler.handleAjaxError(xhr, status, error); + } + }); + } else { + return $.Deferred(function (deferred) { + deferred.reject(); + }).promise(); + } + }; + + /** + * Returns an array of selected relationship names. + */ + var getSelectedRelationships = function () { + // get all available relationships + var availableRelationships = $('#relationship-names'); + var selectedRelationships = []; + + // go through each relationship to determine which are selected + $.each(availableRelationships.children(), function (i, relationshipElement) { + var relationship = $(relationshipElement); + + // get each relationship and its corresponding checkbox + var relationshipCheck = relationship.children('div.available-relationship'); + + // see if this relationship has been selected + if (relationshipCheck.hasClass('checkbox-checked')) { + selectedRelationships.push(relationship.children('span.relationship-name-value').text()); + } + }); + + return selectedRelationships; + }; + + /** + * Validates the specified settings. + */ + var validateSettings = function () { + var errors = []; + + // validate the settings + if (nfCommon.isBlank($('#flow-file-expiration').val())) { + errors.push('File expiration must be specified'); + } + if (!$.isNumeric($('#back-pressure-object-threshold').val())) { + errors.push('Back pressure object threshold must be an integer value'); + } + if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) { + errors.push('Back pressure data size threshold must be specified'); + } + if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE' + && nfCommon.isBlank($('#load-balance-partition-attribute').val())) { + errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"'); + } + + if (errors.length > 0) { + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: nfCommon.formatUnorderedList(errors) + }); + return false; + } else { + return true; + } + }; + + /** + * Resets the dialog. + */ + var resetDialog = function () { + // reset the prioritizers + var selectedList = $('#prioritizer-selected'); + var availableList = $('#prioritizer-available'); + selectedList.children().detach().appendTo(availableList); + + // sort the available list + var listItems = availableList.children('li').get(); + listItems.sort(function (a, b) { + var compA = $(a).text().toUpperCase(); + var compB = $(b).text().toUpperCase(); + return (compA < compB) ? -1 : (compA > compB) ? 1 : 0; + }); + + // clear the available list and re-insert each list item + $.each(listItems, function () { + $(this).detach(); + }); + $.each(listItems, function () { + $(this).appendTo(availableList); + }); + + // reset the fields + $('#connection-name').val(''); + $('#relationship-names').css('border-width', '0').empty(); + $('#relationship-names-container').show(); + + // clear the id field + nfCommon.clearField('connection-id'); + + // hide all the connection source panels + $('#processor-source').hide(); + $('#input-port-source').hide(); + $('#output-port-source').hide(); + $('#read-only-output-port-source').hide(); + $('#funnel-source').hide(); + + // hide all the connection destination panels + $('#processor-destination').hide(); + $('#input-port-destination').hide(); + $('#output-port-destination').hide(); + $('#funnel-destination').hide(); + + // clear and destination details + $('#connection-source-id').val(''); + $('#connection-source-component-id').val(''); + $('#connection-source-group-id').val(''); + + // clear any destination details + $('#connection-destination-id').val(''); + $('#connection-destination-component-id').val(''); + $('#connection-destination-group-id').val(''); + + // clear any ports + $('#output-port-options').empty(); + $('#input-port-options').empty(); + + // clear load balance settings + $('#load-balance-strategy-combo').combo('setSelectedOption', nfCommon.loadBalanceStrategyOptions[0]); + $('#load-balance-partition-attribute').val(''); + $('#load-balance-compression-combo').combo('setSelectedOption', nfCommon.loadBalanceCompressionOptions[0]); + + // see if the temp edge needs to be removed + removeTempEdge(); + }; + + var nfConnectionConfiguration = { + + /** + * Initialize the connection configuration. + * + * @param nfBirdseyeRef The nfBirdseye module. + * @param nfGraphRef The nfGraph module. + */ + init: function (nfBirdseyeRef, nfGraphRef, defaultBackPressureObjectThresholdRef, defaultBackPressureDataSizeThresholdRef) { + nfBirdseye = nfBirdseyeRef; + nfGraph = nfGraphRef; + + defaultBackPressureObjectThreshold = defaultBackPressureObjectThresholdRef; + defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThresholdRef; + + // initially hide the relationship names container + $('#relationship-names-container').show(); + + // initialize the configure connection dialog + $('#connection-configuration').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Configure Connection', + handler: { + close: function () { + // reset the dialog on close + resetDialog(); + }, + open: function () { + nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0)); + } + } + }); + + // initialize the properties tabs + $('#connection-configuration-tabs').tabbs({ + tabStyle: 'tab', + selectedTabStyle: 'selected-tab', + scrollableTabContentStyle: 'scrollable', + tabs: [{ + name: 'Details', + tabContentId: 'connection-details-tab-content' + }, { + name: 'Settings', + tabContentId: 'connection-settings-tab-content' + }] + }); + + // initialize the load balance strategy combo + $('#load-balance-strategy-combo').combo({ + options: nfCommon.loadBalanceStrategyOptions, + select: function (selectedOption) { + // Show the appropriate configurations + if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') { + $('#load-balance-partition-attribute-setting-separator').show(); + $('#load-balance-partition-attribute-setting').show(); + } else { + $('#load-balance-partition-attribute-setting-separator').hide(); + $('#load-balance-partition-attribute-setting').hide(); + } + if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') { + $('#load-balance-compression-setting').hide(); + } else { + $('#load-balance-compression-setting').show(); + } + } + }); + + + // initialize the load balance compression combo + $('#load-balance-compression-combo').combo({ + options: nfCommon.loadBalanceCompressionOptions + }); + + // load the processor prioritizers + $.ajax({ + type: 'GET', + url: config.urls.prioritizers, + dataType: 'json' + }).done(function (response) { + // create an element for each available prioritizer + $.each(response.prioritizerTypes, function (i, documentedType) { + nfConnectionConfiguration.addAvailablePrioritizer('#prioritizer-available', documentedType); + }); + + // make the prioritizer containers sortable + $('#prioritizer-available, #prioritizer-selected').sortable({ + containment: $('#connection-settings-tab-content').find('.settings-right'), + connectWith: 'ul', + placeholder: 'ui-state-highlight', + scroll: true, + opacity: 0.6 + }); + $('#prioritizer-available, #prioritizer-selected').disableSelection(); + }).fail(nfErrorHandler.handleAjaxError); + }, + + /** + * Adds the specified prioritizer to the specified container. + * + * @argument {string} prioritizerContainer The dom Id of the prioritizer container + * @argument {object} prioritizerType The type of prioritizer + */ + addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) { + var type = prioritizerType.type; + var name = nfCommon.substringAfterLast(type, '.'); + + // add the prioritizers to the available list + var prioritizerList = $(prioritizerContainer); + var prioritizer = $('<li></li>').append($('<span style="float: left;"></span>').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList); + + // add the description if applicable + if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) { + $('<div class="fa fa-question-circle"></div>').appendTo(prioritizer).qtip($.extend({ + content: nfCommon.escapeHtml(prioritizerType.description) + }, nfCommon.config.tooltipConfig)); + } + }, + + /** + * Shows the dialog for creating a new connection. + * + * @argument {string} sourceId The source id + * @argument {string} destinationId The destination id + */ + createConnection: function (sourceId, destinationId) { + // select the source and destination + var source = d3.select('#id-' + sourceId); + var destination = d3.select('#id-' + destinationId); + + if (source.empty() || destination.empty()) { + return; + } + + // initialize the connection dialog + $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () { + + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + addDialogRelationshipsChangeListener(); + + // if there is a single relationship auto select + var relationships = $('#relationship-names').children('div'); + if (relationships.length === 1) { + relationships.children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked'); + } + + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + // ensure some relationships were selected + return getSelectedRelationships().length === 0; + }, + handler: { + click: function () { + addConnection(getSelectedRelationships()); + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $('#connection-configuration').modal('hide'); + } + } + }]); + } else { + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + // add the connection + addConnection(); + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $('#connection-configuration').modal('hide'); + } + } + }]); + } + + // set the default values + $('#flow-file-expiration').val('0 sec'); + $('#back-pressure-object-threshold').val(defaultBackPressureObjectThreshold); + $('#back-pressure-data-size-threshold').val(defaultBackPressureDataSizeThreshold); + + // select the first tab + $('#connection-configuration-tabs').find('li:first').click(); + + // configure the header and show the dialog + $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show'); + + // add the ellipsis if necessary + $('#connection-configuration div.relationship-name').ellipsis(); + + // fill in the connection id + nfCommon.populateField('connection-id', null); + + // show the border if necessary + var relationshipNames = $('#relationship-names'); + if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) { + relationshipNames.css('border-width', '1px'); + } + }).fail(function () { + // see if the temp edge needs to be removed + removeTempEdge(); + }); + }, + + /** + * Shows the configuration for the specified connection. If a destination is + * specified it will be considered a new destination. + * + * @argument {selection} selection The connection entry + * @argument {selection} destination Optional new destination + */ + showConfiguration: function (selection, destination) { + return $.Deferred(function (deferred) { + var connectionEntry = selection.datum(); + var connection = connectionEntry.component; + + // identify the source component + var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry); + var source = d3.select('#id-' + sourceComponentId); + + // identify the destination component + if (nfCommon.isUndefinedOrNull(destination)) { + var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry); + destination = d3.select('#id-' + destinationComponentId); + } + + // initialize the connection dialog + $.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () { + var availableRelationships = connection.availableRelationships; + var selectedRelationships = connection.selectedRelationships; + + // Added this block to force add destination relationships to + // get blueprint generation working + if (nfCanvasUtils.isProcessor(destination)) { + if (availableRelationships == undefined) { + // When the source is a port, this could be null or + // undefined since the backend the attribute doesn't + // exist + availableRelationships = []; + } + + var processorData = destination.datum(); + $.each(processorData.component.relationships, function (i, relationship) { + availableRelationships.push(relationship.name); + }); + } + + // show the available relationship if applicable + if (nfCommon.isDefinedAndNotNull(availableRelationships) || nfCommon.isDefinedAndNotNull(selectedRelationships)) { + // populate the available connections + $.each(availableRelationships, function (i, name) { + createRelationshipOption(name); + }); + + addDialogRelationshipsChangeListener(); + + // ensure all selected relationships are present + // (may be undefined) and selected + $.each(selectedRelationships, function (i, name) { + // mark undefined relationships accordingly + if ($.inArray(name, availableRelationships) === -1) { + var option = createRelationshipOption(name); + $(option).children('div.relationship-name').addClass('undefined'); + } + + // ensure all selected relationships are checked + var relationships = $('#relationship-names').children('div'); + $.each(relationships, function (i, relationship) { + var relationshipName = $(relationship).children('span.relationship-name-value'); + if (relationshipName.text() === name) { + $(relationship).children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked'); + } + }); + }); + } + + // if the source is a process group or remote process group, select the appropriate port if applicable + if (nfCanvasUtils.isProcessGroup(source) || nfCanvasUtils.isRemoteProcessGroup(source)) { + // populate the connection source details + $('#connection-source-id').val(connection.source.id); + $('#read-only-output-port-name').text(connection.source.name).attr('title', connection.source.name); + } + + // if the destination is a process gorup or remote process group, select the appropriate port if applicable + if (nfCanvasUtils.isProcessGroup(destination) || nfCanvasUtils.isRemoteProcessGroup(destination)) { + var destinationData = destination.datum(); + + // when the group ids differ, its a new destination component so we don't want to preselect any port + if (connection.destination.groupId === destinationData.id) { + $('#input-port-options').combo('setSelectedOption', { + value: connection.destination.id + }); + } + } + + // set the connection settings + $('#connection-name').val(connection.name); + $('#flow-file-expiration').val(connection.flowFileExpiration); + $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold); + $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold); + + // select the load balance combos + $('#load-balance-strategy-combo').combo('setSelectedOption', { + value: connection.loadBalanceStrategy + }); + $('#load-balance-compression-combo').combo('setSelectedOption', { + value: connection.loadBalanceCompression + }); + $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute); + + // format the connection id + nfCommon.populateField('connection-id', connection.id); + + // handle each prioritizer + $.each(connection.prioritizers, function (i, type) { + $('#prioritizer-available').children('li[id="' + type + '"]').detach().appendTo('#prioritizer-selected'); + }); + + // store the connection details + $('#connection-uri').val(connectionEntry.uri); + + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Apply', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + // ensure some relationships were selected with a processor as the source + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + return getSelectedRelationships().length === 0; + } + return false; + }, + handler: { + click: function () { + // see if we're working with a processor as the source + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + // update the selected relationships + updateConnection(getSelectedRelationships()).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + } else { + // there are no relationships, but the source wasn't a processor, so update anyway + updateConnection(undefined).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + } + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + // hide the dialog + $('#connection-configuration').modal('hide'); + + // reject the deferred + deferred.reject(); + } + } + }]); + + // show the details dialog + $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show'); + + // add the ellipsis if necessary + $('#connection-configuration div.relationship-name').ellipsis(); + + // show the border if necessary + var relationshipNames = $('#relationship-names'); + if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) { + relationshipNames.css('border-width', '1px'); + } + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } + }; + + return nfConnectionConfiguration; +})); diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js new file mode 100644 index 0000000..3c595ca --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-flow-version.js @@ -0,0 +1,1990 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +/** + * Handles versioning. + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'nf.ng.Bridge', + 'nf.ErrorHandler', + 'nf.Dialog', + 'nf.Storage', + 'nf.Common', + 'nf.Client', + 'nf.CanvasUtils', + 'nf.ProcessGroup', + 'nf.ProcessGroupConfiguration', + 'nf.Graph', + 'nf.Birdseye'], + function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) { + return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.FlowVerison = + factory(require('jquery'), + require('nf.ng.Bridge'), + require('nf.ErrorHandler'), + require('nf.Dialog'), + require('nf.Storage'), + require('nf.Common'), + require('nf.Client'), + require('nf.CanvasUtils'), + require('nf.ProcessGroup'), + require('nf.ProcessGroupConfiguration'), + require('nf.Graph'), + require('nf.Birdseye'))); + } else { + nf.FlowVersion = factory(root.$, + root.nf.ng.Bridge, + root.nf.ErrorHandler, + root.nf.Dialog, + root.nf.Storage, + root.nf.Common, + root.nf.Client, + root.nf.CanvasUtils, + root.nf.ProcessGroup, + root.nf.ProcessGroupConfiguration, + root.nf.Graph, + root.nf.Birdseye); + } +}(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfStorage, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) { + 'use strict'; + + var serverTimeOffset = null; + + var gridOptions = { + forceFitColumns: true, + enableTextSelectionOnCells: true, + enableCellNavigation: true, + enableColumnReorder: false, + autoEdit: false, + multiSelect: false, + rowHeight: 24 + }; + + /** + * Reset the save flow version dialog. + */ + var resetSaveFlowVersionDialog = function () { + $('#save-flow-version-registry-combo').combo('destroy').hide(); + $('#save-flow-version-bucket-combo').combo('destroy').hide(); + + $('#save-flow-version-label').text(''); + + $('#save-flow-version-registry').text('').hide(); + $('#save-flow-version-bucket').text('').hide(); + + $('#save-flow-version-name').text('').hide(); + $('#save-flow-version-description').removeClass('unset blank').text('').hide(); + + $('#save-flow-version-name-field').val('').hide(); + $('#save-flow-version-description-field').val('').hide(); + $('#save-flow-version-change-comments').val(''); + + $('#save-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text(''); + }; + + /** + * Reset the revert local changes dialog. + */ + var resetRevertLocalChangesDialog = function () { + $('#revert-local-changes-process-group-id').text(''); + + clearLocalChangesGrid($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries')); + }; + + /** + * Reset the show local changes dialog. + */ + var resetShowLocalChangesDialog = function () { + clearLocalChangesGrid($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries')); + }; + + /** + * Clears the local changes grid. + */ + var clearLocalChangesGrid = function (localChangesTable, filterInput, displayedLabel, totalLabel) { + var localChangesGrid = localChangesTable.data('gridInstance'); + if (nfCommon.isDefinedAndNotNull(localChangesGrid)) { + localChangesGrid.setSelectedRows([]); + localChangesGrid.resetActiveCell(); + + var localChangesData = localChangesGrid.getData(); + localChangesData.setItems([]); + localChangesData.setFilterArgs({ + searchString: '' + }); + } + + filterInput.val(''); + + displayedLabel.text('0'); + totalLabel.text('0'); + }; + + /** + * Clears the version grid + */ + var clearFlowVersionsGrid = function () { + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) { + importFlowVersionGrid.setSelectedRows([]); + importFlowVersionGrid.resetActiveCell(); + + var importFlowVersionData = importFlowVersionGrid.getData(); + importFlowVersionData.setItems([]); + } + }; + + /** + * Reset the import flow version dialog. + */ + var resetImportFlowVersionDialog = function () { + $('#import-flow-version-dialog').removeData('pt'); + + $('#import-flow-version-registry-combo').combo('destroy').hide(); + $('#import-flow-version-bucket-combo').combo('destroy').hide(); + $('#import-flow-version-name-combo').combo('destroy').hide(); + + $('#import-flow-version-registry').text('').hide(); + $('#import-flow-version-bucket').text('').hide(); + $('#import-flow-version-name').text('').hide(); + + clearFlowVersionsGrid(); + + $('#import-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text(''); + + $('#import-flow-version-container').hide(); + $('#import-flow-version-label').text(''); + }; + + /** + * Loads the registries into the specified registry combo. + * + * @param dialog + * @param registryCombo + * @param bucketCombo + * @param flowCombo + * @param selectBucket + * @param bucketCheck + * @returns {deferred} + */ + var loadRegistries = function (dialog, registryCombo, bucketCombo, flowCombo, selectBucket, bucketCheck) { + return $.ajax({ + type: 'GET', + url: '../nifi-api/flow/registries', + dataType: 'json' + }).done(function (registriesResponse) { + var registries = []; + + if (nfCommon.isDefinedAndNotNull(registriesResponse.registries) && registriesResponse.registries.length > 0) { + registriesResponse.registries.sort(function (a, b) { + return a.registry.name > b.registry.name; + }); + + $.each(registriesResponse.registries, function (_, registryEntity) { + var registry = registryEntity.registry; + registries.push({ + text: registry.name, + value: registry.id, + description: nfCommon.escapeHtml(registry.description) + }); + }); + } else { + registries.push({ + text: 'No available registries', + value: null, + optionClass: 'unset', + disabled: true + }); + } + + // load the registries + registryCombo.combo({ + options: registries, + select: function (selectedOption) { + selectRegistry(dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck) + } + }); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Loads the buckets for the specified registryIdentifier for the current user. + * + * @param registryIdentifier + * @param bucketCombo + * @param flowCombo + * @param selectBucket + * @param bucketCheck + * @returns {*} + */ + var loadBuckets = function (registryIdentifier, bucketCombo, flowCombo, selectBucket, bucketCheck) { + return $.ajax({ + type: 'GET', + url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets', + dataType: 'json' + }).done(function (response) { + var buckets = []; + + if (nfCommon.isDefinedAndNotNull(response.buckets) && response.buckets.length > 0) { + response.buckets.sort(function (a, b) { + if (a.permissions.canRead === false && b.permissions.canRead === false) { + return 0; + } else if (a.permissions.canRead === false) { + return -1; + } else if (b.permissions.canRead === false) { + return 1; + } + + return a.bucket.name > b.bucket.name; + }); + + $.each(response.buckets, function (_, bucketEntity) { + if (bucketEntity.permissions.canRead === true) { + var bucket = bucketEntity.bucket; + + if (bucketCheck(bucketEntity)) { + buckets.push({ + text: bucket.name, + value: bucket.id, + description: nfCommon.escapeHtml(bucket.description) + }); + } + } + }); + } + + if (buckets.length === 0) { + buckets.push({ + text: 'No available buckets', + value: null, + optionClass: 'unset', + disabled: true + }); + + if (nfCommon.isDefinedAndNotNull(flowCombo)) { + flowCombo.combo('destroy').combo({ + options: [{ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + } + } + + // load the buckets + bucketCombo.combo('destroy').combo({ + options: buckets, + select: selectBucket + }); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Select handler for the registries combo. + * + * @param dialog + * @param selectedOption + * @param bucketCombo + * @param flowCombo + * @param selectBucket + * @param bucketCheck + */ + var selectRegistry = function (dialog, selectedOption, bucketCombo, flowCombo, selectBucket, bucketCheck) { + var showNoBucketsAvailable = function () { + bucketCombo.combo('destroy').combo({ + options: [{ + text: 'No available buckets', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + + if (nfCommon.isDefinedAndNotNull(flowCombo)) { + flowCombo.combo('destroy').combo({ + options: [{ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + } + + dialog.modal('refreshButtons'); + }; + + if (selectedOption.disabled === true) { + showNoBucketsAvailable(); + } else { + bucketCombo.combo('destroy').combo({ + options: [{ + text: 'Loading buckets...', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + + if (nfCommon.isDefinedAndNotNull(flowCombo)) { + flowCombo.combo('destroy').combo({ + options: [{ + text: 'Loading flows...', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + + clearFlowVersionsGrid(); + } + + loadBuckets(selectedOption.value, bucketCombo, flowCombo, selectBucket, bucketCheck).fail(function () { + showNoBucketsAvailable(); + }); + } + }; + + /** + * Select handler for the buckets combo. + * + * @param selectedOption + */ + var selectBucketSaveFlowVersion = function (selectedOption) { + $('#save-flow-version-dialog').modal('refreshButtons'); + }; + + /** + * Saves a flow version. + * @author: Renu + * @desc for lines 390-396: when a dflow is committed, then environment dropdown selection is enabled. + * If Env is pre-selected and enabled =>submit button is enabled + * @returns {*} + */ + var saveFlowVersion = function () { + var processGroupId = $('#save-flow-version-process-group-id').text(); + var processGroupRevision = $('#save-flow-version-process-group-id').data('revision'); + + $('#environmentType').prop('disabled', false); + console.log("test submit btn..... "); + + if($('#environmentType').val() && !$('#environmentType').prop("disabled")){ + $('#operate-submit-btn').prop('disabled', false); + console.log("button is enabled bcz env is already selected and not disabled"); + } + + var saveFlowVersionRequest = { + processGroupRevision: nfClient.getRevision({ + revision: { + version: processGroupRevision.version + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged() + }; + + var versionControlInformation = $('#save-flow-version-process-group-id').data('versionControlInformation'); + if (nfCommon.isDefinedAndNotNull(versionControlInformation)) { + saveFlowVersionRequest['versionedFlow'] = { + registryId: versionControlInformation.registryId, + bucketId: versionControlInformation.bucketId, + flowId: versionControlInformation.flowId, + comments: $('#save-flow-version-change-comments').val() + } + } else { + var selectedRegistry = $('#save-flow-version-registry-combo').combo('getSelectedOption'); + var selectedBucket = $('#save-flow-version-bucket-combo').combo('getSelectedOption'); + + saveFlowVersionRequest['versionedFlow'] = { + registryId: selectedRegistry.value, + bucketId: selectedBucket.value, + flowName: $('#save-flow-version-name-field').val(), + description: $('#save-flow-version-description-field').val(), + comments: $('#save-flow-version-change-comments').val() + }; + } + + return $.ajax({ + type: 'POST', + data: JSON.stringify(saveFlowVersionRequest), + url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json', + contentType: 'application/json' + }).fail(nfErrorHandler.handleAjaxError) + + + }; + + /** + * Sorts the specified data using the specified sort details. + * + * @param {object} sortDetails + * @param {object} data + */ + var sort = function (sortDetails, data) { + // defines a function for sorting + var comparer = function (a, b) { + var aIsBlank = nfCommon.isBlank(a[sortDetails.columnId]); + var bIsBlank = nfCommon.isBlank(b[sortDetails.columnId]); + + if (aIsBlank && bIsBlank) { + return 0; + } else if (aIsBlank) { + return 1; + } else if (bIsBlank) { + return -1; + } + + return a[sortDetails.columnId] === b[sortDetails.columnId] ? 0 : a[sortDetails.columnId] > b[sortDetails.columnId] ? 1 : -1; + }; + + // perform the sort + data.sort(comparer, sortDetails.sortAsc); + }; + + var initImportFlowVersionTable = function () { + var importFlowVersionTable = $('#import-flow-version-table'); + + var valueFormatter = function (row, cell, value, columnDef, dataContext) { + return nfCommon.escapeHtml(value); + }; + + var timestampFormatter = function (row, cell, value, columnDef, dataContext) { + // get the current user time to properly convert the server time + var now = new Date(); + + // convert the user offset to millis + var userTimeOffset = now.getTimezoneOffset() * 60 * 1000; + + // create the proper date by adjusting by the offsets + var date = new Date(dataContext.timestamp + userTimeOffset + serverTimeOffset); + return nfCommon.formatDateTime(date); + }; + + // define the column model for flow versions + var importFlowVersionColumns = [ + { + id: 'version', + name: 'Version', + field: 'version', + formatter: valueFormatter, + sortable: true, + resizable: true, + width: 75, + maxWidth: 75 + }, + { + id: 'timestamp', + name: 'Created', + field: 'timestamp', + formatter: timestampFormatter, + sortable: true, + resizable: true, + width: 175, + maxWidth: 175 + }, + { + id: 'changeComments', + name: 'Comments', + field: 'comments', + sortable: true, + resizable: true, + formatter: valueFormatter + } + ]; + + // initialize the dataview + var importFlowVersionData = new Slick.Data.DataView({ + inlineFilters: false + }); + + // initialize the sort + sort({ + columnId: 'version', + sortAsc: false + }, importFlowVersionData); + + // initialize the grid + var importFlowVersionGrid = new Slick.Grid(importFlowVersionTable, importFlowVersionData, importFlowVersionColumns, gridOptions); + importFlowVersionGrid.setSelectionModel(new Slick.RowSelectionModel()); + importFlowVersionGrid.registerPlugin(new Slick.AutoTooltips()); + importFlowVersionGrid.setSortColumn('version', false); + importFlowVersionGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.id, + sortAsc: args.sortAsc + }, importFlowVersionData); + }); + importFlowVersionGrid.onSelectedRowsChanged.subscribe(function (e, args) { + $('#import-flow-version-dialog').modal('refreshButtons'); + }); + importFlowVersionGrid.onDblClick.subscribe(function (e, args) { + if ($('#import-flow-version-label').is(':visible')) { + changeFlowVersion(); + } else { + importFlowVersion().always(function () { + $('#import-flow-version-dialog').modal('hide'); + }); + } + }); + + // wire up the dataview to the grid + importFlowVersionData.onRowCountChanged.subscribe(function (e, args) { + importFlowVersionGrid.updateRowCount(); + importFlowVersionGrid.render(); + }); + importFlowVersionData.onRowsChanged.subscribe(function (e, args) { + importFlowVersionGrid.invalidateRows(args.rows); + importFlowVersionGrid.render(); + }); + importFlowVersionData.syncGridSelection(importFlowVersionGrid, true); + + // hold onto an instance of the grid + importFlowVersionTable.data('gridInstance', importFlowVersionGrid); + }; + + /** + * Initializes the specified local changes table. + * + * @param localChangesTable + * @param filterInput + * @param displayedLabel + * @param totalLabel + */ + var initLocalChangesTable = function (localChangesTable, filterInput, displayedLabel, totalLabel) { + + var getFilterText = function () { + return filterInput.val(); + }; + + var applyFilter = function () { + // get the dataview + var localChangesGrid = localChangesTable.data('gridInstance'); + + // ensure the grid has been initialized + if (nfCommon.isDefinedAndNotNull(localChangesGrid)) { + var localChangesData = localChangesGrid.getData(); + + // update the search criteria + localChangesData.setFilterArgs({ + searchString: getFilterText() + }); + localChangesData.refresh(); + } + }; + + var filter = function (item, args) { + if (args.searchString === '') { + return true; + } + + try { + // perform the row filtering + var filterExp = new RegExp(args.searchString, 'i'); + } catch (e) { + // invalid regex + return false; + } + + // determine if the item matches the filter + var matchesId = item['componentId'].search(filterExp) >= 0; + var matchesDifferenceType = item['differenceType'].search(filterExp) >= 0; + var matchesDifference = item['difference'].search(filterExp) >= 0; + + // conditionally consider the component name + var matchesComponentName = false; + if (nfCommon.isDefinedAndNotNull(item['componentName'])) { + matchesComponentName = item['componentName'].search(filterExp) >= 0; + } + + return matchesId || matchesComponentName || matchesDifferenceType || matchesDifference; + }; + + // initialize the component state filter + filterInput.on('keyup', function () { + applyFilter(); + }); + + var valueFormatter = function (row, cell, value, columnDef, dataContext) { + return nfCommon.escapeHtml(value); + }; + + var actionsFormatter = function (row, cell, value, columnDef, dataContext) { + var markup = ''; + + if (dataContext.differenceType !== 'Component Removed' && nfCommon.isDefinedAndNotNull(dataContext.processGroupId)) { + markup += '<div class="pointer go-to-component fa fa-long-arrow-right" title="Go To"></div>'; + } + + return markup; + }; + + // define the column model for local changes + var localChangesColumns = [ + { + id: 'componentName', + name: 'Component Name', + field: 'componentName', + formatter: valueFormatter, + sortable: true, + resizable: true + }, + { + id: 'differenceType', + name: 'Change Type', + field: 'differenceType', + formatter: valueFormatter, + sortable: true, + resizable: true + }, + { + id: 'difference', + name: 'Difference', + field: 'difference', + formatter: valueFormatter, + sortable: true, + resizable: true + }, + { + id: 'actions', + name: ' ', + formatter: actionsFormatter, + sortable: false, + resizable: false, + width: 25 + } + ]; + + // initialize the dataview + var localChangesData = new Slick.Data.DataView({ + inlineFilters: false + }); + localChangesData.setFilterArgs({ + searchString: getFilterText() + }); + localChangesData.setFilter(filter); + + // initialize the sort + sort({ + columnId: 'componentName', + sortAsc: true + }, localChangesData); + + // initialize the grid + var localChangesGrid = new Slick.Grid(localChangesTable, localChangesData, localChangesColumns, gridOptions); + localChangesGrid.setSelectionModel(new Slick.RowSelectionModel()); + localChangesGrid.registerPlugin(new Slick.AutoTooltips()); + localChangesGrid.setSortColumn('componentName', true); + localChangesGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.id, + sortAsc: args.sortAsc + }, localChangesData); + }); + + // configure a click listener + localChangesGrid.onClick.subscribe(function (e, args) { + var target = $(e.target); + + // get the node at this row + var componentDifference = localChangesData.getItem(args.row); + + // determine the desired action + if (localChangesGrid.getColumns()[args.cell].id === 'actions') { + if (target.hasClass('go-to-component')) { + if (componentDifference.componentType === 'Controller Service') { + nfProcessGroupConfiguration.showConfiguration(componentDifference.processGroupId).done(function () { + nfProcessGroupConfiguration.selectControllerService(componentDifference.componentId); + + localChangesTable.closest('.large-dialog').modal('hide'); + }); + } else { + nfCanvasUtils.showComponent(componentDifference.processGroupId, componentDifference.componentId).done(function () { + localChangesTable.closest('.large-dialog').modal('hide'); + }); + } + } + } + }); + + // wire up the dataview to the grid + localChangesData.onRowCountChanged.subscribe(function (e, args) { + localChangesGrid.updateRowCount(); + localChangesGrid.render(); + + // update the total number of displayed items + displayedLabel.text(nfCommon.formatInteger(args.current)); + }); + localChangesData.onRowsChanged.subscribe(function (e, args) { + localChangesGrid.invalidateRows(args.rows); + localChangesGrid.render(); + }); + localChangesData.syncGridSelection(localChangesGrid, true); + + // hold onto an instance of the grid + localChangesTable.data('gridInstance', localChangesGrid); + + // initialize the number of display items + displayedLabel.text('0'); + totalLabel.text('0'); + }; + + /** + * Shows the import flow version dialog. + */ + var showImportFlowVersionDialog = function () { + var pt = $('#new-process-group-dialog').data('pt'); + $('#import-flow-version-dialog').data('pt', pt); + + // update the registry and bucket visibility + var registryCombo = $('#import-flow-version-registry-combo').combo('destroy').combo({ + options: [{ + text: 'Loading registries...', + value: null, + optionClass: 'unset', + disabled: true + }] + }).show(); + var bucketCombo = $('#import-flow-version-bucket-combo').combo('destroy').combo({ + options: [{ + text: 'Loading buckets...', + value: null, + optionClass: 'unset', + disabled: true + }] + }).show(); + var flowCombo = $('#import-flow-version-name-combo').combo('destroy').combo({ + options: [{ + text: 'Loading flows...', + value: null, + optionClass: 'unset', + disabled: true + }] + }).show(); + + loadRegistries($('#import-flow-version-dialog'), registryCombo, bucketCombo, flowCombo, selectBucketImportVersion, function (bucketEntity) { + return true; + }).done(function () { + // show the import dialog + $('#import-flow-version-dialog').modal('setHeaderText', 'Import Version').modal('setButtonModel', [{ + buttonText: 'Import', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: disableImportOrChangeButton, + handler: { + click: function () { + importFlowVersion().always(function () { + $('#import-flow-version-dialog').modal('hide'); + }); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + + // hide the new process group dialog + $('#new-process-group-dialog').modal('hide'); + }); + }; + + /** + * Loads the flow versions for the specified registry, bucket, and flow. + * + * @param registryIdentifier + * @param bucketIdentifier + * @param flowIdentifier + * @returns deferred + */ + var loadFlowVersions = function (registryIdentifier, bucketIdentifier, flowIdentifier) { + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + var importFlowVersionData = importFlowVersionGrid.getData(); + + // begin the update + importFlowVersionData.beginUpdate(); + + // remove the current versions + importFlowVersionGrid.setSelectedRows([]); + importFlowVersionGrid.resetActiveCell(); + importFlowVersionData.setItems([]); + + return $.ajax({ + type: 'GET', + url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows/' + encodeURIComponent(flowIdentifier) + '/versions', + dataType: 'json' + }).done(function (response) { + if (nfCommon.isDefinedAndNotNull(response.versionedFlowSnapshotMetadataSet) && response.versionedFlowSnapshotMetadataSet.length > 0) { + $.each(response.versionedFlowSnapshotMetadataSet, function (_, entity) { + importFlowVersionData.addItem($.extend({ + id: entity.versionedFlowSnapshotMetadata.version + }, entity.versionedFlowSnapshotMetadata)); + }); + } else { + nfDialog.showOkDialog({ + headerText: 'Flow Versions', + dialogContent: 'This flow does not have any versions available.' + }); + } + }).fail(nfErrorHandler.handleAjaxError).always(function () { + // end the update + importFlowVersionData.endUpdate(); + + // resort + importFlowVersionData.reSort(); + importFlowVersionGrid.invalidate(); + }); + }; + + /** + * Loads the versioned flows from the specified registry and bucket. + * + * @param registryIdentifier + * @param bucketIdentifier + * @param selectFlow + * @returns deferred + */ + var loadFlows = function (registryIdentifier, bucketIdentifier, selectFlow) { + return $.ajax({ + type: 'GET', + url: '../nifi-api/flow/registries/' + encodeURIComponent(registryIdentifier) + '/buckets/' + encodeURIComponent(bucketIdentifier) + '/flows', + dataType: 'json' + }).done(function (response) { + var versionedFlows = []; + + if (nfCommon.isDefinedAndNotNull(response.versionedFlows) && response.versionedFlows.length > 0) { + response.versionedFlows.sort(function (a, b) { + return a.versionedFlow.flowName > b.versionedFlow.flowName; + }); + + $.each(response.versionedFlows, function (_, versionedFlowEntity) { + var versionedFlow = versionedFlowEntity.versionedFlow; + versionedFlows.push({ + text: versionedFlow.flowName, + value: versionedFlow.flowId, + description: nfCommon.escapeHtml(versionedFlow.description) + }); + }); + } else { + versionedFlows.push({ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }); + } + + // load the buckets + $('#import-flow-version-name-combo').combo('destroy').combo({ + options: versionedFlows, + select: function (selectedFlow) { + if (nfCommon.isDefinedAndNotNull(selectedFlow.value)) { + selectFlow(registryIdentifier, bucketIdentifier, selectedFlow.value) + } else { + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + var importFlowVersionData = importFlowVersionGrid.getData(); + + // clear the current values + importFlowVersionData.beginUpdate(); + importFlowVersionData.setItems([]); + importFlowVersionData.endUpdate(); + } + } + }); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Handler when a versioned flow is selected. + * + * @param registryIdentifier + * @param bucketIdentifier + * @param flowIdentifier + */ + var selectVersionedFlow = function (registryIdentifier, bucketIdentifier, flowIdentifier) { + loadFlowVersions(registryIdentifier, bucketIdentifier, flowIdentifier).done(function () { + $('#import-flow-version-dialog').modal('refreshButtons'); + }); + }; + + /** + * Handler when a bucket is selected. + * + * @param selectedBucket + */ + var selectBucketImportVersion = function (selectedBucket) { + // clear the flow versions grid + clearFlowVersionsGrid(); + + if (nfCommon.isDefinedAndNotNull(selectedBucket.value)) { + // mark the flows as loading + $('#import-flow-version-name-combo').combo('destroy').combo({ + options: [{ + text: 'Loading flows...', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + + var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption'); + + // load the flows for the currently selected registry and bucket + loadFlows(selectedRegistry.value, selectedBucket.value, selectVersionedFlow); + } else { + // mark no flows available + $('#import-flow-version-name-combo').combo('destroy').combo({ + options: [{ + text: 'No available flows', + value: null, + optionClass: 'unset', + disabled: true + }] + }); + } + }; + + /** + * Imports the selected flow version. + */ + var importFlowVersion = function () { + var pt = $('#import-flow-version-dialog').data('pt'); + + var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption'); + var selectedBucket = $('#import-flow-version-bucket-combo').combo('getSelectedOption'); + var selectedFlow = $('#import-flow-version-name-combo').combo('getSelectedOption'); + + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + var selectedVersionIndex = importFlowVersionGrid.getSelectedRows(); + var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]); + + var processGroupEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'position': { + 'x': pt.x, + 'y': pt.y + }, + 'versionControlInformation': { + 'registryId': selectedRegistry.value, + 'bucketId': selectedBucket.value, + 'flowId': selectedFlow.value, + 'version': selectedVersion.version + } + } + }; + + return $.ajax({ + type: 'POST', + data: JSON.stringify(processGroupEntity), + url: '../nifi-api/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/process-groups', + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // add the process group to the graph + nfGraph.add({ + 'processGroups': [response] + }, { + 'selectAll': true + }); + + // update component visibility + nfGraph.updateVisibility(); + + // update the birdseye + nfBirdseye.refresh(); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Determines whether the import/change button is disabled. + * + * @returns {boolean} + */ + var disableImportOrChangeButton = function () { + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) { + var selected = importFlowVersionGrid.getSelectedRows(); + + // if the version label is visible, this is a change version request so disable when + // the version that represents the current version is selected + if ($('#import-flow-version-label').is(':visible')) { + if (selected.length === 1) { + var selectedFlow = importFlowVersionGrid.getDataItem(selected[0]); + + var currentVersion = parseInt($('#import-flow-version-label').text(), 10); + return currentVersion === selectedFlow.version; + } else { + return true; + } + } else { + // if importing, enable when a single row is selecting + return selected.length !== 1; + } + } else { + return true; + } + }; + + /** + * Changes the flow version for the currently selected Process Group. + * + * @returns {deferred} + */ + var changeFlowVersion = function () { + var changeTimer = null; + var changeRequest = null; + var cancelled = false; + + var processGroupId = $('#import-flow-version-process-group-id').text(); + var processGroupRevision = $('#import-flow-version-process-group-id').data('revision'); + var versionControlInformation = $('#import-flow-version-process-group-id').data('versionControlInformation'); + + var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance'); + var selectedVersionIndex = importFlowVersionGrid.getSelectedRows(); + var selectedVersion = importFlowVersionGrid.getDataItem(selectedVersionIndex[0]); + + // update the button model of the change version status dialog + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Stop', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + cancelled = true; + + $('#change-version-status-dialog').modal('setButtonModel', []); + + // we are waiting for the next poll attempt + if (changeTimer !== null) { + // cancel it + clearTimeout(changeTimer); + + // cancel the change request + completeChangeRequest(); + } + } + } + }]); + + // hide the import dialog immediately + $('#import-flow-version-dialog').modal('hide'); + + var submitChangeRequest = function () { + var changeVersionRequest = { + 'processGroupRevision': nfClient.getRevision({ + 'revision': { + 'version': processGroupRevision.version + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'versionControlInformation': { + 'groupId': processGroupId, + 'registryId': versionControlInformation.registryId, + 'bucketId': versionControlInformation.bucketId, + 'flowId': versionControlInformation.flowId, + 'version': selectedVersion.version + } + }; + + return $.ajax({ + type: 'POST', + data: JSON.stringify(changeVersionRequest), + url: '../nifi-api/versions/update-requests/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json', + contentType: 'application/json' + }).done(function () { + // initialize the progress bar value + updateProgress(0); + + // show the progress dialog + $('#change-version-status-dialog').modal('show'); + }).fail(nfErrorHandler.handleAjaxError); + }; + + var pollChangeRequest = function () { + getChangeRequest().done(processChangeResponse); + }; + + var getChangeRequest = function () { + return $.ajax({ + type: 'GET', + url: changeRequest.uri, + dataType: 'json' + }).fail(completeChangeRequest).fail(nfErrorHandler.handleAjaxError); + }; + + var completeChangeRequest = function () { + if (cancelled === true) { + // update the message to indicate successful completion + $('#change-version-status-message').text('The change version request has been cancelled.'); + + // update the button model + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Close', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]); + } + + if (nfCommon.isDefinedAndNotNull(changeRequest)) { + $.ajax({ + type: 'DELETE', + url: changeRequest.uri + '?' + $.param({ + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged() + }), + dataType: 'json' + }).done(function (response) { + changeRequest = response.request; + + // update the component that was changing + updateProcessGroup(processGroupId); + + if (nfCommon.isDefinedAndNotNull(changeRequest.failureReason)) { + // hide the progress dialog + $('#change-version-status-dialog').modal('hide'); + + nfDialog.showOkDialog({ + headerText: 'Change Version', + dialogContent: nfCommon.escapeHtml(changeRequest.failureReason) + }); + } else { + // update the percent complete + updateProgress(changeRequest.percentCompleted); + + // update the message to indicate successful completion + $('#change-version-status-message').text('This Process Group version has changed.'); + + // update the button model + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Close', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]); + } + }); + } + }; + + var processChangeResponse = function (response) { + changeRequest = response.request; + + if (changeRequest.complete === true || cancelled === true) { + completeChangeRequest(); + } else { + // update the percent complete + updateProgress(changeRequest.percentCompleted); + + // update the status of the listing request + $('#change-version-status-message').text(changeRequest.state); + + changeTimer = setTimeout(function () { + // clear the timer since we've been invoked + changeTimer = null; + + // poll revert request + pollChangeRequest(); + }, 2000); + } + }; + + submitChangeRequest().done(processChangeResponse); + }; + + /** + * Gets the version control information for the specified process group id. + * + * @param processGroupId + * @return {deferred} + */ + var getVersionControlInformation = function (processGroupId) { + return $.Deferred(function (deferred) { + if (processGroupId === nfCanvasUtils.getGroupId()) { + $.ajax({ + type: 'GET', + url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json' + }).done(function (response) { + deferred.resolve(response); + }).fail(function () { + deferred.reject(); + }); + } else { + var processGroup = nfProcessGroup.get(processGroupId); + if (processGroup.permissions.canRead === true && processGroup.permissions.canWrite === true) { + deferred.resolve({ + 'processGroupRevision': processGroup.revision, + 'versionControlInformation': processGroup.component.versionControlInformation + }); + } else { + deferred.reject(); + } + } + }).promise(); + }; + + /** + * Updates the specified process group with the specified version control information. + * + * @param processGroupId + * @param versionControlInformation + */ + var updateVersionControlInformation = function (processGroupId, versionControlInformation) { + // refresh either selected PG or bread crumb to reflect connected/tracking status + if (nfCanvasUtils.getGroupId() === processGroupId) { + nfNgBridge.injector.get('breadcrumbsCtrl').updateVersionControlInformation(processGroupId, versionControlInformation); + nfNgBridge.digest(); + } else { + nfProcessGroup.reload(processGroupId); + } + }; + + /** + * Updates the specified process group following an operation that may change it's contents. + * + * @param processGroupId + */ + var updateProcessGroup = function (processGroupId) { + if (nfCanvasUtils.getGroupId() === processGroupId) { + // if reverting/changing current PG... reload/refresh this group/canvas + + $.ajax({ + type: 'GET', + url: '../nifi-api/flow/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json' + }).done(function (response) { + // update the graph components + nfGraph.set(response.processGroupFlow.flow); + + // update the component visibility + nfGraph.updateVisibility(); + + // update the breadcrumbs + var breadcrumbsCtrl = nfNgBridge.injector.get('breadcrumbsCtrl'); + breadcrumbsCtrl.resetBreadcrumbs(); + breadcrumbsCtrl.generateBreadcrumbs(response.processGroupFlow.breadcrumb); + + // inform Angular app values have changed + nfNgBridge.digest(); + }).fail(nfErrorHandler.handleAjaxError); + } else { + // if reverting selected PG... reload selected PG to update counts, etc + nfProcessGroup.reload(processGroupId); + } + }; + + /** + * Updates the progress bar to the specified percent complete. + * + * @param percentComplete + */ + var updateProgress = function (percentComplete) { + // remove existing labels + var progressBar = $('#change-version-percent-complete'); + progressBar.find('div.progress-label').remove(); + progressBar.find('md-progress-linear').remove(); + + // update the progress + var label = $('<div class="progress-label"></div>').text(percentComplete + '%'); + (nfNgBridge.injector.get('$compile')($('<md-progress-linear ng-cloak ng-value="' + percentComplete + '" class="md-hue-2" md-mode="determinate" aria-label="Searching Queue"></md-progress-linear>'))(nfNgBridge.rootScope)).appendTo(progressBar); + progressBar.append(label); + }; + + /** + * Shows local changes for the specified process group. + * + * @param processGroupId + * @param localChangesMessage + * @param localChangesTable + * @param totalLabel + */ + var loadLocalChanges = function (processGroupId, localChangesMessage, localChangesTable, totalLabel) { + var localChangesGrid = localChangesTable.data('gridInstance'); + var localChangesData = localChangesGrid.getData(); + + // begin the update + localChangesData.beginUpdate(); + + // remove the current versions + localChangesGrid.setSelectedRows([]); + localChangesGrid.resetActiveCell(); + localChangesData.setItems([]); + + // load the necessary details + var loadMessage = getVersionControlInformation(processGroupId).done(function (response) { + if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) { + var vci = response.versionControlInformation; + localChangesMessage.text('The following changes have been made to ' + vci.flowName + ' (Version ' + vci.version + ').'); + } else { + nfDialog.showOkDialog({ + headerText: 'Change Version', + dialogContent: 'This Process Group is not currently under version control.' + }); + } + }); + var loadChanges = $.ajax({ + type: 'GET', + url: '../nifi-api/process-groups/' + encodeURIComponent(processGroupId) + '/local-modifications', + dataType: 'json' + }).done(function (response) { + if (nfCommon.isDefinedAndNotNull(response.componentDifferences) && response.componentDifferences.length > 0) { + var totalDifferences = 0; + $.each(response.componentDifferences, function (_, componentDifference) { + $.each(componentDifference.differences, function (_, difference) { + localChangesData.addItem({ + id: totalDifferences++, + componentId: componentDifference.componentId, + componentName: componentDifference.componentName, + componentType: componentDifference.componentType, + processGroupId: componentDifference.processGroupId, + differenceType: difference.differenceType, + difference: difference.difference + }); + }); + }); + + // end the update + localChangesData.endUpdate(); + + // resort + localChangesData.reSort(); + localChangesGrid.invalidate(); + + // update the total displayed + totalLabel.text(nfCommon.formatInteger(totalDifferences)); + } else { + nfDialog.showOkDialog({ + headerText: 'Local Changes', + dialogContent: 'This Process Group does not have any local changes.' + }); + } + }).fail(nfErrorHandler.handleAjaxError); + + return $.when(loadMessage, loadChanges); + }; + + /** + * Revert local changes for the specified process group. + * + * @param processGroupId + */ + var revertLocalChanges = function (processGroupId) { + getVersionControlInformation(processGroupId).done(function (response) { + if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) { + var revertTimer = null; + var revertRequest = null; + var cancelled = false; + + // update the button model of the revert status dialog + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Stop', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + cancelled = true; + + $('#change-version-status-dialog').modal('setButtonModel', []); + + // we are waiting for the next poll attempt + if (revertTimer !== null) { + // cancel it + clearTimeout(revertTimer); + + // cancel the revert request + completeRevertRequest(); + } + } + } + }]); + + // hide the import dialog immediately + $('#import-flow-version-dialog').modal('hide'); + + var submitRevertRequest = function () { + var revertFlowVersionRequest = { + 'processGroupRevision': nfClient.getRevision({ + 'revision': { + 'version': response.processGroupRevision.version + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'versionControlInformation': response.versionControlInformation + }; + + return $.ajax({ + type: 'POST', + data: JSON.stringify(revertFlowVersionRequest), + url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json', + contentType: 'application/json' + }).done(function () { + // initialize the progress bar value + updateProgress(0); + + // show the progress dialog + $('#change-version-status-dialog').modal('show'); + }).fail(nfErrorHandler.handleAjaxError); + }; + + var pollRevertRequest = function () { + getRevertRequest().done(processRevertResponse); + }; + + var getRevertRequest = function () { + return $.ajax({ + type: 'GET', + url: revertRequest.uri, + dataType: 'json' + }).fail(completeRevertRequest).fail(nfErrorHandler.handleAjaxError); + }; + + var completeRevertRequest = function () { + if (cancelled === true) { + // update the message to indicate successful completion + $('#change-version-status-message').text('The revert request has been cancelled.'); + + // update the button model + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Close', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]); + } + + if (nfCommon.isDefinedAndNotNull(revertRequest)) { + $.ajax({ + type: 'DELETE', + url: revertRequest.uri + '?' + $.param({ + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged() + }), + dataType: 'json' + }).done(function (response) { + revertRequest = response.request; + + // update the component that was changing + updateProcessGroup(processGroupId); + + if (nfCommon.isDefinedAndNotNull(revertRequest.failureReason)) { + // hide the progress dialog + $('#change-version-status-dialog').modal('hide'); + + nfDialog.showOkDialog({ + headerText: 'Revert Local Changes', + dialogContent: nfCommon.escapeHtml(revertRequest.failureReason) + }); + } else { + // update the percent complete + updateProgress(revertRequest.percentCompleted); + + // update the message to indicate successful completion + $('#change-version-status-message').text('This Process Group version has changed.'); + + // update the button model + $('#change-version-status-dialog').modal('setButtonModel', [{ + buttonText: 'Close', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]); + } + }); + } + }; + + var processRevertResponse = function (response) { + revertRequest = response.request; + + if (revertRequest.complete === true || cancelled === true) { + completeRevertRequest(); + } else { + // update the percent complete + updateProgress(revertRequest.percentCompleted); + + // update the status of the revert request + $('#change-version-status-message').text(revertRequest.state); + + revertTimer = setTimeout(function () { + // clear the timer since we've been invoked + revertTimer = null; + + // poll revert request + pollRevertRequest(); + }, 2000); + } + }; + + submitRevertRequest().done(processRevertResponse); + } else { + nfDialog.showOkDialog({ + headerText: 'Revert Changes', + dialogContent: 'This Process Group is not currently under version control.' + }); + } + }).fail(nfErrorHandler.handleAjaxError); + }; + + return { + init: function (timeOffset) { + serverTimeOffset = timeOffset; + + // initialize the flow version dialog + $('#save-flow-version-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Save Flow Version', + buttons: [{ + buttonText: 'Save', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + if ($('#save-flow-version-registry-combo').is(':visible')) { + var selectedRegistry = $('#save-flow-version-registry-combo').combo('getSelectedOption'); + var selectedBucket = $('#save-flow-version-bucket-combo').combo('getSelectedOption'); + + if (nfCommon.isDefinedAndNotNull(selectedRegistry) && nfCommon.isDefinedAndNotNull(selectedBucket)) { + return selectedRegistry.disabled === true || selectedBucket.disabled === true; + } else { + return true; + } + } else { + return false; + } + }, + handler: { + click: function () { + var processGroupId = $('#save-flow-version-process-group-id').text(); + saveFlowVersion().done(function (response) { + updateVersionControlInformation(processGroupId, response.versionControlInformation); + }); + + $(this).modal('hide'); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }], + handler: { + close: function () { + resetSaveFlowVersionDialog(); + } + } + }); + + // initialize the import flow version dialog + $('#import-flow-version-dialog').modal({ + scrollableContentStyle: 'scrollable', + handler: { + close: function () { + resetImportFlowVersionDialog(); + } + } + }); + + // configure the drop request status dialog + $('#change-version-status-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Change Flow Version', + handler: { + close: function () { + // clear the current button model + $('#change-version-status-dialog').modal('setButtonModel', []); + } + } + }); + + // init the revert local changes dialog + $('#revert-local-changes-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Revert Local Changes', + buttons: [{ + buttonText: 'Revert', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + var processGroupId = $('#revert-local-changes-process-group-id').text(); + revertLocalChanges(processGroupId); + + $(this).modal('hide'); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }], + handler: { + close: function () { + resetRevertLocalChangesDialog(); + } + } + }); + + // init the show local changes dialog + $('#show-local-changes-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Show Local Changes', + buttons: [{ + buttonText: 'Close', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }], + handler: { + close: function () { + resetShowLocalChangesDialog(); + } + } + }); + + // handle the click for the process group import + $('#import-process-group-link').on('click', function() { + showImportFlowVersionDialog(); + }); + + // initialize the import flow version table + initImportFlowVersionTable(); + initLocalChangesTable($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries')); + initLocalChangesTable($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries')); + }, + + /** + * Shows the flow version dialog. + * + * @param processGroupId + */ + showFlowVersionDialog: function (processGroupId) { + var focusName = true; + + return $.Deferred(function (deferred) { + getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) { + if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) { + var versionControlInformation = groupVersionControlInformation.versionControlInformation; + + // update the registry and bucket visibility + $('#save-flow-version-registry').text(versionControlInformation.registryName).show(); + $('#save-flow-version-bucket').text(versionControlInformation.bucketName).show(); + $('#save-flow-version-label').text(versionControlInformation.version + 1); + + $('#save-flow-version-name').text(versionControlInformation.flowName).show(); + nfCommon.populateField('save-flow-version-description', versionControlInformation.flowDescription); + $('#save-flow-version-description').show(); + + // record the versionControlInformation + $('#save-flow-version-process-group-id').data('versionControlInformation', versionControlInformation); + + // reposition the version label + $('#save-flow-version-label').css('margin-top', '-15px'); + + focusName = false; + deferred.resolve(); + } else { + // update the registry and bucket visibility + var registryCombo = $('#save-flow-version-registry-combo').combo('destroy').combo({ + options: [{ + text: 'Loading registries...', + value: null, + optionClass: 'unset', + disabled: true + }] + }).show(); + var bucketCombo = $('#save-flow-version-bucket-combo').combo('destroy').combo({ + options: [{ + text: 'Loading buckets...', + value: null, + optionClass: 'unset', + disabled: true + }] + }).show(); + + // set the initial version + $('#save-flow-version-label').text(1); + + $('#save-flow-version-name-field').show(); + $('#save-flow-version-description-field').show(); + + // reposition the version label + $('#save-flow-version-label').css('margin-top', '0'); + + loadRegistries($('#save-flow-version-dialog'), registryCombo, bucketCombo, null, selectBucketSaveFlowVersion, function (bucketEntity) { + return bucketEntity.permissions.canWrite === true; + }).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + } + + // record the revision + $('#save-flow-version-process-group-id').data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId); + }).fail(nfErrorHandler.handleAjaxError); + }).done(function () { + $('#save-flow-version-dialog').modal('show'); + + if (focusName) { + $('#save-flow-version-name-field').focus(); + } else { + $('#save-flow-version-change-comments').focus(); + } + }).fail(function () { + $('#save-flow-version-dialog').modal('refreshButtons'); + }).promise(); + }, + + /** + * Reverts local changes for the specified Process Group. + * + * @param processGroupId + */ + revertLocalChanges: function (processGroupId) { + loadLocalChanges(processGroupId, $('#revert-local-changes-message'), $('#revert-local-changes-table'), $('#total-revert-local-changes-entries')).done(function () { + $('#revert-local-changes-process-group-id').text(processGroupId); + $('#revert-local-changes-dialog').modal('show'); + }); + }, + + /** + * Shows local changes for the specified process group. + * + * @param processGroupId + */ + showLocalChanges: function (processGroupId) { + loadLocalChanges(processGroupId, $('#show-local-changes-message'), $('#show-local-changes-table'), $('#total-show-local-changes-entries')).done(function () { + $('#show-local-changes-dialog').modal('show'); + }); + }, + + /** + * Shows the change flow version dialog. + * + * @param processGroupId + */ + showChangeFlowVersionDialog: function (processGroupId) { + return $.Deferred(function (deferred) { + getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) { + if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) { + var versionControlInformation = groupVersionControlInformation.versionControlInformation; + + // update the registry and bucket visibility + $('#import-flow-version-registry').text(versionControlInformation.registryName).show(); + $('#import-flow-version-bucket').text(versionControlInformation.bucketName).show(); + $('#import-flow-version-name').text(versionControlInformation.flowName).show(); + + // show the current version information + $('#import-flow-version-container').show(); + $('#import-flow-version-label').text(versionControlInformation.version); + + // record the versionControlInformation + $('#import-flow-version-process-group-id').data('versionControlInformation', versionControlInformation).data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId); + + // load the flow versions + loadFlowVersions(versionControlInformation.registryId, versionControlInformation.bucketId, versionControlInformation.flowId).done(function () { + deferred.resolve(); + }).fail(function () { + nfDialog.showOkDialog({ + headerText: 'Change Version', + dialogContent: 'Unable to load available versions for this Process Group.' + }); + + deferred.reject(); + }); + } else { + nfDialog.showOkDialog({ + headerText: 'Change Version', + dialogContent: 'This Process Group is not currently under version control.' + }); + + deferred.reject(); + } + }).fail(nfErrorHandler.handleAjaxError); + }).done(function () { + // show the dialog + $('#import-flow-version-dialog').modal('setHeaderText', 'Change Version').modal('setButtonModel', [{ + buttonText: 'Change', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: disableImportOrChangeButton, + handler: { + click: function () { + changeFlowVersion(); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + }).promise(); + }, + + /** + * Stops version control for the specified Process Group. + * + * @param processGroupId + */ + stopVersionControl: function (processGroupId) { + // prompt the user before disconnecting + nfDialog.showYesNoDialog({ + headerText: 'Stop Version Control', + dialogContent: 'Are you sure you want to stop version control?', + noText: 'Cancel', + yesText: 'Disconnect', + yesHandler: function () { + $.ajax({ + type: 'GET', + url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId), + dataType: 'json' + }).done(function (response) { + if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) { + var revision = nfClient.getRevision({ + revision: { + version: response.processGroupRevision.version + } + }); + + $.ajax({ + type: 'DELETE', + url: '../nifi-api/versions/process-groups/' + encodeURIComponent(processGroupId) + '?' + $.param($.extend({ + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged() + }, revision)), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + updateVersionControlInformation(processGroupId, undefined); + + nfDialog.showOkDialog({ + headerText: 'Disconnect', + dialogContent: 'This Process Group is no longer under version control.' + }); + }).fail(nfErrorHandler.handleAjaxError); + } else { + nfDialog.showOkDialog({ + headerText: 'Disconnect', + dialogContent: 'This Process Group is not currently under version control.' + }) + } + }).fail(nfErrorHandler.handleAjaxError); + } + }); + } + }; +})); diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js new file mode 100644 index 0000000..614472c --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-process-group.js @@ -0,0 +1,1744 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global d3, define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'd3', + 'nf.Connection', + 'nf.Common', + 'nf.Client', + 'nf.CanvasUtils', + 'nf.Dialog'], + function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) { + return (nf.ProcessGroup = factory($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ProcessGroup = + factory(require('jquery'), + require('d3'), + require('nf.Connection'), + require('nf.Common'), + require('nf.Client'), + require('nf.CanvasUtils'), + require('nf.Dialog'))); + } else { + nf.ProcessGroup = factory(root.$, + root.d3, + root.nf.Connection, + root.nf.Common, + root.nf.Client, + root.nf.CanvasUtils, + root.nf.Dialog); + } +} +(this, function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) { + 'use strict'; + + var nfConnectable; + var nfDraggable; + var nfSelectable; + var nfContextMenu; + + var PREVIEW_NAME_LENGTH = 30; + + var dimensions = { + width: 380, + height: 172 + }; + + // ---------------------------- + // process groups currently on the graph + // ---------------------------- + + var processGroupMap; + + // ----------------------------------------------------------- + // cache for components that are added/removed from the canvas + // ----------------------------------------------------------- + + var removedCache; + var addedCache; + + // -------------------- + // component containers + // -------------------- + + var processGroupContainer; + + // -------------------------- + // privately scoped functions + // -------------------------- + + /** + * Determines whether the specified process group is under version control. + * + * @param d + */ + var isUnderVersionControl = function (d) { + return nfCommon.isDefinedAndNotNull(d.versionedFlowState); + }; + + /** + * Selects the process group elements against the current process group map. + */ + var select = function () { + return processGroupContainer.selectAll('g.process-group').data(processGroupMap.values(), function (d) { + return d.id; + }); + }; + + + /** + * Renders the process groups in the specified selection. + * + * @param {selection} entered The selection of process groups to be rendered + * @param {boolean} selected Whether the process group should be selected + * @return the entered selection + */ + var renderProcessGroups = function (entered, selected) { + if (entered.empty()) { + return entered; + } + + var processGroup = entered.append('g') + .attrs({ + 'id': function (d) { + return 'id-' + d.id; + }, + 'class': 'process-group component' + }) + .classed('selected', selected) + .call(nfCanvasUtils.position); + + // ---- + // body + // ---- + + // process group border + processGroup.append('rect') + .attrs({ + 'class': 'border', + 'width': function (d) { + return d.dimensions.width; + }, + 'height': function (d) { + return d.dimensions.height; + }, + 'fill': 'transparent', + 'stroke': 'transparent' + }); + + // process group body + processGroup.append('rect') + .attrs({ + 'class': 'body', + 'width': function (d) { + return d.dimensions.width; + }, + 'height': function (d) { + return d.dimensions.height; + }, + 'filter': 'url(#component-drop-shadow)', + 'stroke-width': 0 + }); + + // process group name background + processGroup.append('rect') + .attrs({ + 'width': function (d) { + return d.dimensions.width; + }, + 'height': 32, + 'fill': '#b8c6cd' + }); + + // process group name + processGroup.append('text') + .attrs({ + 'x': 10, + 'y': 20, + 'width': 300, + 'height': 16, + 'class': 'process-group-name' + }); + + // process group name + processGroup.append('text') + .attrs({ + 'x': 10, + 'y': 21, + 'class': 'version-control' + }); + + console.log(processGroup); + + + // always support selecting and navigation + processGroup.on('dblclick', function (d) { + // enter this group on double click + nfProcessGroup.enterGroup(d.id); + }) + .call(nfSelectable.activate).call(nfContextMenu.activate); + + // only support dragging, connection, and drag and drop if appropriate + processGroup.filter(function (d) { + return d.permissions.canWrite && d.permissions.canRead; + }) + .on('mouseover.drop', function (d) { + // Using mouseover/out to workaround chrome issue #122746 + + // get the target and ensure its not already been marked for drop + var target = d3.select(this); + if (!target.classed('drop')) { + var targetData = target.datum(); + + // see if there is a selection being dragged + var drag = d3.select('rect.drag-selection'); + if (!drag.empty()) { + // filter the current selection by this group + var selection = nfCanvasUtils.getSelection().filter(function (d) { + return targetData.id === d.id; + }); + + // ensure this group isn't in the selection + if (selection.empty()) { + // mark that we are hovering over a drop area if appropriate + target.classed('drop', function () { + // get the current selection and ensure its disconnected + return nfConnection.isDisconnected(nfCanvasUtils.getSelection()); + }); + } + } + } + }) + .on('mouseout.drop', function (d) { + // mark that we are no longer hovering over a drop area unconditionally + d3.select(this).classed('drop', false); + }) + .call(nfDraggable.activate) + .call(nfConnectable.activate); + + return processGroup; + }; + + // attempt of space between component count and icon for process group contents + var CONTENTS_SPACER = 10; + var CONTENTS_VALUE_SPACER = 5; + + /** + * Updates the process groups in the specified selection. + * + * @param {selection} updated The process groups to be updated + */ + var updateProcessGroups = function (updated) { + if (updated.empty()) { + return; + } + + // process group border authorization + updated.select('rect.border') + .classed('unauthorized', function (d) { + return d.permissions.canRead === false; + }); + + // process group body authorization + updated.select('rect.body') + .classed('unauthorized', function (d) { + return d.permissions.canRead === false; + }); + + updated.each(function (processGroupData) { + var processGroup = d3.select(this); + var details = processGroup.select('g.process-group-details'); + + // update the component behavior as appropriate + nfCanvasUtils.editable(processGroup, nfConnectable, nfDraggable); + + // if this processor is visible, render everything + if (processGroup.classed('visible')) { + if (details.empty()) { + details = processGroup.append('g').attr('class', 'process-group-details'); + + // ------------------- + // contents background + // ------------------- + + details.append('rect') + .attrs({ + 'x': 0, + 'y': 32, + 'width': function () { + return processGroupData.dimensions.width + }, + 'height': 24, + 'fill': '#e3e8eb' + }); + + details.append('rect') + .attrs({ + 'x': 0, + 'y': function () { + return processGroupData.dimensions.height - 24; + }, + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 24, + 'fill': '#e3e8eb' + }); + + // -------- + // contents + // -------- + + // transmitting icon +// details.append('text') +// .attrs({ +// 'x': 10, +// 'y': 49, +// 'class': 'process-group-transmitting process-group-contents-icon', +// 'font-family': 'FontAwesome' +// }) +// .text('\uf140') +// .append("title") +// .text("Transmitting Remote Process Groups"); + + + // transmitting count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-transmitting-count process-group-contents-count' +// }); + + // not transmitting icon +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-not-transmitting process-group-contents-icon', +// 'font-family': 'flowfont' +// }) +// .text('\ue80a') +// .append("title") +// .text("Not Transmitting Remote Process Groups"); + + // not transmitting count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-not-transmitting-count process-group-contents-count' +// }); + + // running icon +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-running process-group-contents-icon', +// 'font-family': 'FontAwesome' +// }) +// .text('\uf04b') +// .append("title") +// .text("Running Components"); + + // running count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-running-count process-group-contents-count' +// }); + + // stopped icon +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-stopped process-group-contents-icon', +// 'font-family': 'FontAwesome' +// }) +// .text('\uf04d') +// .append("title") +// .text("Stopped Components"); + + // stopped count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-stopped-count process-group-contents-count' +// }); + + // invalid icon +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-invalid process-group-contents-icon', +// 'font-family': 'FontAwesome' +// }) +// .text('\uf071') +// .append("title") +// .text("Invalid Components"); + + // invalid count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-invalid-count process-group-contents-count' +// }); + + // disabled icon +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-disabled process-group-contents-icon', +// 'font-family': 'flowfont' +// }) +// .text('\ue802') +// .append("title") +// .text("Disabled Components"); + + // disabled count +// details.append('text') +// .attrs({ +// 'y': 49, +// 'class': 'process-group-disabled-count process-group-contents-count' +// }); + + // up to date icon + details.append('text') + .attrs({ + 'x': 10, + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-up-to-date process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf00c') + .append("title") + .text("Up to date Versioned Process Groups"); + + // up to date count + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-up-to-date-count process-group-contents-count' + }); + + // locally modified icon + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-locally-modified process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf069') + .append("title") + .text("Locally modified Versioned Process Groups"); + + // locally modified count + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-locally-modified-count process-group-contents-count' + }); + + // stale icon + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-stale process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf0aa') + .append("title") + .text("Stale Versioned Process Groups"); + + // stale count + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-stale-count process-group-contents-count' + }); + + // locally modified and stale icon + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-locally-modified-and-stale process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf06a') + .append("title") + .text("Locally modified and stale Versioned Process Groups"); + + // locally modified and stale count + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-locally-modified-and-stale-count process-group-contents-count' + }); + + // sync failure icon + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-sync-failure process-group-contents-icon', + 'font-family': 'FontAwesome' + }) + .text('\uf128') + .append("title") + .text("Sync failure Versioned Process Groups"); + + // sync failure count + details.append('text') + .attrs({ + 'y': function () { + return processGroupData.dimensions.height - 7; + }, + 'class': 'process-group-sync-failure-count process-group-contents-count' + }); + + // ---------------- + // stats background + // ---------------- + + // queued + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 19, + 'x': 0, + 'y': 66, + 'fill': '#f4f6f7' + }); + + // border + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 1, + 'x': 0, + 'y': 84, + 'fill': '#c7d2d7' + }); + + // in + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 19, + 'x': 0, + 'y': 85, + 'fill': '#ffffff' + }); + + // border + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 1, + 'x': 0, + 'y': 103, + 'fill': '#c7d2d7' + }); + + // read/write + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 19, + 'x': 0, + 'y': 104, + 'fill': '#f4f6f7' + }); + + // border + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 1, + 'x': 0, + 'y': 122, + 'fill': '#c7d2d7' + }); + + // out + details.append('rect') + .attrs({ + 'width': function () { + return processGroupData.dimensions.width; + }, + 'height': 19, + 'x': 0, + 'y': 123, + 'fill': '#ffffff' + }); + + // ----- + // stats + // ----- + + // stats label container + var processGroupStatsLabel = details.append('g') + .attrs({ + 'transform': 'translate(6, 75)' + }); + + // queued label + processGroupStatsLabel.append('text') + .attrs({ + 'width': 73, + 'height': 10, + 'x': 4, + 'y': 5, + 'class': 'stats-label' + }) + .text('Queued'); + + // in label + processGroupStatsLabel.append('text') + .attrs({ + 'width': 73, + 'height': 10, + 'x': 4, + 'y': 24, + 'class': 'stats-label' + }) + .text('In'); + + // read/write label + processGroupStatsLabel.append('text') + .attrs({ + 'width': 73, + 'height': 10, + 'x': 4, + 'y': 42, + 'class': 'stats-label' + }) + .text('Read/Write'); + + // out label + processGroupStatsLabel.append('text') + .attrs({ + 'width': 73, + 'height': 10, + 'x': 4, + 'y': 60, + 'class': 'stats-label' + }) + .text('Out'); + + // stats value container + var processGroupStatsValue = details.append('g') + .attrs({ + 'transform': 'translate(95, 75)' + }); + + // queued value + var queuedText = processGroupStatsValue.append('text') + .attrs({ + 'width': 180, + 'height': 10, + 'x': 4, + 'y': 5, + 'class': 'process-group-queued stats-value' + }); + + // queued count + queuedText.append('tspan') + .attrs({ + 'class': 'count' + }); + + // queued size + queuedText.append('tspan') + .attrs({ + 'class': 'size' + }); + + // in value + var inText = processGroupStatsValue.append('text') + .attrs({ + 'width': 180, + 'height': 10, + 'x': 4, + 'y': 24, + 'class': 'process-group-in stats-value' + }); + + // in count + inText.append('tspan') + .attrs({ + 'class': 'count' + }); + + // in size + inText.append('tspan') + .attrs({ + 'class': 'size' + }); + + // in + inText.append('tspan') + .attrs({ + 'class': 'ports' + }); + + // read/write value + processGroupStatsValue.append('text') + .attrs({ + 'width': 180, + 'height': 10, + 'x': 4, + 'y': 42, + 'class': 'process-group-read-write stats-value' + }); + + // out value + var outText = processGroupStatsValue.append('text') + .attrs({ + 'width': 180, + 'height': 10, + 'x': 4, + 'y': 60, + 'class': 'process-group-out stats-value' + }); + + // out ports + outText.append('tspan') + .attrs({ + 'class': 'ports' + }); + + // out count + outText.append('tspan') + .attrs({ + 'class': 'count' + }); + + // out size + outText.append('tspan') + .attrs({ + 'class': 'size' + }); + + // stats value container + var processGroupStatsInfo = details.append('g') + .attrs({ + 'transform': 'translate(335, 75)' + }); + + // in info + processGroupStatsInfo.append('text') + .attrs({ + 'width': 25, + 'height': 10, + 'x': 4, + 'y': 24, + 'class': 'stats-info' + }) + .text('5 min'); + + // read/write info + processGroupStatsInfo.append('text') + .attrs({ + 'width': 25, + 'height': 10, + 'x': 4, + 'y': 42, + 'class': 'stats-info' + }) + .text('5 min'); + + // out info + processGroupStatsInfo.append('text') + .attrs({ + 'width': 25, + 'height': 10, + 'x': 4, + 'y': 60, + 'class': 'stats-info' + }) + .text('5 min'); + + // -------- + // comments + // -------- + + details.append('path') + .attrs({ + 'class': 'component-comments', + 'transform': 'translate(' + (processGroupData.dimensions.width - 2) + ', ' + (processGroupData.dimensions.height - 10) + ')', + 'd': 'm0,0 l0,8 l-8,0 z' + }); + + // ------------------- + // active thread count + // ------------------- + + // active thread count + details.append('text') + .attrs({ + 'class': 'active-thread-count-icon', + 'y': 20 + }) + .text('\ue83f'); + + // active thread icon + details.append('text') + .attrs({ + 'class': 'active-thread-count', + 'y': 20 + }); + + // --------- + // bulletins + // --------- + + // bulletin background + details.append('rect') + .attrs({ + 'class': 'bulletin-background', + 'x': function () { + return processGroupData.dimensions.width - 24; + }, + 'y': 32, + 'width': 24, + 'height': 24 + }); + + // bulletin icon + details.append('text') + .attrs({ + 'class': 'bulletin-icon', + 'x': function () { + return processGroupData.dimensions.width - 17; + }, + 'y': 49 + }) + .text('\uf24a'); + } + + // update transmitting + var transmitting = details.select('text.process-group-transmitting') + .classed('transmitting', function (d) { + return d.permissions.canRead && d.activeRemotePortCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.activeRemotePortCount === 0; + }); + var transmittingCount = details.select('text.process-group-transmitting-count') + .attr('x', function () { + var transmittingCountX = parseInt(transmitting.attr('x'), 10); + return transmittingCountX + Math.round(transmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.activeRemotePortCount; + }); + transmittingCount.append("title").text("Transmitting Remote Process Groups"); + + // update not transmitting + var notTransmitting = details.select('text.process-group-not-transmitting') + .classed('not-transmitting', function (d) { + return d.permissions.canRead && d.inactiveRemotePortCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.inactiveRemotePortCount === 0; + }) + .attr('x', function () { + var transmittingX = parseInt(transmittingCount.attr('x'), 10); + return transmittingX + Math.round(transmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var notTransmittingCount = details.select('text.process-group-not-transmitting-count') + .attr('x', function () { + var notTransmittingCountX = parseInt(notTransmitting.attr('x'), 10); + return notTransmittingCountX + Math.round(notTransmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.inactiveRemotePortCount; + }); + notTransmittingCount.append("title").text("Not transmitting Remote Process Groups") + + // update running + var running = details.select('text.process-group-running') + .classed('running', function (d) { + return d.permissions.canRead && d.component.runningCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.runningCount === 0; + }) + .attr('x', function () { + var notTransmittingX = parseInt(notTransmittingCount.attr('x'), 10); + return notTransmittingX + Math.round(notTransmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var runningCount = details.select('text.process-group-running-count') + .attr('x', function () { + var runningCountX = parseInt(running.attr('x'), 10); + return runningCountX + Math.round(running.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.runningCount; + }); + runningCount.append("title").text("Running Components"); + + // update stopped + var stopped = details.select('text.process-group-stopped') + .classed('stopped', function (d) { + return d.permissions.canRead && d.component.stoppedCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.stoppedCount === 0; + }) + .attr('x', function () { + var runningX = parseInt(runningCount.attr('x'), 10); + return runningX + Math.round(runningCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var stoppedCount = details.select('text.process-group-stopped-count') + .attr('x', function () { + var stoppedCountX = parseInt(stopped.attr('x'), 10); + return stoppedCountX + Math.round(stopped.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.stoppedCount; + }); + stoppedCount.append("title").text("Stopped Components"); + + // update invalid + var invalid = details.select('text.process-group-invalid') + .classed('invalid', function (d) { + return d.permissions.canRead && d.component.invalidCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.invalidCount === 0; + }) + .attr('x', function () { + var stoppedX = parseInt(stoppedCount.attr('x'), 10); + return stoppedX + Math.round(stoppedCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var invalidCount = details.select('text.process-group-invalid-count') + .attr('x', function () { + var invalidCountX = parseInt(invalid.attr('x'), 10); + return invalidCountX + Math.round(invalid.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.invalidCount; + }); + invalidCount.append("title").text("Invalid Components"); + + // update disabled + var disabled = details.select('text.process-group-disabled') + .classed('disabled', function (d) { + return d.permissions.canRead && d.component.disabledCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.disabledCount === 0; + }) + .attr('x', function () { + var invalidX = parseInt(invalidCount.attr('x'), 10); + return invalidX + Math.round(invalidCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var disabledCount = details.select('text.process-group-disabled-count') + .attr('x', function () { + var disabledCountX = parseInt(disabled.attr('x'), 10); + return disabledCountX + Math.round(disabled.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.disabledCount; + }); + disabledCount.append("title").text("Disabled Components"); + + // up to date current + var upToDate = details.select('text.process-group-up-to-date') + .classed('up-to-date', function (d) { + return d.permissions.canRead && d.component.upToDateCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.upToDateCount === 0; + }); + var upToDateCount = details.select('text.process-group-up-to-date-count') + .attr('x', function () { + var updateToDateCountX = parseInt(upToDate.attr('x'), 10); + return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.upToDateCount; + }); + upToDateCount.append("title").text("Up to date Versioned Process Groups"); + + // update locally modified + var locallyModified = details.select('text.process-group-locally-modified') + .classed('locally-modified', function (d) { + return d.permissions.canRead && d.component.locallyModifiedCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.locallyModifiedCount === 0; + }) + .attr('x', function () { + var upToDateX = parseInt(upToDateCount.attr('x'), 10); + return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var locallyModifiedCount = details.select('text.process-group-locally-modified-count') + .attr('x', function () { + var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10); + return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.locallyModifiedCount; + }); + locallyModifiedCount.append("title").text("Locally modified Versioned Process Groups"); + + // update stale + var stale = details.select('text.process-group-stale') + .classed('stale', function (d) { + return d.permissions.canRead && d.component.staleCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.staleCount === 0; + }) + .attr('x', function () { + var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10); + return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var staleCount = details.select('text.process-group-stale-count') + .attr('x', function () { + var staleCountX = parseInt(stale.attr('x'), 10); + return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.staleCount; + }); + staleCount.append("title").text("Stale Versioned Process Groups"); + + // update locally modified and stale + var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale') + .classed('locally-modified-and-stale', function (d) { + return d.permissions.canRead && d.component.locallyModifiedAndStaleCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.locallyModifiedAndStaleCount === 0; + }) + .attr('x', function () { + var staleX = parseInt(staleCount.attr('x'), 10); + return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER; + }); + var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count') + .attr('x', function () { + var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10); + return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.locallyModifiedAndStaleCount; + }); + locallyModifiedAndStaleCount.append("title").text("Locally modified and stale Versioned Process Groups"); + + // update sync failure + var syncFailure = details.select('text.process-group-sync-failure') + .classed('sync-failure', function (d) { + return d.permissions.canRead && d.component.syncFailureCount > 0; + }) + .classed('zero', function (d) { + return d.permissions.canRead && d.component.syncFailureCount === 0; + }) + .attr('x', function () { + var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10); + return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2; + }); + var syncFailureCount = details.select('text.process-group-sync-failure-count') + .attr('x', function () { + var syncFailureCountX = parseInt(syncFailure.attr('x'), 10); + return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + }) + .text(function (d) { + return d.syncFailureCount; + }); + syncFailureCount.append("title").text("Sync failure Versioned Process Groups"); + + /** + * update version control information + * @author: Renu + * @desc for lines 1110-1201: based on state of the process group, environment selection and submit button enable/disable + */ + var versionControl = processGroup.select('text.version-control') + .styles({ + 'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden', + 'fill': function () { + if (isUnderVersionControl(processGroupData)) { + var vciState = processGroupData.versionedFlowState; + if (vciState === 'SYNC_FAILURE') { + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return '#666666'; + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { + console.log("locally but stale in style"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return '#BA554A'; + } else if (vciState === 'STALE') { + console.log("stale in style"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + return '#BA554A'; + } else if (vciState === 'LOCALLY_MODIFIED') { + console.log("locally modified in style"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return '#666666'; + } else { + return '#1A9964'; + $('#environmentType').prop('disabled', false); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + } + } else { + $('#environmentType').prop('disabled', true); + return '#000'; + } + } + }) + .text(function () { + if (isUnderVersionControl(processGroupData)) { + var vciState = processGroupData.versionedFlowState; + if (vciState === 'SYNC_FAILURE') { + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return '\uf128' + } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') { + console.log("locally but stale in text"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + + return '\uf06a'; + } else if (vciState === 'STALE') { + console.log("stale in text"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + return '\uf0aa'; + } else if (vciState === 'LOCALLY_MODIFIED') { + console.log("locally modified in text"); + $('#environmentType').prop('disabled', true); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + return '\uf069'; + } else { + return '\uf00c'; + $('#environmentType').prop('disabled', false); + if($('#environmentType').val() && !$('#environmentType').prop('disabled')){ + $('#operate-submit-btn').prop('disabled', false); + }else{$('#operate-submit-btn').prop('disabled', true);} + } + } else { + $('#environmentType').prop('disabled', true); + return ''; + } + }); + + if (processGroupData.permissions.canRead) { + // version control tooltip + versionControl.each(function () { + // get the tip + var tip = d3.select('#version-control-tip-' + processGroupData.id); + + // if there are validation errors generate a tooltip + if (isUnderVersionControl(processGroupData)) { + // create the tip if necessary + if (tip.empty()) { + tip = d3.select('#process-group-tooltips').append('div') + .attr('id', function () { + return 'version-control-tip-' + processGroupData.id; + }) + .attr('class', 'tooltip nifi-tooltip'); + } + + // update the tip + tip.html(function () { + var vci = processGroupData.component.versionControlInformation; + var versionControlTip = $('<div></div>').text('Tracking to "' + vci.flowName + '" version ' + vci.version + ' in "' + vci.registryName + ' - ' + vci.bucketName + '"'); + var versionControlStateTip = $('<div></div>').text(nfCommon.getVersionControlTooltip(vci)); + return $('<div></div>').append(versionControlTip).append('<br/>').append(versionControlStateTip).html(); + }); + + // add the tooltip + nfCanvasUtils.canvasTooltip(tip, d3.select(this)); + } else { + // remove the tip if necessary + if (!tip.empty()) { + tip.remove(); + } + } + }); + + // update the process group comments + processGroup.select('path.component-comments') + .style('visibility', nfCommon.isBlank(processGroupData.component.comments) ? 'hidden' : 'visible') + .each(function () { + // get the tip + var tip = d3.select('#comments-tip-' + processGroupData.id); + + // if there are validation errors generate a tooltip + if (nfCommon.isBlank(processGroupData.component.comments)) { + // remove the tip if necessary + if (!tip.empty()) { + tip.remove(); + } + } else { + // create the tip if necessary + if (tip.empty()) { + tip = d3.select('#process-group-tooltips').append('div') + .attr('id', function () { + return 'comments-tip-' + processGroupData.id; + }) + .attr('class', 'tooltip nifi-tooltip'); + } + + // update the tip + tip.text(processGroupData.component.comments); + + // add the tooltip + nfCanvasUtils.canvasTooltip(tip, d3.select(this)); + } + }); + + // update the process group name + processGroup.select('text.process-group-name') + .attrs({ + 'x': function () { + if (isUnderVersionControl(processGroupData)) { + var versionControlX = parseInt(versionControl.attr('x'), 10); + return versionControlX + Math.round(versionControl.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER; + } else { + return 10; + } + }, + 'width': function () { + if (isUnderVersionControl(processGroupData)) { + var versionControlX = parseInt(versionControl.attr('x'), 10); + var processGroupNameX = parseInt(d3.select(this).attr('x'), 10); + return 300 - (processGroupNameX - versionControlX); + } else { + return 300; + } + } + }) + .each(function (d) { + var processGroupName = d3.select(this); + + // reset the process group name to handle any previous state + processGroupName.text(null).selectAll('title').remove(); + + // apply ellipsis to the process group name as necessary + nfCanvasUtils.ellipsis(processGroupName, d.component.name); + }) + .append('title') + .text(function (d) { + return d.component.name; + }); + } else { + // clear the process group comments + processGroup.select('path.component-comments').style('visibility', 'hidden'); + + // clear the process group name + processGroup.select('text.process-group-name') + .attrs({ + 'x': 10, + 'width': 316 + }) + .text(null); + + // clear tooltips + processGroup.call(removeTooltips); + } + + // populate the stats + processGroup.call(updateProcessGroupStatus); + } else { + if (processGroupData.permissions.canRead) { + // update the process group name + processGroup.select('text.process-group-name') + .text(function (d) { + var name = d.component.name; + if (name.length > PREVIEW_NAME_LENGTH) { + return name.substring(0, PREVIEW_NAME_LENGTH) + String.fromCharCode(8230); + } else { + return name; + } + }); + } else { + // clear the process group name + processGroup.select('text.process-group-name').text(null); + } + + // remove the tooltips + processGroup.call(removeTooltips); + + // remove the details if necessary + if (!details.empty()) { + details.remove(); + } + } + }); + }; + + /** + * Updates the process group status. + * + * @param {selection} updated The process groups to be updated + */ + var updateProcessGroupStatus = function (updated) { + if (updated.empty()) { + return; + } + + // queued count value + updated.select('text.process-group-queued tspan.count') + .text(function (d) { + return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.queued, ' '); + }); + + // queued size value + updated.select('text.process-group-queued tspan.size') + .text(function (d) { + return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.queued, ' '); + }); + + // in count value + updated.select('text.process-group-in tspan.count') + .text(function (d) { + return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.input, ' '); + }); + + // in size value + updated.select('text.process-group-in tspan.size') + .text(function (d) { + return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.input, ' '); + }); + + // in ports value + updated.select('text.process-group-in tspan.ports') + .text(function (d) { + return ' ' + String.fromCharCode(8594) + ' ' + d.inputPortCount; + }); + + // read/write value + updated.select('text.process-group-read-write') + .text(function (d) { + return d.status.aggregateSnapshot.read + ' / ' + d.status.aggregateSnapshot.written; + }); + + // out ports value + updated.select('text.process-group-out tspan.ports') + .text(function (d) { + return d.outputPortCount + ' ' + String.fromCharCode(8594) + ' '; + }); + + // out count value + updated.select('text.process-group-out tspan.count') + .text(function (d) { + return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.output, ' '); + }); + + // out size value + updated.select('text.process-group-out tspan.size') + .text(function (d) { + return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.output, ' '); + }); + + updated.each(function (d) { + var processGroup = d3.select(this); + var offset = 0; + + // ------------------- + // active thread count + // ------------------- + + nfCanvasUtils.activeThreadCount(processGroup, d, function (off) { + offset = off; + }); + + // --------- + // bulletins + // --------- + + processGroup.select('rect.bulletin-background').classed('has-bulletins', function () { + return !nfCommon.isEmpty(d.status.aggregateSnapshot.bulletins); + }); + + nfCanvasUtils.bulletins(processGroup, d, function () { + return d3.select('#process-group-tooltips'); + }, offset); + }); + }; + + /** + * Removes the process groups in the specified selection. + * + * @param {selection} removed The process groups to be removed + */ + var removeProcessGroups = function (removed) { + if (removed.empty()) { + return; + } + + removed.call(removeTooltips).remove(); + }; + + /** + * Removes the tooltips for the process groups in the specified selection. + * + * @param {selection} removed + */ + var removeTooltips = function (removed) { + removed.each(function (d) { + // remove any associated tooltips + $('#bulletin-tip-' + d.id).remove(); + $('#version-control-tip-' + d.id).remove(); + $('#comments-tip-' + d.id).remove(); + }); + }; + + var nfProcessGroup = { + /** + * Initializes of the Process Group handler. + * + * @param nfConnectableRef The nfConnectable module. + * @param nfDraggableRef The nfDraggable module. + * @param nfSelectableRef The nfSelectable module. + * @param nfContextMenuRef The nfContextMenu module. + */ + init: function (nfConnectableRef, nfDraggableRef, nfSelectableRef, nfContextMenuRef) { + nfConnectable = nfConnectableRef; + nfDraggable = nfDraggableRef; + nfSelectable = nfSelectableRef; + nfContextMenu = nfContextMenuRef; + + processGroupMap = d3.map(); + removedCache = d3.map(); + addedCache = d3.map(); + + // create the process group container + processGroupContainer = d3.select('#canvas').append('g') + .attrs({ + 'pointer-events': 'all', + 'class': 'process-groups' + }); + }, + + /** + * Adds the specified process group entity. + * + * @param processGroupEntities The process group + * @param options Configuration options + */ + add: function (processGroupEntities, options) { + var selectAll = false; + if (nfCommon.isDefinedAndNotNull(options)) { + selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll; + } + + // get the current time + var now = new Date().getTime(); + + var add = function (processGroupEntity) { + addedCache.set(processGroupEntity.id, now); + + // add the process group + processGroupMap.set(processGroupEntity.id, $.extend({ + type: 'ProcessGroup', + dimensions: dimensions + }, processGroupEntity)); + }; + + // determine how to handle the specified process groups + if ($.isArray(processGroupEntities)) { + $.each(processGroupEntities, function (_, processGroupEntity) { + add(processGroupEntity); + }); + } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) { + add(processGroupEntities); + } + + // select + var selection = select(); + + // enter + var entered = renderProcessGroups(selection.enter(), selectAll); + + // update + updateProcessGroups(selection.merge(entered)); + }, + + /** + * Populates the graph with the specified process groups. + * + * @argument {object | array} processGroupEntities The process groups to add + * @argument {object} options Configuration options + */ + set: function (processGroupEntities, options) { + var selectAll = false; + var transition = false; + var overrideRevisionCheck = false; + if (nfCommon.isDefinedAndNotNull(options)) { + selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll; + transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition; + overrideRevisionCheck = nfCommon.isDefinedAndNotNull(options.overrideRevisionCheck) ? options.overrideRevisionCheck : overrideRevisionCheck; + } + + var set = function (proposedProcessGroupEntity) { + var currentProcessGroupEntity = processGroupMap.get(proposedProcessGroupEntity.id); + + // set the process group if appropriate due to revision and wasn't previously removed + if ((nfClient.isNewerRevision(currentProcessGroupEntity, proposedProcessGroupEntity) && !removedCache.has(proposedProcessGroupEntity.id)) || overrideRevisionCheck === true) { + processGroupMap.set(proposedProcessGroupEntity.id, $.extend({ + type: 'ProcessGroup', + dimensions: dimensions + }, proposedProcessGroupEntity)); + } + }; + + // determine how to handle the specified process groups + if ($.isArray(processGroupEntities)) { + $.each(processGroupMap.keys(), function (_, key) { + var currentProcessGroupEntity = processGroupMap.get(key); + var isPresent = $.grep(processGroupEntities, function (proposedProcessGroupEntity) { + return proposedProcessGroupEntity.id === currentProcessGroupEntity.id; + }); + + // if the current process group is not present and was not recently added, remove it + if (isPresent.length === 0 && !addedCache.has(key)) { + processGroupMap.remove(key); + } + }); + $.each(processGroupEntities, function (_, processGroupEntity) { + set(processGroupEntity); + }); + } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) { + set(processGroupEntities); + } + + // select + var selection = select(); + + // enter + var entered = renderProcessGroups(selection.enter(), selectAll); + + // update + var updated = selection.merge(entered); + updated.call(updateProcessGroups).call(nfCanvasUtils.position, transition); + + // exit + selection.exit().call(removeProcessGroups); + }, + + /** + * If the process group id is specified it is returned. If no process group id + * specified, all process groups are returned. + * + * @param {string} id + */ + get: function (id) { + if (nfCommon.isUndefined(id)) { + return processGroupMap.values(); + } else { + return processGroupMap.get(id); + } + }, + + /** + * If the process group id is specified it is refresh according to the current + * state. If no process group id is specified, all process groups are refreshed. + * + * @param {string} id Optional + */ + refresh: function (id) { + if (nfCommon.isDefinedAndNotNull(id)) { + d3.select('#id-' + id).call(updateProcessGroups); + } else { + d3.selectAll('g.process-group').call(updateProcessGroups); + } + }, + + /** + * Refreshes the components necessary after a pan event. + */ + pan: function () { + d3.selectAll('g.process-group.entering, g.process-group.leaving').call(updateProcessGroups); + }, + + /** + * Reloads the process group state from the server and refreshes the UI. + * If the process group is currently unknown, this function reloads the canvas. + * + * @param {string} id The process group id + */ + reload: function (id) { + if (processGroupMap.has(id)) { + var processGroupEntity = processGroupMap.get(id); + return $.ajax({ + type: 'GET', + url: processGroupEntity.uri, + dataType: 'json' + }).done(function (response) { + nfProcessGroup.set(response); + }); + } + }, + + /** + * Positions the component. + * + * @param {string} id The id + */ + position: function (id) { + d3.select('#id-' + id).call(nfCanvasUtils.position); + }, + + /** + * Removes the specified process group. + * + * @param {string} processGroupIds The process group id(s) + */ + remove: function (processGroupIds) { + var now = new Date().getTime(); + + if ($.isArray(processGroupIds)) { + $.each(processGroupIds, function (_, processGroupId) { + removedCache.set(processGroupId, now); + processGroupMap.remove(processGroupId); + }); + } else { + removedCache.set(processGroupIds, now); + processGroupMap.remove(processGroupIds); + } + + // apply the selection and handle all removed process groups + select().exit().call(removeProcessGroups); + }, + + /** + * Removes all process groups. + */ + removeAll: function () { + nfProcessGroup.remove(processGroupMap.keys()); + }, + + /** + * Expires the caches up to the specified timestamp. + * + * @param timestamp + */ + expireCaches: function (timestamp) { + var expire = function (cache) { + cache.each(function (entryTimestamp, id) { + if (timestamp > entryTimestamp) { + cache.remove(id); + } + }); + }; + + expire(addedCache); + expire(removedCache); + }, + + /** + * Enters the specified group. + * + * @param {string} groupId + */ + enterGroup: function (groupId) { + + // hide the context menu + nfContextMenu.hide(); + + // set the new group id + nfCanvasUtils.setGroupId(groupId); + + // reload the graph + return nfCanvasUtils.reload().done(function () { + + // attempt to restore the view + var viewRestored = nfCanvasUtils.restoreUserView(); + + // if the view was not restore attempt to fit + if (viewRestored === false) { + nfCanvasUtils.fitCanvas(); + } + + // update URL deep linking params + nfCanvasUtils.setURLParameters(groupId, d3.select()); + + }).fail(function () { + nfDialog.showOkDialog({ + headerText: 'Process Group', + dialogContent: 'Unable to enter the selected group.' + }); + }); + } + }; + + return nfProcessGroup; +})); diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js new file mode 100644 index 0000000..8c61dac --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-settings.js @@ -0,0 +1,2373 @@ +/* + * 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. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'Slick', + 'd3', + 'nf.Client', + 'nf.Dialog', + 'nf.Storage', + 'nf.Common', + 'nf.CanvasUtils', + 'nf.ControllerServices', + 'nf.ErrorHandler', + 'nf.FilteredDialogCommon', + 'nf.ReportingTask', + 'nf.Shell', + 'nf.ComponentState', + 'nf.ComponentVersion', + 'nf.PolicyManagement'], + function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) { + return (nf.Settings = factory($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.Settings = + factory(require('jquery'), + require('Slick'), + require('d3'), + require('nf.Client'), + require('nf.Dialog'), + require('nf.Storage'), + require('nf.Common'), + require('nf.CanvasUtils'), + require('nf.ControllerServices'), + require('nf.ErrorHandler'), + require('nf.FilteredDialogCommon'), + require('nf.ReportingTask'), + require('nf.Shell'), + require('nf.ComponentState'), + require('nf.ComponentVersion'), + require('nf.PolicyManagement'))); + } else { + nf.Settings = factory(root.$, + root.Slick, + root.d3, + root.nf.Client, + root.nf.Dialog, + root.nf.Storage, + root.nf.Common, + root.nf.CanvasUtils, + root.nf.ControllerServices, + root.nf.ErrorHandler, + root.nf.FilteredDialogCommon, + root.nf.ReportingTask, + root.nf.Shell, + root.nf.ComponentState, + root.nf.ComponentVersion, + root.nf.PolicyManagement); + } +}(this, function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfControllerServices, nfErrorHandler, nfFilteredDialogCommon, nfReportingTask, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement) { + 'use strict'; + + + var config = { + urls: { + api: '../nifi-api', + controllerConfig: '../nifi-api/controller/config', + reportingTaskTypes: '../nifi-api/flow/reporting-task-types', + createReportingTask: '../nifi-api/controller/reporting-tasks', + reportingTasks: '../nifi-api/flow/reporting-tasks', + registries: '../nifi-api/controller/registry-clients' + } + }; + + var gridOptions = { + forceFitColumns: true, + enableTextSelectionOnCells: true, + enableCellNavigation: true, + enableColumnReorder: false, + autoEdit: false, + multiSelect: false, + rowHeight: 24 + }; + + + var dcaeDistributorApiHostname; + + //get hostname + $.ajax({ + type: 'GET', + url: '../nifi-api/flow/config', + dataType: 'json', + contentType: 'application/json', + success: function(data){ + dcaeDistributorApiHostname= data.flowConfiguration.dcaeDistributorApiHostname; + console.log(dcaeDistributorApiHostname); + } + }); + + + /** + * Gets the controller services table. + * + * @returns {*|jQuery|HTMLElement} + */ + var getDistributionEnvironmentsTable = function () { + return $('#distribution-environments-table'); + }; + + /** + * Gets the controller services table. + * + * @returns {*|jQuery|HTMLElement} + */ + var getControllerServicesTable = function () { + return $('#controller-services-table'); + }; + + /** + * Saves the settings for the controller. + * + * @param version + */ + var saveSettings = function (version) { + // marshal the configuration details + var configuration = marshalConfiguration(); + var entity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': version + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': configuration + }; + + // save the new configuration details + $.ajax({ + type: 'PUT', + url: config.urls.controllerConfig, + data: JSON.stringify(entity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // close the settings dialog + nfDialog.showOkDialog({ + headerText: 'Settings', + dialogContent: 'Settings successfully applied.' + }); + + // register the click listener for the save button + $('#settings-save').off('click').on('click', function () { + saveSettings(response.revision.version); + }); + }).fail(nfErrorHandler.handleAjaxError); + } + + /** + * Initializes the general tab. + */ + var initGeneral = function () { + }; + + /** + * Marshals the details to include in the configuration request. + */ + var marshalConfiguration = function () { + // create the configuration + var configuration = {}; + configuration['maxTimerDrivenThreadCount'] = $('#maximum-timer-driven-thread-count-field').val(); + configuration['maxEventDrivenThreadCount'] = $('#maximum-event-driven-thread-count-field').val(); + return configuration; + }; + + /** + * Determines if the item matches the filter. + * + * @param {object} item The item to filter + * @param {object} args The filter criteria + * @returns {boolean} Whether the item matches the filter + */ + var matchesRegex = function (item, args) { + if (args.searchString === '') { + return true; + } + + try { + // perform the row filtering + var filterExp = new RegExp(args.searchString, 'i'); + } catch (e) { + // invalid regex + return false; + } + + // determine if the item matches the filter + var matchesLabel = item['label'].search(filterExp) >= 0; + var matchesTags = item['tags'].search(filterExp) >= 0; + return matchesLabel || matchesTags; + }; + + /** + * Determines if the specified tags match all the tags selected by the user. + * + * @argument {string[]} tagFilters The tag filters + * @argument {string} tags The tags to test + */ + var matchesSelectedTags = function (tagFilters, tags) { + var selectedTags = []; + $.each(tagFilters, function (_, filter) { + selectedTags.push(filter); + }); + + // normalize the tags + var normalizedTags = tags.toLowerCase(); + + var matches = true; + $.each(selectedTags, function (i, selectedTag) { + if (normalizedTags.indexOf(selectedTag) === -1) { + matches = false; + return false; + } + }); + + return matches; + }; + + /** + * Whether the specified item is selectable. + * + * @param item reporting task type + */ + var isSelectable = function (item) { + return item.restricted === false || nfCommon.canAccessComponentRestrictions(item.explicitRestrictions); + }; + + /** + * Formatter for the name column. + * + * @param {type} row + * @param {type} cell + * @param {type} value + * @param {type} columnDef + * @param {type} dataContext + * @returns {String} + */ + var nameFormatter = function (row, cell, value, columnDef, dataContext) { + if (!dataContext.permissions.canRead) { + return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>'; + } + + return nfCommon.escapeHtml(dataContext.component.name); + }; + + /** + * Sorts the specified data using the specified sort details. + * + * @param {object} sortDetails + * @param {object} data + */ + var sort = function (sortDetails, data) { + // defines a function for sorting + var comparer = function (a, b) { + if (a.permissions.canRead && b.permissions.canRead) { + if (sortDetails.columnId === 'moreDetails') { + var aBulletins = 0; + if (!nfCommon.isEmpty(a.bulletins)) { + aBulletins = a.bulletins.length; + } + var bBulletins = 0; + if (!nfCommon.isEmpty(b.bulletins)) { + bBulletins = b.bulletins.length; + } + return aBulletins - bBulletins; + } else if (sortDetails.columnId === 'type') { + var aType = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? nfCommon.substringAfterLast(a.component[sortDetails.columnId], '.') : ''; + var bType = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? nfCommon.substringAfterLast(b.component[sortDetails.columnId], '.') : ''; + return aType === bType ? 0 : aType > bType ? 1 : -1; + } else if (sortDetails.columnId === 'state') { + var aState; + if (a.component.validationStatus === 'VALIDATING') { + aState = 'Validating'; + } else if (a.component.validationStatus === 'INVALID') { + aState = 'Invalid'; + } else { + aState = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : ''; + } + var bState; + if (b.component.validationStatus === 'VALIDATING') { + bState = 'Validating'; + } else if (b.component.validationStatus === 'INVALID') { + bState = 'Invalid'; + } else { + bState = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : ''; + } + return aState === bState ? 0 : aState > bState ? 1 : -1; + } else { + var aString = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : ''; + var bString = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : ''; + return aString === bString ? 0 : aString > bString ? 1 : -1; + } + } else { + if (!a.permissions.canRead && !b.permissions.canRead) { + return 0; + } + if (a.permissions.canRead) { + return 1; + } else { + return -1; + } + } + }; + + // perform the sort + data.sort(comparer, sortDetails.sortAsc); + }; + + /** + * Get the text out of the filter field. If the filter field doesn't + * have any text it will contain the text 'filter list' so this method + * accounts for that. + */ + var getReportingTaskTypeFilterText = function () { + return $('#reporting-task-type-filter').val(); + }; + + /** + * Filters the reporting task type table. + */ + var applyReportingTaskTypeFilter = function () { + // get the dataview + var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance'); + + // ensure the grid has been initialized + if (nfCommon.isDefinedAndNotNull(reportingTaskTypesGrid)) { + var reportingTaskTypesData = reportingTaskTypesGrid.getData(); + + // update the search criteria + reportingTaskTypesData.setFilterArgs({ + searchString: getReportingTaskTypeFilterText() + }); + reportingTaskTypesData.refresh(); + + // update the buttons to possibly trigger the disabled state + $('#new-reporting-task-dialog').modal('refreshButtons'); + + // update the selection if possible + if (reportingTaskTypesData.getLength() > 0) { + nfFilteredDialogCommon.choseFirstRow(reportingTaskTypesGrid); + } + } + }; + + /** + * Hides the selected reporting task. + */ + var clearSelectedReportingTask = function () { + $('#reporting-task-type-description').attr('title', '').text(''); + $('#reporting-task-type-name').attr('title', '').text(''); + $('#reporting-task-type-bundle').attr('title', '').text(''); + $('#selected-reporting-task-name').text(''); + $('#selected-reporting-task-type').text('').removeData('bundle'); + $('#reporting-task-description-container').hide(); + }; + + /** + * Clears the selected reporting task type. + */ + var clearReportingTaskSelection = function () { + // clear the selected row + clearSelectedReportingTask(); + + // clear the active cell the it can be reselected when its included + var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance'); + reportingTaskTypesGrid.resetActiveCell(); + }; + + /** + * Performs the filtering. + * + * @param {object} item The item subject to filtering + * @param {object} args Filter arguments + * @returns {Boolean} Whether or not to include the item + */ + var filterReportingTaskTypes = function (item, args) { + // determine if the item matches the filter + var matchesFilter = matchesRegex(item, args); + + // determine if the row matches the selected tags + var matchesTags = true; + if (matchesFilter) { + var tagFilters = $('#reporting-task-tag-cloud').tagcloud('getSelectedTags'); + var hasSelectedTags = tagFilters.length > 0; + if (hasSelectedTags) { + matchesTags = matchesSelectedTags(tagFilters, item['tags']); + } + } + + // determine if the row matches the selected source group + var matchesGroup = true; + if (matchesFilter && matchesTags) { + var bundleGroup = $('#reporting-task-bundle-group-combo').combo('getSelectedOption'); + if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') { + matchesGroup = (item.bundle.group === bundleGroup.value); + } + } + + // determine if this row should be visible + var matches = matchesFilter && matchesTags && matchesGroup; + + // if this row is currently selected and its being filtered + if (matches === false && $('#selected-reporting-task-type').text() === item['type']) { + clearReportingTaskSelection(); + } + + return matches; + }; + + /** + * Adds the currently selected reporting task. + */ + var addSelectedReportingTask = function () { + var selectedTaskType = $('#selected-reporting-task-type').text(); + var selectedTaskBundle = $('#selected-reporting-task-type').data('bundle'); + + // ensure something was selected + if (selectedTaskType === '') { + nfDialog.showOkDialog({ + headerText: 'Settings', + dialogContent: 'The type of reporting task to create must be selected.' + }); + } else { + addReportingTask(selectedTaskType, selectedTaskBundle); + } + }; + + /** + * Adds a new reporting task of the specified type. + * + * @param {string} reportingTaskType + * @param {object} reportingTaskBundle + */ + var addReportingTask = function (reportingTaskType, reportingTaskBundle) { + // build the reporting task entity + var reportingTaskEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'type': reportingTaskType, + 'bundle': reportingTaskBundle + } + }; + + // add the new reporting task + var addTask = $.ajax({ + type: 'POST', + url: config.urls.createReportingTask, + data: JSON.stringify(reportingTaskEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (reportingTaskEntity) { + // add the item + var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance'); + var reportingTaskData = reportingTaskGrid.getData(); + reportingTaskData.addItem($.extend({ + type: 'ReportingTask', + bulletins: [] + }, reportingTaskEntity)); + + // resort + reportingTaskData.reSort(); + reportingTaskGrid.invalidate(); + + // select the new reporting task + var row = reportingTaskData.getRowById(reportingTaskEntity.id); + nfFilteredDialogCommon.choseRow(reportingTaskGrid, row); + reportingTaskGrid.scrollRowIntoView(row); + }).fail(nfErrorHandler.handleAjaxError); + + // hide the dialog + $('#new-reporting-task-dialog').modal('hide'); + + return addTask; + }; + + /** + * Adds the specified entity. + */ + var addRegistry = function () { + var registryEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'name': $('#registry-name').val(), + 'uri': $('#registry-location').val(), + 'description': $('#registry-description').val() + } + }; + + // add the new registry + var addRegistry = $.ajax({ + type: 'POST', + url: config.urls.registries, + data: JSON.stringify(registryEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (registryEntity) { + // add the item + var registriesGrid = $('#registries-table').data('gridInstance'); + var registriesData = registriesGrid.getData(); + registriesData.addItem($.extend({ + type: 'Registry' + }, registryEntity)); + + // resort + registriesData.reSort(); + registriesGrid.invalidate(); + + // select the new reporting task + var row = registriesData.getRowById(registryEntity.id); + nfFilteredDialogCommon.choseRow(registriesGrid, row); + registriesGrid.scrollRowIntoView(row); + }).fail(nfErrorHandler.handleAjaxError); + + // hide the dialog + $('#registry-configuration-dialog').modal('hide'); + + return addRegistry; + }; + + + /** + * Adds the specific environment. + */ + var addDistributionEnvironment= function () { + var environmentEntity = { + 'name': $('#distribution-environment-name').val(), + 'runtimeApiUrl': $('#distribution-environment-location').val(), + 'description': $('#distribution-environment-description').val() +// 'nextDistributionTargetId': $('#distribution-environment-nextDistributionTargetId').val() + }; + + console.log("before POST call "); + console.log(environmentEntity); + + // add the new distribution environment + var addDistributionEnvironment= $.ajax({ + type: 'POST', + url: dcaeDistributorApiHostname+'/distribution-targets', + data: JSON.stringify(environmentEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (environmentEntity) { + // add the item + console.log("after POST call in response "); + console.log(environmentEntity); + var environmentsGrid = $('#distribution-environments-table').data('gridInstance'); + console.log(environmentsGrid); + var environmentsData = environmentsGrid.getData(); + environmentsData.addItem($.extend({ + type: 'Environment' + }, environmentEntity)); + + + // resort + environmentsData.reSort(); + environmentsGrid.invalidate(); + + // select the new distribution env. + var row = environmentsData.getRowById(environmentEntity.id); + nfFilteredDialogCommon.choseRow(environmentsGrid, row); + registriesGrid.scrollRowIntoView(row); + }).fail(nfErrorHandler.handleAjaxError); + + // hide the dialog + $('#distribution-environment-dialog').modal('hide'); + + return addDistributionEnvironment; + }; + + + /** + * Updates the registry with the specified id. + * + * @param registryId + */ + var updateRegistry = function (registryId) { + var registriesGrid = $('#registries-table').data('gridInstance'); + var registriesData = registriesGrid.getData(); + + var registryEntity = registriesData.getItemById(registryId); + var requestRegistryEntity = { + 'revision': nfClient.getRevision(registryEntity), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'id': registryId, + 'name': $('#registry-name').val(), + 'uri': $('#registry-location').val(), + 'description': $('#registry-description').val() + } + }; + + // add the new reporting task + var updateRegistry = $.ajax({ + type: 'PUT', + url: registryEntity.uri, + data: JSON.stringify(requestRegistryEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (registryEntity) { + // add the item + registriesData.updateItem(registryId, $.extend({ + type: 'Registry' + }, registryEntity)); + }).fail(nfErrorHandler.handleAjaxError); + + // hide the dialog + $('#registry-configuration-dialog').modal('hide'); + + return updateRegistry; + }; + + + /** + * Updates the distribution environment with the specified id. + * + * @param environmentId + */ + var updateDistributionEnvironment = function (environmentId) { + var environmentsGrid = $('#distribution-environments-table').data('gridInstance'); + var environmentsData = environmentsGrid.getData(); + + var environmentEntity = environmentsData.getItemById(environmentId); + var requestEnvironmentEntity = { + 'id': environmentId, + 'name': $('#distribution-environment-name').val(), + 'runtimeApiUrl': $('#distribution-environment-location').val(), + 'description': $('#distribution-environment-description').val(), +// 'nextDistributionTargetId': $('#distribution-environment-nextDistributionTargetId').val() + }; + + console.log(requestEnvironmentEntity); + // updating distribution environment + var updateDistributionEnvironment = $.ajax({ + type: 'PUT', + url: dcaeDistributorApiHostname+'/distribution-targets/'+environmentEntity.id, + data: JSON.stringify(requestEnvironmentEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (environmentEntity) { + // update the item + console.log(environmentsGrid); + environmentsData.updateItem(environmentId, $.extend({ + type: 'Environment' + }, environmentEntity)); + }).fail(nfErrorHandler.handleAjaxError); + + // hide the dialog + $('#distribution-environment-dialog').modal('hide'); + + return updateDistributionEnvironment; + }; + + + + + + /** + * Initializes the new reporting task dialog. + */ + var initNewReportingTaskDialog = function () { + // initialize the reporting task type table + var reportingTaskTypesColumns = [ + { + id: 'type', + name: 'Type', + field: 'label', + formatter: nfCommon.typeFormatter, + sortable: true, + resizable: true + }, + { + id: 'version', + name: 'Version', + field: 'version', + formatter: nfCommon.typeVersionFormatter, + sortable: true, + resizable: true + }, + { + id: 'tags', + name: 'Tags', + field: 'tags', + sortable: true, + resizable: true, + formatter: nfCommon.genericValueFormatter + } + ]; + + // initialize the dataview + var reportingTaskTypesData = new Slick.Data.DataView({ + inlineFilters: false + }); + reportingTaskTypesData.setItems([]); + reportingTaskTypesData.setFilterArgs({ + searchString: getReportingTaskTypeFilterText() + }); + reportingTaskTypesData.setFilter(filterReportingTaskTypes); + + // initialize the sort + nfCommon.sortType({ + columnId: 'type', + sortAsc: true + }, reportingTaskTypesData); + + // initialize the grid + var reportingTaskTypesGrid = new Slick.Grid('#reporting-task-types-table', reportingTaskTypesData, reportingTaskTypesColumns, gridOptions); + reportingTaskTypesGrid.setSelectionModel(new Slick.RowSelectionModel()); + reportingTaskTypesGrid.registerPlugin(new Slick.AutoTooltips()); + reportingTaskTypesGrid.setSortColumn('type', true); + reportingTaskTypesGrid.onSort.subscribe(function (e, args) { + nfCommon.sortType({ + columnId: args.sortCol.field, + sortAsc: args.sortAsc + }, reportingTaskTypesData); + }); + reportingTaskTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) { + if ($.isArray(args.rows) && args.rows.length === 1) { + var reportingTaskTypeIndex = args.rows[0]; + var reportingTaskType = reportingTaskTypesGrid.getDataItem(reportingTaskTypeIndex); + + // set the reporting task type description + if (nfCommon.isDefinedAndNotNull(reportingTaskType)) { + // show the selected reporting task + $('#reporting-task-description-container').show(); + + if (nfCommon.isBlank(reportingTaskType.description)) { + $('#reporting-task-type-description') + .attr('title', '') + .html('<span class="unset">No description specified</span>'); + } else { + $('#reporting-task-type-description') + .width($('#reporting-task-description-container').innerWidth() - 1) + .html(reportingTaskType.description) + .ellipsis(); + } + + var bundle = nfCommon.formatBundle(reportingTaskType.bundle); + var type = nfCommon.formatType(reportingTaskType); + + // populate the dom + $('#reporting-task-type-name').text(type).attr('title', type); + $('#reporting-task-type-bundle').text(bundle).attr('title', bundle); + $('#selected-reporting-task-name').text(reportingTaskType.label); + $('#selected-reporting-task-type').text(reportingTaskType.type).data('bundle', reportingTaskType.bundle); + + // refresh the buttons based on the current selection + $('#new-reporting-task-dialog').modal('refreshButtons'); + } + } + }); + reportingTaskTypesGrid.onDblClick.subscribe(function (e, args) { + var reportingTaskType = reportingTaskTypesGrid.getDataItem(args.row); + + if (isSelectable(reportingTaskType)) { + addReportingTask(reportingTaskType.type, reportingTaskType.bundle); + } + }); + reportingTaskTypesGrid.onViewportChanged.subscribe(function (e, args) { + nfCommon.cleanUpTooltips($('#reporting-task-types-table'), 'div.view-usage-restriction'); + }); + + // wire up the dataview to the grid + reportingTaskTypesData.onRowCountChanged.subscribe(function (e, args) { + reportingTaskTypesGrid.updateRowCount(); + reportingTaskTypesGrid.render(); + + // update the total number of displayed processors + $('#displayed-reporting-task-types').text(args.current); + }); + reportingTaskTypesData.onRowsChanged.subscribe(function (e, args) { + reportingTaskTypesGrid.invalidateRows(args.rows); + reportingTaskTypesGrid.render(); + }); + reportingTaskTypesData.syncGridSelection(reportingTaskTypesGrid, true); + + // hold onto an instance of the grid + $('#reporting-task-types-table').data('gridInstance', reportingTaskTypesGrid).on('mouseenter', 'div.slick-cell', function (e) { + var usageRestriction = $(this).find('div.view-usage-restriction'); + if (usageRestriction.length && !usageRestriction.data('qtip')) { + var rowId = $(this).find('span.row-id').text(); + + // get the status item + var item = reportingTaskTypesData.getItemById(rowId); + + // show the tooltip + if (item.restricted === true) { + var restrictionTip = $('<div></div>'); + + if (nfCommon.isBlank(item.usageRestriction)) { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:')); + } else { + restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:')); + } + + var restrictions = []; + if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) { + $.each(item.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation)); + }); + } else { + restrictions.push('Access to restricted components regardless of restrictions.'); + } + restrictionTip.append(nfCommon.formatUnorderedList(restrictions)); + + usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, { + content: restrictionTip, + position: { + container: $('#summary'), + at: 'bottom right', + my: 'top left', + adjust: { + x: 4, + y: 4 + } + } + })); + } + } + }); + + var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components'); + + // load the available reporting tasks + $.ajax({ + type: 'GET', + url: config.urls.reportingTaskTypes, + dataType: 'json' + }).done(function (response) { + var id = 0; + var tags = []; + var groups = d3.set(); + var restrictedUsage = d3.map(); + var requiredPermissions = d3.map(); + + // begin the update + reportingTaskTypesData.beginUpdate(); + + // go through each reporting task type + $.each(response.reportingTaskTypes, function (i, documentedType) { + if (documentedType.restricted === true) { + if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) { + $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) { + var requiredPermission = explicitRestriction.requiredPermission; + + // update required permissions + if (!requiredPermissions.has(requiredPermission.id)) { + requiredPermissions.set(requiredPermission.id, requiredPermission.label); + } + + // update component restrictions + if (!restrictedUsage.has(requiredPermission.id)) { + restrictedUsage.set(requiredPermission.id, []); + } + + restrictedUsage.get(requiredPermission.id).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(explicitRestriction.explanation) + }) + }); + } else { + // update required permissions + if (!requiredPermissions.has(generalRestriction.value)) { + requiredPermissions.set(generalRestriction.value, generalRestriction.text); + } + + // update component restrictions + if (!restrictedUsage.has(generalRestriction.value)) { + restrictedUsage.set(generalRestriction.value, []); + } + + restrictedUsage.get(generalRestriction.value).push({ + type: nfCommon.formatType(documentedType), + bundle: nfCommon.formatBundle(documentedType.bundle), + explanation: nfCommon.escapeHtml(documentedType.usageRestriction) + }); + } + } + + // record the group + groups.add(documentedType.bundle.group); + + // add the documented type + reportingTaskTypesData.addItem({ + id: id++, + label: nfCommon.substringAfterLast(documentedType.type, '.'), + type: documentedType.type, + bundle: documentedType.bundle, + description: nfCommon.escapeHtml(documentedType.description), + restricted: documentedType.restricted, + usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction), + explicitRestrictions: documentedType.explicitRestrictions, + tags: documentedType.tags.join(', ') + }); + + // count the frequency of each tag for this type + $.each(documentedType.tags, function (i, tag) { + tags.push(tag.toLowerCase()); + }); + }); + + // end the update + reportingTaskTypesData.endUpdate(); + + // resort + reportingTaskTypesData.reSort(); + reportingTaskTypesGrid.invalidate(); + + // set the component restrictions and the corresponding required permissions + nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions); + + // set the total number of processors + $('#total-reporting-task-types, #displayed-reporting-task-types').text(response.reportingTaskTypes.length); + + // create the tag cloud + $('#reporting-task-tag-cloud').tagcloud({ + tags: tags, + select: applyReportingTaskTypeFilter, + remove: applyReportingTaskTypeFilter + }); + + // build the combo options + var options = [{ + text: 'all groups', + value: '' + }]; + groups.each(function (group) { + options.push({ + text: group, + value: group + }); + }); + + // initialize the bundle group combo + $('#reporting-task-bundle-group-combo').combo({ + options: options, + select: applyReportingTaskTypeFilter + }); + }).fail(nfErrorHandler.handleAjaxError); + + var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN]; + + // define the function for filtering the list + $('#reporting-task-type-filter').off('keyup').on('keyup', function (e) { + var code = e.keyCode ? e.keyCode : e.which; + + // ignore navigation keys + if ($.inArray(code, navigationKeys) !== -1) { + return; + } + + if (code === $.ui.keyCode.ENTER) { + var selected = reportingTaskTypesGrid.getSelectedRows(); + + if (selected.length > 0) { + // grid configured with multi-select = false + var item = reportingTaskTypesGrid.getDataItem(selected[0]); + if (isSelectable(item)) { + addSelectedReportingTask(); + } + } + } else { + applyReportingTaskTypeFilter(); + } + }); + + // setup row navigation + nfFilteredDialogCommon.addKeydownListener('#reporting-task-type-filter', reportingTaskTypesGrid, reportingTaskTypesGrid.getData()); + + // initialize the reporting task dialog + $('#new-reporting-task-dialog').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Add Reporting Task', + buttons: [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + var selected = reportingTaskTypesGrid.getSelectedRows(); + + if (selected.length > 0) { + // grid configured with multi-select = false + var item = reportingTaskTypesGrid.getDataItem(selected[0]); + return isSelectable(item) === false; + } else { + return reportingTaskTypesGrid.getData().getLength() === 0; + } + }, + handler: { + click: function () { + addSelectedReportingTask(); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }], + handler: { + close: function () { + // clear the selected row + clearSelectedReportingTask(); + + // clear any filter strings + $('#reporting-task-type-filter').val(''); + + // clear the tagcloud + $('#reporting-task-tag-cloud').tagcloud('clearSelectedTags'); + + // reset the group combo + $('#reporting-task-bundle-group-combo').combo('setSelectedOption', { + value: '' + }); + + // reset the filter + applyReportingTaskTypeFilter(); + + // unselect any current selection + var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance'); + reportingTaskTypesGrid.setSelectedRows([]); + reportingTaskTypesGrid.resetActiveCell(); + }, + resize: function () { + $('#reporting-task-type-description') + .width($('#reporting-task-description-container').innerWidth() - 1) + .text($('#reporting-task-type-description').attr('title')) + .ellipsis(); + } + } + }); + + // initialize the registry configuration dialog + $('#registry-configuration-dialog').modal({ + scrollableContentStyle: 'scrollable', + handler: { + close: function () { + $('#registry-id').text(''); + $('#registry-name').val(''); + $('#registry-location').val(''); + $('#registry-description').val(''); + } + } + }); + + + // initialize the distribution environment dialog + $('#distribution-environment-dialog').modal({ + scrollableContentStyle: 'scrollable', + handler: { + close: function () { + $('#distribution-environment-id').text(''); + $('#distribution-environment-name').val(''); + $('#distribution-environment-location').val(''); + $('#distribution-environment-description').val(''); +// $('#distribution-environment-nextDistributionTargetId').val(''); + } + } + }); + }; + + /** + * Initializes the reporting tasks tab. + */ + var initReportingTasks = function () { + // initialize the new reporting task dialog + initNewReportingTaskDialog(); + + var moreReportingTaskDetails = function (row, cell, value, columnDef, dataContext) { + if (!dataContext.permissions.canRead) { + return ''; + } + + var markup = '<div title="View Details" class="pointer view-reporting-task fa fa-info-circle"></div>'; + + // always include a button to view the usage + markup += '<div title="Usage" class="pointer reporting-task-usage fa fa-book"></div>'; + + var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors); + var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins); + + if (hasErrors) { + markup += '<div class="pointer has-errors fa fa-warning" ></div>'; + } + + if (hasBulletins) { + markup += '<div class="has-bulletins fa fa-sticky-note-o"></div>'; + } + + if (hasErrors || hasBulletins) { + markup += '<span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.component.id) + '</span>'; + } + + return markup; + }; + + var reportingTaskRunStatusFormatter = function (row, cell, value, columnDef, dataContext) { + // determine the appropriate label + var icon = '', label = ''; + if (dataContext.status.validationStatus === 'VALIDATING') { + icon = 'validating fa fa-spin fa-circle-notch'; + label = 'Validating'; + } else if (dataContext.status.validationStatus === 'INVALID') { + icon = 'invalid fa fa-warning'; + label = 'Invalid'; + } else { + if (dataContext.status.runStatus === 'STOPPED') { + label = 'Stopped'; + icon = 'fa fa-stop stopped'; + } else if (dataContext.status.runStatus === 'RUNNING') { + label = 'Running'; + icon = 'fa fa-play running'; + } else { + label = 'Disabled'; + icon = 'icon icon-enable-false disabled'; + } + } + + // include the active thread count if appropriate + var activeThreadCount = ''; + if (nfCommon.isDefinedAndNotNull(dataContext.status.activeThreadCount) && dataContext.status.activeThreadCount > 0) { + activeThreadCount = '(' + dataContext.status.activeThreadCount + ')'; + } + + // format the markup + var formattedValue = '<div layout="row"><div class="' + icon + '"></div>'; + return formattedValue + '<div class="status-text">' + nfCommon.escapeHtml(label) + '</div><div style="float: left; margin-left: 4px;">' + nfCommon.escapeHtml(activeThreadCount) + '</div></div>'; + }; + + var reportingTaskActionFormatter = function (row, cell, value, columnDef, dataContext) { + var markup = ''; + + var canWrite = dataContext.permissions.canWrite; + var canRead = dataContext.permissions.canRead; + var canOperate = dataContext.operatePermissions.canWrite || canWrite; + var isStopped = dataContext.status.runStatus === 'STOPPED'; + + if (dataContext.status.runStatus === 'RUNNING') { + if (canOperate) { + markup += '<div title="Stop" class="pointer stop-reporting-task fa fa-stop"></div>'; + } + + } else if (isStopped || dataContext.status.runStatus === 'DISABLED') { + + if (canRead && canWrite) { + markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil"></div>'; + } + + // support starting when stopped and no validation errors + if (canOperate && dataContext.status.runStatus === 'STOPPED' && dataContext.status.validationStatus === 'VALID') { + markup += '<div title="Start" class="pointer start-reporting-task fa fa-play"></div>'; + } + + if (canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) { + markup += '<div title="Change Version" class="pointer change-version-reporting-task fa fa-exchange"></div>'; + } + + if (canRead && canWrite && nfCommon.canModifyController()) { + markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash"></div>'; + } + } + + if (canRead && canWrite && dataContext.component.persistsState === true) { + markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks"></div>'; + } + + // allow policy configuration conditionally + if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) { + markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>'; + } + + return markup; + }; + + // define the column model for the reporting tasks table + var reportingTasksColumnModel = [ + { + id: 'moreDetails', + name: ' ', + resizable: false, + formatter: moreReportingTaskDetails, + sortable: true, + width: 90, + maxWidth: 90, + toolTip: 'Sorts based on presence of bulletins' + }, + { + id: 'name', + name: 'Name', + sortable: true, + resizable: true, + formatter: nameFormatter + }, + { + id: 'type', + name: 'Type', + formatter: nfCommon.instanceTypeFormatter, + sortable: true, + resizable: true + }, + { + id: 'bundle', + name: 'Bundle', + formatter: nfCommon.instanceBundleFormatter, + sortable: true, + resizable: true + }, + { + id: 'state', + name: 'Run Status', + sortable: true, + resizeable: true, + formatter: reportingTaskRunStatusFormatter + } + ]; + + // action column should always be last + reportingTasksColumnModel.push({ + id: 'actions', + name: ' ', + resizable: false, + formatter: reportingTaskActionFormatter, + sortable: false, + width: 90, + maxWidth: 90 + }); + + // initialize the dataview + var reportingTasksData = new Slick.Data.DataView({ + inlineFilters: false + }); + reportingTasksData.setItems([]); + + // initialize the sort + sort({ + columnId: 'name', + sortAsc: true + }, reportingTasksData); + + // initialize the grid + var reportingTasksGrid = new Slick.Grid('#reporting-tasks-table', reportingTasksData, reportingTasksColumnModel, gridOptions); + reportingTasksGrid.setSelectionModel(new Slick.RowSelectionModel()); + reportingTasksGrid.registerPlugin(new Slick.AutoTooltips()); + reportingTasksGrid.setSortColumn('name', true); + reportingTasksGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.id, + sortAsc: args.sortAsc + }, reportingTasksData); + }); + + // configure a click listener + reportingTasksGrid.onClick.subscribe(function (e, args) { + var target = $(e.target); + + // get the service at this row + var reportingTaskEntity = reportingTasksData.getItem(args.row); + + // determine the desired action + if (reportingTasksGrid.getColumns()[args.cell].id === 'actions') { + if (target.hasClass('edit-reporting-task')) { + nfReportingTask.showConfiguration(reportingTaskEntity); + } else if (target.hasClass('start-reporting-task')) { + nfReportingTask.start(reportingTaskEntity); + } else if (target.hasClass('stop-reporting-task')) { + nfReportingTask.stop(reportingTaskEntity); + } else if (target.hasClass('delete-reporting-task')) { + nfReportingTask.promptToDeleteReportingTask(reportingTaskEntity); + } else if (target.hasClass('view-state-reporting-task')) { + var canClear = reportingTaskEntity.status.runStatus === 'STOPPED' && reportingTaskEntity.status.activeThreadCount === 0; + nfComponentState.showState(reportingTaskEntity, canClear); + } else if (target.hasClass('change-version-reporting-task')) { + nfComponentVersion.promptForVersionChange(reportingTaskEntity); + } else if (target.hasClass('edit-access-policies')) { + // show the policies for this service + nfPolicyManagement.showReportingTaskPolicy(reportingTaskEntity); + + // close the settings dialog + $('#shell-close-button').click(); + } + } else if (reportingTasksGrid.getColumns()[args.cell].id === 'moreDetails') { + if (target.hasClass('view-reporting-task')) { + nfReportingTask.showDetails(reportingTaskEntity); + } else if (target.hasClass('reporting-task-usage')) { + // close the settings dialog + $('#shell-close-button').click(); + + // open the documentation for this reporting task + nfShell.showPage('../nifi-docs/documentation?' + $.param({ + select: reportingTaskEntity.component.type, + group: reportingTaskEntity.component.bundle.group, + artifact: reportingTaskEntity.component.bundle.artifact, + version: reportingTaskEntity.component.bundle.version + })).done(function () { + nfSettings.showSettings(); + }); + } + } + }); + + // wire up the dataview to the grid + reportingTasksData.onRowCountChanged.subscribe(function (e, args) { + reportingTasksGrid.updateRowCount(); + reportingTasksGrid.render(); + }); + reportingTasksData.onRowsChanged.subscribe(function (e, args) { + reportingTasksGrid.invalidateRows(args.rows); + reportingTasksGrid.render(); + }); + reportingTasksData.syncGridSelection(reportingTasksGrid, true); + + // hold onto an instance of the grid + $('#reporting-tasks-table').data('gridInstance', reportingTasksGrid).on('mouseenter', 'div.slick-cell', function (e) { + var errorIcon = $(this).find('div.has-errors'); + if (errorIcon.length && !errorIcon.data('qtip')) { + var taskId = $(this).find('span.row-id').text(); + + // get the task item + var reportingTaskEntity = reportingTasksData.getItemById(taskId); + + // format the errors + var tooltip = nfCommon.formatUnorderedList(reportingTaskEntity.component.validationErrors); + + // show the tooltip + if (nfCommon.isDefinedAndNotNull(tooltip)) { + errorIcon.qtip($.extend({}, + nfCommon.config.tooltipConfig, + { + content: tooltip, + position: { + target: 'mouse', + viewport: $('#shell-container'), + adjust: { + x: 8, + y: 8, + method: 'flipinvert flipinvert' + } + } + })); + } + } + + var bulletinIcon = $(this).find('div.has-bulletins'); + if (bulletinIcon.length && !bulletinIcon.data('qtip')) { + var taskId = $(this).find('span.row-id').text(); + + // get the task item + var reportingTaskEntity = reportingTasksData.getItemById(taskId); + + // format the tooltip + var bulletins = nfCommon.getFormattedBulletins(reportingTaskEntity.bulletins); + var tooltip = nfCommon.formatUnorderedList(bulletins); + + // show the tooltip + if (nfCommon.isDefinedAndNotNull(tooltip)) { + bulletinIcon.qtip($.extend({}, + nfCommon.config.tooltipConfig, + { + content: tooltip, + position: { + target: 'mouse', + viewport: $('#shell-container'), + adjust: { + x: 8, + y: 8, + method: 'flipinvert flipinvert' + } + } + })); + } + } + }); + }; + + + + /** + * Initializing Registry table + */ + var initRegistriesTable = function () { + + var locationFormatter = function (row, cell, value, columnDef, dataContext) { + if (!dataContext.permissions.canRead) { + return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>'; + } + + return nfCommon.escapeHtml(dataContext.component.uri); + }; + + var descriptionFormatter = function (row, cell, value, columnDef, dataContext) { + if (!dataContext.permissions.canRead) { + return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>'; + } + + return nfCommon.escapeHtml(dataContext.component.description); + }; + + var registriesActionFormatter = function (row, cell, value, columnDef, dataContext) { + var markup = ''; + + if (nfCommon.canModifyController()) { + // edit registry + markup += '<div title="Edit" class="pointer edit-registry fa fa-pencil"></div>'; + + // remove registry + markup += '<div title="Remove" class="pointer remove-registry fa fa-trash"></div>'; + } + + return markup; + }; + + // define the column model for the reporting tasks table + var registriesColumnModel = [ + { + id: 'name', + name: 'Name', + field: 'name', + formatter: nameFormatter, + sortable: true, + resizable: true + }, + { + id: 'uri', + name: 'Location', + field: 'uri', + formatter: locationFormatter, + sortable: true, + resizable: true + }, + { + id: 'description', + name: 'Description', + field: 'description', + formatter: descriptionFormatter, + sortable: true, + resizable: true + } + ]; + + // action column should always be last + registriesColumnModel.push({ + id: 'actions', + name: ' ', + resizable: false, + formatter: registriesActionFormatter, + sortable: false, + width: 90, + maxWidth: 90 + }); + + // initialize the dataview + var registriesData = new Slick.Data.DataView({ + inlineFilters: false + }); + registriesData.setItems([]); + + // initialize the sort + sort({ + columnId: 'name', + sortAsc: true + }, registriesData); + + // initialize the grid + var registriesGrid = new Slick.Grid('#registries-table', registriesData, registriesColumnModel, gridOptions); + registriesGrid.setSelectionModel(new Slick.RowSelectionModel()); + registriesGrid.registerPlugin(new Slick.AutoTooltips()); + registriesGrid.setSortColumn('name', true); + registriesGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.id, + sortAsc: args.sortAsc + }, registriesData); + }); + + // configure a click listener + registriesGrid.onClick.subscribe(function (e, args) { + var target = $(e.target); + + // get the service at this row + var registryEntity = registriesData.getItem(args.row); + + // determine the desired action + if (registriesGrid.getColumns()[args.cell].id === 'actions') { + if (target.hasClass('edit-registry')) { + editRegistry(registryEntity); + } else if (target.hasClass('remove-registry')) { + promptToRemoveRegistry(registryEntity); + } + } else if (registriesGrid.getColumns()[args.cell].id === 'moreDetails') { + // if (target.hasClass('view-reporting-task')) { + // nfReportingTask.showDetails(reportingTaskEntity); + // } else if (target.hasClass('reporting-task-usage')) { + // // close the settings dialog + // $('#shell-close-button').click(); + // + // // open the documentation for this reporting task + // nfShell.showPage('../nifi-docs/documentation?' + $.param({ + // select: reportingTaskEntity.component.type, + // group: reportingTaskEntity.component.bundle.group, + // artifact: reportingTaskEntity.component.bundle.artifact, + // version: reportingTaskEntity.component.bundle.version + // })).done(function () { + // nfSettings.showSettings(); + // }); + // } + } + }); + + // wire up the dataview to the grid + registriesData.onRowCountChanged.subscribe(function (e, args) { + registriesGrid.updateRowCount(); + registriesGrid.render(); + }); + registriesData.onRowsChanged.subscribe(function (e, args) { + registriesGrid.invalidateRows(args.rows); + registriesGrid.render(); + }); + registriesData.syncGridSelection(registriesGrid, true); + + // hold onto an instance of the grid + $('#registries-table').data('gridInstance', registriesGrid); + }; + + + + + /** + * Initializing Distribution Environments table + */ + var initDistributionEnvironmentsTable = function () { + + var locationFormatter = function (row, cell, value, columnDef, dataContext) { +// if (!dataContext.permissions.canRead) { + return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>'; +// } + + return nfCommon.escapeHtml(dataContext.component.uri); + }; + + var descriptionFormatter = function (row, cell, value, columnDef, dataContext) { +// if (!dataContext.permissions.canRead) { + return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>'; +// } + + return nfCommon.escapeHtml(dataContext.component.description); + }; + + var envActionFormatter = function (row, cell, value, columnDef, dataContext) { + var markup = ''; + + if (nfCommon.canModifyController()) { + // edit environment + markup += '<div title="Edit" class="pointer edit-registry fa fa-pencil"></div>'; + + // remove environment + markup += '<div title="Remove" class="pointer remove-registry fa fa-trash"></div>'; + } + + return markup; + }; + + // define the column model for the Distribution environments table + var environmentsColumnModel = [ + { + id: 'name', + name: 'Name', + field: 'name', +// formatter: nameFormatter, + sortable: true, + resizable: true + }, + { + id: 'runtimeApiUrl', + name: 'Location', + field: 'runtimeApiUrl', +// formatter: locationFormatter, + sortable: true, + resizable: true + }, + { + id: 'description', + name: 'Description', + field: 'description', +// formatter: descriptionFormatter, + sortable: true, + resizable: true + } +// , +// { +// id: 'nextDistributionTargetId', +// name: 'Next Distribution TargetId', +// field: 'nextDistributionTargetId', +// formatter: descriptionFormatter, +// sortable: true, +// resizable: true +// } + ]; + + // action column should always be last + environmentsColumnModel.push({ + id: 'actions', + name: ' ', + resizable: false, + formatter: envActionFormatter, + sortable: false, + width: 90, + maxWidth: 90 + }); + + // initialize the dataview + var environmentsData = new Slick.Data.DataView({ + inlineFilters: false + }); + environmentsData.setItems([]); + + // initialize the sort + sort({ + columnId: 'name', + sortAsc: true + }, environmentsData); + + // initialize the grid + var environmentsGrid = new Slick.Grid('#distribution-environments-table', environmentsData, environmentsColumnModel, gridOptions); + environmentsGrid.setSelectionModel(new Slick.RowSelectionModel()); + environmentsGrid.registerPlugin(new Slick.AutoTooltips()); + environmentsGrid.setSortColumn('name', true); + environmentsGrid.onSort.subscribe(function (e, args) { + sort({ + columnId: args.sortCol.id, + sortAsc: args.sortAsc + }, environmentsData); + }); + + // configure a click listener + environmentsGrid.onClick.subscribe(function (e, args) { + var target = $(e.target); + + // get the service at this row + var environmentEntity = environmentsData.getItem(args.row); + + // determine the desired action + if (environmentsGrid.getColumns()[args.cell].id === 'actions') { + if (target.hasClass('edit-registry')) { //left it as edit-registry intentionally to inherit styles + editDistributionEnvironment(environmentEntity); + } else if (target.hasClass('remove-registry')) { //left it as remove-registry intentionally to inherit styles + promptToRemoveDistributionEnvironment(environmentEntity); + } + } else if (environmentsGrid.getColumns()[args.cell].id === 'moreDetails') { } + }); + + // wire up the dataview to the grid + environmentsData.onRowCountChanged.subscribe(function (e, args) { + environmentsGrid.updateRowCount(); + environmentsGrid.render(); + }); + environmentsData.onRowsChanged.subscribe(function (e, args) { + environmentsGrid.invalidateRows(args.rows); + environmentsGrid.render(); + }); + environmentsData.syncGridSelection(environmentsGrid, true); + + // hold onto an instance of the grid + $('#distribution-environments-table').data('gridInstance', environmentsGrid); + }; + + + + /** + * Edits the specified registry entity. + * + * @param registryEntity + */ + var editRegistry = function (registryEntity) { + // populate the dialog + $('#registry-id').text(registryEntity.id); + $('#registry-name').val(registryEntity.component.name); + $('#registry-location').val(registryEntity.component.uri); + $('#registry-description').val(registryEntity.component.description); + + // show the dialog + $('#registry-configuration-dialog').modal('setHeaderText', 'Edit Registry Client').modal('setButtonModel', [{ + buttonText: 'Update', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + updateRegistry(registryEntity.id); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + }; + + + /** + * Edits the specified distribution environment entity. + *@author Renu + * @param distributionEnvironmentEntity + */ + var editDistributionEnvironment = function (distributionEnvironmentEntity) { + // populate the dialog + $('#distribution-environment-id').text(distributionEnvironmentEntity.id); + $('#distribution-environment-name').val(distributionEnvironmentEntity.name); + $('#distribution-environment-location').val(distributionEnvironmentEntity.runtimeApiUrl); + $('#distribution-environment-description').val(distributionEnvironmentEntity.description); +// $('#distribution-environment-nextDistributionTargetId ').val(distributionEnvironmentEntity.component.description); + + // show the dialog + $('#distribution-environment-dialog').modal('setHeaderText', 'Edit Environment').modal('setButtonModel', [{ + buttonText: 'Update', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + updateDistributionEnvironment(distributionEnvironmentEntity.id); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + }; + + + /** + * Prompts the user before attempting to delete the specified environment. + * + * @param {object} environmentEntity + */ + var promptToRemoveDistributionEnvironment = function (environmentEntity) { + // prompt for deletion + nfDialog.showYesNoDialog({ + headerText: 'Delete Environment', + dialogContent: 'Delete Environment \'' + nfCommon.escapeHtml(environmentEntity.name) + '\'?', + yesHandler: function () { + removeDistributionEnvironment(environmentEntity); + } + }); + }; + + + /** + * Deletes the specified environment. + * + * @param {object} environmentEntity + */ + var removeDistributionEnvironment = function (environmentEntity) { + console.log(environmentEntity); + $.ajax({ + type: 'DELETE', + url: dcaeDistributorApiHostname+'/distribution-targets/'+environmentEntity.id, + dataType: 'json' + }).done(function (response) { + console.log(response); + // remove the task + var environmentsGrid = $('#distribution-environments-table').data('gridInstance'); + console.log(environmentsGrid); + var environmentsData = environmentsGrid.getData(); + environmentsData.deleteItem(environmentEntity.id); + }).fail(nfErrorHandler.handleAjaxError); + }; + + + /** + * Prompts the user before attempting to delete the specified registry. + * + * @param {object} registryEntity + */ + var promptToRemoveRegistry = function (registryEntity) { + // prompt for deletion + nfDialog.showYesNoDialog({ + headerText: 'Delete Registry', + dialogContent: 'Delete registry \'' + nfCommon.escapeHtml(registryEntity.component.name) + '\'?', + yesHandler: function () { + removeRegistry(registryEntity); + } + }); + }; + + /** + * Deletes the specified registry. + * + * @param {object} registryEntity + */ + var removeRegistry = function (registryEntity) { + var revision = nfClient.getRevision(registryEntity); + $.ajax({ + type: 'DELETE', + url: registryEntity.uri + '?' + $.param({ + 'version': revision.version, + 'clientId': revision.clientId, + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged() + }), + dataType: 'json' + }).done(function (response) { + // remove the task + var registryGrid = $('#registries-table').data('gridInstance'); + var registryData = registryGrid.getData(); + registryData.deleteItem(registryEntity.id); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Loads the settings. + */ + var loadSettings = function () { + var setUnauthorizedText = function () { + $('#read-only-maximum-timer-driven-thread-count-field').addClass('unset').text('Unauthorized'); + $('#read-only-maximum-event-driven-thread-count-field').addClass('unset').text('Unauthorized'); + }; + + var setEditable = function (editable) { + if (editable) { + $('#general-settings div.editable').show(); + $('#general-settings div.read-only').hide(); + $('#settings-save').show(); + } else { + $('#general-settings div.editable').hide(); + $('#general-settings div.read-only').show(); + $('#settings-save').hide(); + } + }; + + var settings = $.Deferred(function (deferred) { + $.ajax({ + type: 'GET', + url: config.urls.controllerConfig, + dataType: 'json' + }).done(function (response) { + if (response.permissions.canWrite) { + // populate the settings + $('#maximum-timer-driven-thread-count-field').removeClass('unset').val(response.component.maxTimerDrivenThreadCount); + $('#maximum-event-driven-thread-count-field').removeClass('unset').val(response.component.maxEventDrivenThreadCount); + + setEditable(true); + + // register the click listener for the save button + $('#settings-save').off('click').on('click', function () { + saveSettings(response.revision.version); + }); + } else { + if (response.permissions.canRead) { + // populate the settings + $('#read-only-maximum-timer-driven-thread-count-field').removeClass('unset').text(response.component.maxTimerDrivenThreadCount); + $('#read-only-maximum-event-driven-thread-count-field').removeClass('unset').text(response.component.maxEventDrivenThreadCount); + } else { + setUnauthorizedText(); + } + + setEditable(false); + } + deferred.resolve(); + }).fail(function (xhr, status, error) { + if (xhr.status === 403) { + setUnauthorizedText(); + setEditable(false); + deferred.resolve(); + } else { + deferred.reject(xhr, status, error); + } + }); + }).promise(); + + // load the controller services + var controllerServicesUri = config.urls.api + '/flow/controller/controller-services'; + var controllerServicesXhr = nfControllerServices.loadControllerServices(controllerServicesUri, getControllerServicesTable()); + + // load the reporting tasks + var reportingTasks = loadReportingTasks(); + + // load the registries + var registries = loadRegistries(); + + // load the distribution environments + var distributionEnvironments = loadDistributionEnvironments(); + + // return a deferred for all parts of the settings + return $.when(settings, controllerServicesXhr, reportingTasks).done(function (settingsResult, controllerServicesResult) { + var controllerServicesResponse = controllerServicesResult[0]; + + // update the current time + $('#settings-last-refreshed').text(controllerServicesResponse.currentTime); + }).fail(nfErrorHandler.handleAjaxError); + }; + + /** + * Loads the reporting tasks. + */ + var loadReportingTasks = function () { + return $.ajax({ + type: 'GET', + url: config.urls.reportingTasks, + dataType: 'json' + }).done(function (response) { + var tasks = []; + $.each(response.reportingTasks, function (_, task) { + tasks.push($.extend({ + type: 'ReportingTask', + bulletins: [] + }, task)); + }); + + var reportingTasksElement = $('#reporting-tasks-table'); + nfCommon.cleanUpTooltips(reportingTasksElement, 'div.has-errors'); + nfCommon.cleanUpTooltips(reportingTasksElement, 'div.has-bulletins'); + + var reportingTasksGrid = reportingTasksElement.data('gridInstance'); + var reportingTasksData = reportingTasksGrid.getData(); + + // update the reporting tasks + reportingTasksData.setItems(tasks); + reportingTasksData.reSort(); + reportingTasksGrid.invalidate(); + }); + }; + + /** + * Loads the registries. + */ + var loadRegistries = function () { + return $.ajax({ + type: 'GET', + url: config.urls.registries, + dataType: 'json' + }).done(function (response) { + var registries = []; + $.each(response.registries, function (_, registryEntity) { + registries.push($.extend({ + type: 'Registry' + }, registryEntity)); + }); + + var registriesGrid = $('#registries-table').data('gridInstance'); + var registriesData = registriesGrid.getData(); + + // update the registries + registriesData.setItems(registries); + registriesData.reSort(); + registriesGrid.invalidate(); + }); + }; + + /** + * Loads the distribution environments. + */ + var loadDistributionEnvironments = function(){ + console.log("in loadDistributionEnvironments.. "); + return $.ajax({ + type: 'GET', + url: dcaeDistributorApiHostname+'/distribution-targets', + dataType: 'json' + }).done(function (response) { + console.log(response); + var environments = []; + $.each(response.distributionTargets, function (_, environmentEntity) { + console.log(environmentEntity); + environments.push($.extend({ + type: 'Environment' + }, environmentEntity)); + }); + + console.log(environments); + var environmentsGrid = $('#distribution-environments-table').data('gridInstance'); + console.log(environmentsGrid); + var environmentsData = environmentsGrid.getData(); + + // update the distribution environments + environmentsData.setItems(environments); + environmentsData.reSort(); + environmentsGrid.invalidate(); + }); + }; + + /** + * Shows the process group configuration. + */ + var showSettings = function () { + // show the settings dialog + nfShell.showContent('#settings').done(function () { + reset(); + }); + + //reset content to account for possible policy changes + $('#settings-tabs').find('.selected-tab').click(); + + // adjust the table size + nfSettings.resetTableSize(); + }; + + /** + * Reset state of this dialog. + */ + var reset = function () { + // reset button state + $('#settings-save').mouseout(); + }; + + var nfSettings = { + /** + * Initializes the settings page. + */ + init: function () { + // initialize the settings tabs + $('#settings-tabs').tabbs({ + tabStyle: 'tab', + selectedTabStyle: 'selected-tab', + scrollableTabContentStyle: 'scrollable', + tabs: [{ + name: 'General', + tabContentId: 'general-settings-tab-content' + }, { + name: 'Reporting Task Controller Services', + tabContentId: 'controller-services-tab-content' + }, { + name: 'Reporting Tasks', + tabContentId: 'reporting-tasks-tab-content' + }, { + name: 'Registry Clients', + tabContentId: 'registries-tab-content' + },{ + name: 'Distribution Target Environments', + tabContentId: 'distribution-environment-content' + } + ], + select: function () { + var tab = $(this).text(); + if (tab === 'General') { + $('#controller-cs-availability').hide(); + $('#new-service-or-task').hide(); + $('#settings-save').show(); + } else { + var canModifyController = false; + if (nfCommon.isDefinedAndNotNull(nfCommon.currentUser)) { + // only consider write permissions for creating new controller services/reporting tasks + canModifyController = nfCommon.currentUser.controllerPermissions.canWrite === true; + } + + if (canModifyController) { + $('#new-service-or-task').show(); + $('div.controller-settings-table').css('top', '32px'); + + // update the tooltip on the button + $('#new-service-or-task').attr('title', function () { + if (tab === 'Reporting Task Controller Services') { + $('#settings-save').hide(); + return 'Create a new reporting task controller service'; + } else if (tab === 'Reporting Tasks') { + $('#settings-save').hide(); + return 'Create a new reporting task'; + } else if (tab === 'Registry Clients') { + $('#settings-save').hide(); + return 'Register a new registry client'; + }else if (tab === 'Distribution Target Environments') { + console.log("in env tab..."); + $('#settings-save').hide(); + return 'Add a new distribution environment'; + } + }); + } else { + $('#new-service-or-task').hide(); + $('div.controller-settings-table').css('top', '0'); + } + + if (tab === 'Reporting Task Controller Services') { + $('#controller-cs-availability').show(); + } else if (tab === 'Reporting Tasks' || tab === 'Registry Clients'|| tab === 'Distribution Target Environments') { + $('#controller-cs-availability').hide(); + } + + // resize the table + nfSettings.resetTableSize(); + } + } + }); + + // settings refresh button + $('#settings-refresh-button').click(function () { + loadSettings(); + }); + + // create a new controller service or reporting task + $('#new-service-or-task').on('click', function () { + console.log("on Add Buttton clicked"); + var selectedTab = $('#settings-tabs li.selected-tab').text(); + if (selectedTab === 'Reporting Task Controller Services') { + var controllerServicesUri = config.urls.api + '/controller/controller-services'; + nfControllerServices.promptNewControllerService(controllerServicesUri, getControllerServicesTable()); + } else if (selectedTab === 'Reporting Tasks') { + $('#new-reporting-task-dialog').modal('show'); + + var reportingTaskTypesGrid = $('#reporting-task-types-table').data('gridInstance'); + if (nfCommon.isDefinedAndNotNull(reportingTaskTypesGrid)) { + var reportingTaskTypesData = reportingTaskTypesGrid.getData(); + + // reset the canvas size after the dialog is shown + reportingTaskTypesGrid.resizeCanvas(); + + // select the first row if possible + if (reportingTaskTypesData.getLength() > 0) { + nfFilteredDialogCommon.choseFirstRow(reportingTaskTypesGrid); + } + } + + // set the initial focus + $('#reporting-task-type-filter').focus(); + } else if (selectedTab === 'Registry Clients') { + $('#registry-configuration-dialog').modal('setHeaderText', 'Add Registry Client').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + addRegistry(); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + + // set the initial focus + $('#registry-name').focus(); + } else if (selectedTab === 'Distribution Target Environments') { + $('#distribution-environment-dialog').modal('setHeaderText', 'Add New Environment').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + addDistributionEnvironment(); + } + } + }, { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $(this).modal('hide'); + } + } + }]).modal('show'); + + $(window).resize(function() { + var x=0; + console.log(x); + $("#distribution-environment-content").text(x= x + 1); + }); + + $(window).resize(function() { + var x=0; + console.log(x); + $("#distribution-environments-table").text(x= x + 1); + console.log(x); + console.log("resizing...table..."); + }); + // set the initial focus + $('#distribution-environment-name').focus(); + } + }); + + // initialize each tab + initGeneral(); + nfControllerServices.init(getControllerServicesTable(), nfSettings.showSettings); + initReportingTasks(); + initRegistriesTable(); + initDistributionEnvironmentsTable(); + }, + + /** + * Update the size of the grid based on its container's current size. + */ + resetTableSize: function () { + nfControllerServices.resetTableSize(getControllerServicesTable()); + nfControllerServices.resetTableSize(getDistributionEnvironmentsTable()); + + var reportingTasksGrid = $('#reporting-tasks-table').data('gridInstance'); + if (nfCommon.isDefinedAndNotNull(reportingTasksGrid)) { + reportingTasksGrid.resizeCanvas(); + } + }, + + /** + * Shows the settings dialog. + */ + showSettings: function () { + return loadSettings().done(showSettings); + }, + + /** + * Loads the settings dialogs. + */ + loadSettings: function () { + return loadSettings(); + }, + + /** + * Selects the specified controller service. + * + * @param {string} controllerServiceId + */ + selectControllerService: function (controllerServiceId) { + var controllerServiceGrid = getControllerServicesTable().data('gridInstance'); + var controllerServiceData = controllerServiceGrid.getData(); + + // select the desired service + var row = controllerServiceData.getRowById(controllerServiceId); + nfFilteredDialogCommon.choseRow(controllerServiceGrid, row); + controllerServiceGrid.scrollRowIntoView(row); + + // select the controller services tab + $('#settings-tabs').find('li:eq(1)').click(); + }, + + /** + * Selects the specified reporting task. + * + * @param {string} reportingTaskId + */ + selectReportingTask: function (reportingTaskId) { + var reportingTaskGrid = $('#reporting-tasks-table').data('gridInstance'); + var reportingTaskData = reportingTaskGrid.getData(); + + // select the desired service + var row = reportingTaskData.getRowById(reportingTaskId); + nfFilteredDialogCommon.choseRow(reportingTaskGrid, row); + reportingTaskGrid.scrollRowIntoView(row); + + // select the controller services tab + $('#settings-tabs').find('li:eq(2)').click(); + }, + + /** + * Sets the controller service and reporting task bulletins in their respective tables. + * + * @param {object} controllerServiceBulletins + * @param {object} reportingTaskBulletins + */ + setBulletins: function (controllerServiceBulletins, reportingTaskBulletins) { + if ($('#controller-services-table').data('gridInstance')) { + nfControllerServices.setBulletins(getControllerServicesTable(), controllerServiceBulletins); + } + + // reporting tasks + var reportingTasksGrid = $('#reporting-tasks-table').data('gridInstance'); + var reportingTasksData = reportingTasksGrid.getData(); + reportingTasksData.beginUpdate(); + + // if there are some bulletins process them + if (!nfCommon.isEmpty(reportingTaskBulletins)) { + var reportingTaskBulletinsBySource = d3.nest() + .key(function (d) { + return d.sourceId; + }) + .map(reportingTaskBulletins, d3.map); + + reportingTaskBulletinsBySource.each(function (sourceBulletins, sourceId) { + var reportingTask = reportingTasksData.getItemById(sourceId); + if (nfCommon.isDefinedAndNotNull(reportingTask)) { + reportingTasksData.updateItem(sourceId, $.extend(reportingTask, { + bulletins: sourceBulletins + })); + } + }); + } else { + // if there are no bulletins clear all + var reportingTasks = reportingTasksData.getItems(); + $.each(reportingTasks, function (_, reportingTask) { + reportingTasksData.updateItem(reportingTask.id, $.extend(reportingTask, { + bulletins: [] + })); + }); + } + reportingTasksData.endUpdate(); + } + }; + + return nfSettings; +})); |