| /* |
| * 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. |
| */ |
| |
| /* global define, module, require, exports */ |
| |
| (function (root, factory) { |
| if (typeof define === 'function' && define.amd) { |
| define(['jquery', |
| 'd3', |
| 'nf.Common', |
| 'nf.Dialog', |
| 'nf.Graph', |
| 'nf.Shell', |
| 'nf.ng.Bridge', |
| 'nf.ClusterSummary', |
| 'nf.ErrorHandler', |
| 'nf.AuthorizationStorage', |
| 'nf.Storage', |
| 'nf.CanvasUtils', |
| 'nf.Birdseye', |
| 'nf.ContextMenu', |
| 'nf.Actions', |
| 'nf.ProcessGroup', |
| 'nf.ParameterContexts'], |
| function ($, d3, nfCommon, nfDialog, nfGraph, nfShell, nfNgBridge, nfClusterSummary, nfErrorHandler, nfAuthorizationStorage, nfStorage, nfCanvasUtils, nfBirdseye, nfContextMenu, nfActions, nfProcessGroup, nfParameterContexts) { |
| return (nf.Canvas = factory($, d3, nfCommon, nfDialog, nfGraph, nfShell, nfNgBridge, nfClusterSummary, nfErrorHandler, nfAuthorizationStorage, nfStorage, nfCanvasUtils, nfBirdseye, nfContextMenu, nfActions, nfProcessGroup, nfParameterContexts)); |
| }); |
| } else if (typeof exports === 'object' && typeof module === 'object') { |
| module.exports = (nf.Canvas = |
| factory(require('jquery'), |
| require('d3'), |
| require('nf.Common'), |
| require('nf.Dialog'), |
| require('nf.Graph'), |
| require('nf.Shell'), |
| require('nf.ng.Bridge'), |
| require('nf.ClusterSummary'), |
| require('nf.ErrorHandler'), |
| require('nf.AuthorizationStorage'), |
| require('nf.Storage'), |
| require('nf.CanvasUtils'), |
| require('nf.Birdseye'), |
| require('nf.ContextMenu'), |
| require('nf.Actions'), |
| require('nf.ProcessGroup'), |
| require('nf.ParameterContexts'))); |
| } else { |
| nf.Canvas = factory(root.$, |
| root.d3, |
| root.nf.Common, |
| root.nf.Dialog, |
| root.nf.Graph, |
| root.nf.Shell, |
| root.nf.ng.Bridge, |
| root.nf.ClusterSummary, |
| root.nf.ErrorHandler, |
| root.nf.AuthorizationStorage, |
| root.nf.Storage, |
| root.nf.CanvasUtils, |
| root.nf.Birdseye, |
| root.nf.ContextMenu, |
| root.nf.Actions, |
| root.nf.ProcessGroup, |
| root.nf.ParameterContexts); |
| } |
| }(this, function ($, d3, nfCommon, nfDialog, nfGraph, nfShell, nfNgBridge, nfClusterSummary, nfErrorHandler, nfAuthorizationStorage, nfStorage, nfCanvasUtils, nfBirdseye, nfContextMenu, nfActions, nfProcessGroup, nfParameterContexts) { |
| 'use strict'; |
| |
| var SCALE = 1; |
| var TRANSLATE = [0, 0]; |
| var INCREMENT = 1.2; |
| var MAX_SCALE = 8; |
| var MIN_SCALE = 0.2; |
| var MIN_SCALE_TO_RENDER = 0.6; |
| |
| var DEFAULT_PAGE_TITLE = ''; |
| var BANNER_TEXT = ''; |
| |
| var polling = false; |
| var allowPageRefresh = false; |
| var groupId = 'root'; |
| var parameterContext; |
| var groupName = null; |
| var permissions = null; |
| var parentGroupId = null; |
| var managedAuthorizer = false; |
| var configurableAuthorizer = false; |
| var configurableUsersAndGroups = false; |
| var svg = null; |
| var canvas = null; |
| |
| var canvasClicked = false; |
| |
| var config = { |
| urls: { |
| api: '../nifi-api', |
| accessConfig: '../nifi-api/access/config', |
| currentUser: '../nifi-api/flow/current-user', |
| controllerBulletins: '../nifi-api/flow/controller/bulletins', |
| kerberos: '../nifi-api/access/kerberos', |
| oidc: '../nifi-api/access/oidc/exchange', |
| saml: '../nifi-api/access/saml/login/exchange', |
| revision: '../nifi-api/flow/revision', |
| banners: '../nifi-api/flow/banners' |
| } |
| }; |
| |
| /** |
| * Register the poller. |
| * |
| * @argument {int} autoRefreshInterval The auto refresh interval |
| */ |
| var poll = function (autoRefreshInterval) { |
| // ensure we're suppose to poll |
| if (polling) { |
| // reload the status |
| nfCanvas.reload({ |
| 'transition': true |
| }).done(function () { |
| // start the wait to poll again |
| setTimeout(function () { |
| poll(autoRefreshInterval); |
| }, autoRefreshInterval * 1000); |
| }); |
| } |
| }; |
| |
| /** |
| * Refreshes the graph. |
| * |
| * @argument {object} options Configuration options |
| */ |
| var reloadProcessGroup = function (options) { |
| var now = new Date().getTime(); |
| var processGroupId = nfCanvas.getGroupId(); |
| |
| // load the controller |
| return $.ajax({ |
| type: 'GET', |
| url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupId), |
| dataType: 'json', |
| data: { |
| uiOnly: true |
| } |
| }).done(function (flowResponse) { |
| // get the controller and its contents |
| var processGroupFlow = flowResponse.processGroupFlow; |
| |
| // set the group and parameter context details |
| nfCanvas.setGroupId(processGroupFlow.id); |
| nfCanvas.setParameterContext(processGroupFlow.parameterContext); |
| |
| // get the current group name from the breadcrumb |
| var breadcrumb = processGroupFlow.breadcrumb; |
| if (breadcrumb.permissions.canRead) { |
| nfCanvas.setGroupName(breadcrumb.breadcrumb.name); |
| } else { |
| nfCanvas.setGroupName(breadcrumb.id); |
| } |
| |
| // update the access policies |
| permissions = flowResponse.permissions; |
| |
| // update the breadcrumbs |
| nfNgBridge.injector.get('breadcrumbsCtrl').resetBreadcrumbs(); |
| // inform Angular app values have changed |
| nfNgBridge.digest(); |
| nfNgBridge.injector.get('breadcrumbsCtrl').generateBreadcrumbs(breadcrumb); |
| nfNgBridge.injector.get('breadcrumbsCtrl').resetScrollPosition(); |
| |
| // set page title to the name of the root processor group |
| var rootBreadcrumb = breadcrumb; |
| while(rootBreadcrumb.parentBreadcrumb != null) { |
| rootBreadcrumb = rootBreadcrumb.parentBreadcrumb |
| } |
| |
| if(DEFAULT_PAGE_TITLE == ''){ |
| DEFAULT_PAGE_TITLE = document.title; |
| } |
| |
| if(rootBreadcrumb.permissions.canRead){ |
| document.title = rootBreadcrumb.breadcrumb.name + BANNER_TEXT; |
| } else { |
| document.title = DEFAULT_PAGE_TITLE + BANNER_TEXT; |
| } |
| |
| // update the timestamp |
| $('#stats-last-refreshed').text(processGroupFlow.lastRefreshed); |
| |
| // set the parent id if applicable |
| if (nfCommon.isDefinedAndNotNull(processGroupFlow.parentGroupId)) { |
| nfCanvas.setParentGroupId(processGroupFlow.parentGroupId); |
| } else { |
| nfCanvas.setParentGroupId(null); |
| } |
| |
| // refresh the graph |
| nfGraph.expireCaches(now); |
| nfGraph.set(processGroupFlow.flow, $.extend({ |
| 'selectAll': false, |
| 'overrideRevisionCheck': nfClusterSummary.didConnectedStateChange() |
| }, options)); |
| |
| // update component visibility |
| nfGraph.updateVisibility(); |
| |
| // update the birdseye |
| nfBirdseye.refresh(); |
| }).fail(nfErrorHandler.handleAjaxError); |
| }; |
| |
| /** |
| * Reloads the flow from the server based on the specified group id. |
| * |
| * @param processGroupId Id of the Process Group to load |
| * @param options |
| */ |
| var changeProcessGroup = function (processGroupId, options) { |
| // capture the current group id to reset to in case of failure |
| var currentProcessGroup = nfCanvas.getGroupId(); |
| var currentParameterContext = nfCanvas.getParameterContext(); |
| |
| // clear caches because what is in the cache may not be applicable and we don't want the caches to grow indefinitely. |
| nfCanvasUtils.clearEllipsisCache(); |
| |
| // update process group id and attempt to reload |
| nfCanvas.setGroupId(processGroupId); |
| var processGroupXhr = reloadProcessGroup(options); |
| |
| // if the request fails, ensure the process group id and parameter context id is reset |
| processGroupXhr |
| .fail(function (xhr, status, error) { |
| nfCanvas.setGroupId(currentProcessGroup); |
| nfCanvas.setParameterContext(currentParameterContext); |
| }); |
| |
| return processGroupXhr; |
| }; |
| |
| |
| /** |
| * Loads the current user and updates the current user locally. |
| * |
| * @returns xhr |
| */ |
| var loadCurrentUser = function () { |
| // get the current user |
| return $.ajax({ |
| type: 'GET', |
| url: config.urls.currentUser, |
| dataType: 'json' |
| }).done(function (currentUser) { |
| // set the current user |
| nfCommon.setCurrentUser(currentUser); |
| }); |
| }; |
| |
| var nfCanvas = { |
| CANVAS_OFFSET: 0, |
| |
| /** |
| * Determines if the current broswer supports SVG. |
| */ |
| SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, |
| |
| /** |
| * Hides the splash that is displayed while the application is loading. |
| */ |
| hideSplash: function () { |
| $('#splash').fadeOut(); |
| }, |
| |
| /** |
| * Remove the status poller. |
| */ |
| stopPolling: function () { |
| // set polling flag |
| polling = false; |
| }, |
| |
| /** |
| * Disable the canvas refresh hot key. |
| */ |
| disableRefreshHotKey: function () { |
| allowPageRefresh = true; |
| }, |
| |
| /** |
| * Reloads the flow from the server based on the currently specified group id. |
| * To load another group, set the groupId parameter otherwise the current group |
| * will be reloaded. |
| */ |
| reload: function (options, groupId) { |
| return $.Deferred(function (deferred) { |
| // issue the requests |
| |
| var processGroupXhr; |
| if (nfCommon.isDefinedAndNotNull(groupId)) { |
| processGroupXhr = changeProcessGroup(groupId, options); |
| } else { |
| processGroupXhr = reloadProcessGroup(options); |
| } |
| |
| var statusXhr = nfNgBridge.injector.get('flowStatusCtrl').reloadFlowStatus(); |
| var currentUserXhr = loadCurrentUser(); |
| var controllerBulletins = $.ajax({ |
| type: 'GET', |
| url: config.urls.controllerBulletins, |
| dataType: 'json' |
| }).done(function (response) { |
| nfNgBridge.injector.get('flowStatusCtrl').updateBulletins(response); |
| }); |
| var clusterSummary = nfClusterSummary.loadClusterSummary().done(function (response) { |
| var clusterSummary = response.clusterSummary; |
| |
| // see if this node has been (dis)connected |
| if (nfClusterSummary.didConnectedStateChange()) { |
| if (clusterSummary.connectedToCluster) { |
| nfDialog.showConnectedToClusterMessage(function () { |
| nfStorage.resetDisconnectionAcknowledgement(); |
| }); |
| } else { |
| nfDialog.showDisconnectedFromClusterMessage(function () { |
| nfStorage.acknowledgeDisconnection(); |
| }); |
| } |
| } |
| |
| // update the cluster summary |
| nfNgBridge.injector.get('flowStatusCtrl').updateClusterSummary(clusterSummary); |
| }); |
| |
| // wait for all requests to complete |
| $.when(processGroupXhr, statusXhr, currentUserXhr, controllerBulletins, clusterSummary).done(function (processGroupResult) { |
| var processGroupResponse = processGroupResult[0]; |
| |
| // inform Angular app values have changed |
| nfNgBridge.digest(); |
| |
| // resolve the deferred |
| deferred.resolve(processGroupResponse); |
| }).fail(function (xhr, status, error) { |
| deferred.reject(xhr, status, error); |
| }); |
| }).promise(); |
| }, |
| |
| /** |
| * Initializes the canvas. |
| */ |
| initCanvas: function () { |
| var canvasContainer = $('#canvas-container'); |
| |
| // create the canvas |
| svg = d3.select('#canvas-container').append('svg') |
| .on('contextmenu', function () { |
| // reset the canvas click flag |
| canvasClicked = false; |
| |
| // since the context menu event propagated back to the canvas, clear the selection |
| nfCanvasUtils.getSelection().classed('selected', false); |
| |
| // update URL deep linking params |
| nfCanvasUtils.setURLParameters(); |
| |
| // show the context menu on the canvas |
| nfContextMenu.show(); |
| |
| // prevent default browser behavior |
| d3.event.preventDefault(); |
| }); |
| |
| // create the definitions element |
| var defs = svg.append('defs'); |
| |
| // create arrow definitions for the various line types |
| defs.selectAll('marker') |
| .data(['normal', 'ghost', 'unauthorized', 'full']) |
| .enter().append('marker') |
| .attrs({ |
| 'id': function (d) { |
| return d; |
| }, |
| 'viewBox': '0 0 6 6', |
| 'refX': 5, |
| 'refY': 3, |
| 'markerWidth': 6, |
| 'markerHeight': 6, |
| 'orient': 'auto', |
| 'fill': function (d) { |
| if (d === 'ghost') { |
| return '#aaaaaa'; |
| } else if (d === 'unauthorized') { |
| return '#ba554a'; |
| } else if (d === 'full') { |
| return '#ba554a'; |
| } else { |
| return '#000000'; |
| } |
| } |
| }) |
| .append('path') |
| .attr('d', 'M2,3 L0,6 L6,3 L0,0 z'); |
| |
| // filter for drop shadow |
| var componentDropShadowFilter = defs.append('filter') |
| .attrs({ |
| 'id': 'component-drop-shadow', |
| 'height': '140%', |
| 'y': '-20%' |
| }); |
| |
| // blur |
| componentDropShadowFilter.append('feGaussianBlur') |
| .attrs({ |
| 'in': 'SourceAlpha', |
| 'stdDeviation': 3, |
| 'result': 'blur' |
| }); |
| |
| // offset |
| componentDropShadowFilter.append('feOffset') |
| .attrs({ |
| 'in': 'blur', |
| 'dx': 0, |
| 'dy': 1, |
| 'result': 'offsetBlur' |
| }); |
| |
| // color/opacity |
| componentDropShadowFilter.append('feFlood') |
| .attrs({ |
| 'flood-color': '#000000', |
| 'flood-opacity': 0.4, |
| 'result': 'offsetColor' |
| }); |
| |
| // combine |
| componentDropShadowFilter.append('feComposite') |
| .attrs({ |
| 'in': 'offsetColor', |
| 'in2': 'offsetBlur', |
| 'operator': 'in', |
| 'result': 'offsetColorBlur' |
| }); |
| |
| // stack the effect under the source graph |
| var componentDropShadowFeMerge = componentDropShadowFilter.append('feMerge'); |
| componentDropShadowFeMerge.append('feMergeNode') |
| .attr('in', 'offsetColorBlur'); |
| componentDropShadowFeMerge.append('feMergeNode') |
| .attr('in', 'SourceGraphic'); |
| |
| // filter for drop shadow |
| var connectionFullDropShadowFilter = defs.append('filter') |
| .attrs({ |
| 'id': 'connection-full-drop-shadow', |
| 'height': '140%', |
| 'y': '-20%' |
| }); |
| |
| // blur |
| connectionFullDropShadowFilter.append('feGaussianBlur') |
| .attrs({ |
| 'in': 'SourceAlpha', |
| 'stdDeviation': 3, |
| 'result': 'blur' |
| }); |
| |
| // offset |
| connectionFullDropShadowFilter.append('feOffset') |
| .attrs({ |
| 'in': 'blur', |
| 'dx': 0, |
| 'dy': 1, |
| 'result': 'offsetBlur' |
| }); |
| |
| // color/opacity |
| connectionFullDropShadowFilter.append('feFlood') |
| .attrs({ |
| 'flood-color': '#ba554a', |
| 'flood-opacity': 1, |
| 'result': 'offsetColor' |
| }); |
| |
| // combine |
| connectionFullDropShadowFilter.append('feComposite') |
| .attrs({ |
| 'in': 'offsetColor', |
| 'in2': 'offsetBlur', |
| 'operator': 'in', |
| 'result': 'offsetColorBlur' |
| }); |
| |
| // stack the effect under the source graph |
| var connectionFullFeMerge = connectionFullDropShadowFilter.append('feMerge'); |
| connectionFullFeMerge.append('feMergeNode') |
| .attr('in', 'offsetColorBlur'); |
| connectionFullFeMerge.append('feMergeNode') |
| .attr('in', 'SourceGraphic'); |
| |
| // create the canvas element |
| canvas = svg.append('g') |
| .attrs({ |
| 'transform': 'translate(' + TRANSLATE + ') scale(' + SCALE + ')', |
| 'pointer-events': 'all', |
| 'id': 'canvas' |
| }); |
| |
| // handle canvas events |
| svg.on('mousedown.selection', function () { |
| canvasClicked = true; |
| |
| if (d3.event.button !== 0) { |
| // prevent further propagation (to parents and others handlers |
| // on the same element to prevent zoom behavior) |
| d3.event.stopImmediatePropagation(); |
| return; |
| } |
| |
| // show selection box if shift is held down |
| if (d3.event.shiftKey) { |
| var position = d3.mouse(canvas.node()); |
| canvas.append('rect') |
| .attr('rx', 6) |
| .attr('ry', 6) |
| .attr('x', position[0]) |
| .attr('y', position[1]) |
| .attr('class', 'component-selection') |
| .attr('width', 0) |
| .attr('height', 0) |
| .attr('stroke-width', function () { |
| return 1 / nfCanvas.View.getScale(); |
| }) |
| .attr('stroke-dasharray', function () { |
| return 4 / nfCanvas.View.getScale(); |
| }) |
| .datum(position); |
| |
| // prevent further propagation (to parents and others handlers |
| // on the same element to prevent zoom behavior) |
| d3.event.stopImmediatePropagation(); |
| |
| // prevents the browser from changing to a text selection cursor |
| d3.event.preventDefault(); |
| } |
| }) |
| .on('mousemove.selection', function () { |
| // update selection box if shift is held down |
| if (d3.event.shiftKey) { |
| // get the selection box |
| var selectionBox = d3.select('rect.component-selection'); |
| if (!selectionBox.empty()) { |
| // get the original position |
| var originalPosition = selectionBox.datum(); |
| var position = d3.mouse(canvas.node()); |
| |
| var d = {}; |
| if (originalPosition[0] < position[0]) { |
| d.x = originalPosition[0]; |
| d.width = position[0] - originalPosition[0]; |
| } else { |
| d.x = position[0]; |
| d.width = originalPosition[0] - position[0]; |
| } |
| |
| if (originalPosition[1] < position[1]) { |
| d.y = originalPosition[1]; |
| d.height = position[1] - originalPosition[1]; |
| } else { |
| d.y = position[1]; |
| d.height = originalPosition[1] - position[1]; |
| } |
| |
| // update the selection box |
| selectionBox.attrs(d); |
| |
| // prevent further propagation (to parents) |
| d3.event.stopPropagation(); |
| } |
| } |
| }) |
| .on('mouseup.selection', function () { |
| // ensure this originated from clicking the canvas, not a component. |
| // when clicking on a component, the event propagation is stopped so |
| // it never reaches the canvas. we cannot do this however on up events |
| // since the drag events break down |
| if (canvasClicked === false) { |
| return; |
| } |
| |
| // reset the canvas click flag |
| canvasClicked = false; |
| |
| // get the selection box |
| var selectionBox = d3.select('rect.component-selection'); |
| if (!selectionBox.empty()) { |
| var selectionBoundingBox = { |
| x: parseInt(selectionBox.attr('x'), 10), |
| y: parseInt(selectionBox.attr('y'), 10), |
| width: parseInt(selectionBox.attr('width'), 10), |
| height: parseInt(selectionBox.attr('height'), 10) |
| }; |
| |
| // see if a component should be selected or not |
| d3.selectAll('g.component').classed('selected', function (d) { |
| // consider it selected if its already selected or enclosed in the bounding box |
| return d3.select(this).classed('selected') || |
| d.position.x >= selectionBoundingBox.x && (d.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) && |
| d.position.y >= selectionBoundingBox.y && (d.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height); |
| }); |
| |
| // see if a connection should be selected or not |
| d3.selectAll('g.connection').classed('selected', function (d) { |
| // consider all points |
| var points = [d.start].concat(d.bends, [d.end]); |
| |
| // determine the bounding box |
| var x = d3.extent(points, function (pt) { |
| return pt.x; |
| }); |
| var y = d3.extent(points, function (pt) { |
| return pt.y; |
| }); |
| |
| // consider it selected if its already selected or enclosed in the bounding box |
| return d3.select(this).classed('selected') || |
| x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) && |
| y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height); |
| }); |
| |
| // remove the selection box |
| selectionBox.remove(); |
| |
| // update URL deep linking params |
| nfCanvasUtils.setURLParameters(); |
| } |
| |
| // inform Angular app values have changed |
| nfNgBridge.digest(); |
| }); |
| |
| // define a function for update the graph dimensions |
| var updateGraphSize = function () { |
| // get the location of the bottom of the graph |
| var footer = $('#banner-footer'); |
| var bottom = 0; |
| if (footer.is(':visible')) { |
| bottom = footer.height(); |
| } |
| |
| // calculate size |
| var top = parseInt(canvasContainer.css('top'), 10); |
| var windowHeight = $(window).height(); |
| var canvasHeight = (windowHeight - (bottom + top)); |
| |
| // canvas/svg |
| canvasContainer.css({ |
| 'height': canvasHeight + 'px', |
| 'bottom': bottom + 'px' |
| }); |
| svg.attrs({ |
| 'height': canvasContainer.height(), |
| 'width': $(window).width() |
| }); |
| |
| //breadcrumbs |
| nfNgBridge.injector.get('breadcrumbsCtrl').updateBreadcrumbsCss({'bottom': bottom + 'px'}); |
| |
| // body |
| $('#canvas-body').css({ |
| 'height': windowHeight + 'px', |
| 'width': $(window).width() + 'px' |
| }); |
| }; |
| |
| // listen for events to go to components |
| $('body').on('GoTo:Component', function (e, item) { |
| nfCanvasUtils.showComponent(item.parentGroupId, item.id); |
| }); |
| |
| // listen for events to go to process groups |
| $('body').on('GoTo:ProcessGroup', function (e, item) { |
| nfProcessGroup.enterGroup(item.id).done(function () { |
| nfCanvasUtils.getSelection().classed('selected', false); |
| |
| // inform Angular app that values have changed |
| nfNgBridge.digest(); |
| }); |
| }); |
| |
| // listen for events to go to parameter contexts |
| $('body').on('GoTo:ParameterContext', function (e, item) { |
| nfParameterContexts.showParameterContexts(item.id); |
| }); |
| |
| // don't let the reload action get called more than once every second |
| var throttledCanvasReload = nfCommon.throttle(nfActions.reload, 1000); |
| |
| // listen for browser resize events to reset the graph size |
| $(window).on('resize', function (e) { |
| if (e.target === window) { |
| // close the hamburger menu if open |
| if($('.md-menu-backdrop').is(':visible') === true){ |
| $('.md-menu-backdrop').click(); |
| } |
| |
| updateGraphSize(); |
| |
| // resize shell when appropriate |
| var shell = $('#shell-dialog'); |
| if (shell.is(':visible')){ |
| setTimeout(function(shell){ |
| nfShell.resizeContent(shell); |
| if(shell.find('#shell-iframe').is(':visible')) { |
| nfShell.resizeIframe(shell); |
| } |
| }, 50, shell); |
| } |
| |
| // resize dialogs when appropriate |
| var dialogs = $('.dialog'); |
| for (var i = 0, len = dialogs.length; i < len; i++) { |
| if ($(dialogs[i]).is(':visible')){ |
| setTimeout(function(dialog){ |
| dialog.modal('resize'); |
| }, 50, $(dialogs[i])); |
| } |
| } |
| |
| // resize grids when appropriate |
| var gridElements = $('*[class*="slickgrid_"]'); |
| for (var j = 0, len = gridElements.length; j < len; j++) { |
| if ($(gridElements[j]).is(':visible')) { |
| setTimeout(function(gridElement) { |
| var grid = gridElement.data('gridInstance'); |
| var editorLock = grid.getEditorLock(); |
| if (!editorLock.isActive()) { |
| grid.resizeCanvas(); |
| } |
| }, 50, $(gridElements[j])); |
| } |
| } |
| |
| // toggle tabs .scrollable when appropriate |
| var tabsContainers = $('.tab-container'); |
| var tabsContents = []; |
| for (var k = 0, len = tabsContainers.length; k < len; k++) { |
| if ($(tabsContainers[k]).is(':visible')){ |
| tabsContents.push($('#' + $(tabsContainers[k]).attr('id') + '-content')); |
| } |
| } |
| $.each(tabsContents, function (index, tabsContent) { |
| nfCommon.toggleScrollable(tabsContent.get(0)); |
| }); |
| } |
| }).on('keydown', function (evt) { |
| // if a dialog is open, disable canvas shortcuts |
| if ($('.dialog').is(':visible') || $('#search-field').is(':focus')) { |
| return; |
| } |
| |
| // get the current selection |
| var selection = nfCanvasUtils.getSelection(); |
| |
| // handle shortcuts |
| var isCtrl = evt.ctrlKey || evt.metaKey; |
| if (isCtrl) { |
| if (evt.keyCode === 82) { |
| if (allowPageRefresh === true) { |
| location.reload(); |
| return; |
| } |
| // ctrl-r |
| throttledCanvasReload(); |
| |
| // default prevented in nf-universal-capture.js |
| } else if (evt.keyCode === 65) { |
| // ctrl-a |
| nfActions.selectAll(); |
| |
| // update URL deep linking params |
| nfCanvasUtils.setURLParameters(); |
| |
| nfNgBridge.digest(); |
| |
| // only want to prevent default if the action was performed, otherwise default select all would be overridden |
| evt.preventDefault(); |
| } else if (evt.keyCode === 67) { |
| // ctrl-c |
| if (nfCanvas.canWrite() && nfCanvasUtils.isCopyable(selection)) { |
| nfActions.copy(selection); |
| } |
| } else if (evt.keyCode === 86) { |
| // ctrl-v |
| if (nfCanvas.canWrite() && nfCanvasUtils.isPastable()) { |
| nfActions.paste(selection); |
| |
| // only want to prevent default if the action was performed, otherwise default paste would be overridden |
| evt.preventDefault(); |
| } |
| } |
| } else { |
| if (evt.keyCode === 8 || evt.keyCode === 46) { |
| // backspace or delete |
| if (nfCanvas.canWrite() && nfCanvasUtils.areDeletable(selection)) { |
| nfActions['delete'](selection); |
| } |
| |
| // default prevented in nf-universal-capture.js |
| } |
| } |
| }); |
| |
| // get the banners and update the page accordingly |
| $.ajax({ |
| type: 'GET', |
| url: config.urls.banners, |
| dataType: 'json' |
| }).done(function (response) { |
| // ensure the banners response is specified |
| if (nfCommon.isDefinedAndNotNull(response.banners)) { |
| if (nfCommon.isDefinedAndNotNull(response.banners.headerText) && response.banners.headerText !== '') { |
| // update the header text and show it |
| $('#banner-header').addClass('banner-header-background').text(response.banners.headerText).show(); |
| $('#canvas-container').css('top', '98px'); |
| // save the banner text to update the title of the page |
| BANNER_TEXT = ' - ' + response.banners.headerText; |
| } |
| |
| if (nfCommon.isDefinedAndNotNull(response.banners.footerText) && response.banners.footerText !== '') { |
| // update the footer text and show it |
| var bannerFooter = $('#banner-footer').text(response.banners.footerText).show(); |
| |
| var updateBottom = function (elementId) { |
| var element = $('#' + elementId); |
| element.css('bottom', parseInt(bannerFooter.css('height'), 10) + 'px'); |
| }; |
| |
| // update the position of elements affected by bottom banners |
| updateBottom('graph'); |
| } |
| } |
| |
| // update the graph dimensions |
| updateGraphSize(); |
| }).fail(nfErrorHandler.handleAjaxError); |
| }, |
| |
| /** |
| * Initialize NiFi. |
| */ |
| init: function () { |
| // attempt kerberos/oidc/saml authentication |
| var ticketExchange = $.Deferred(function (deferred) { |
| var successfulAuthentication = function (jwt) { |
| // Use Expiration from JWT for tracking authentication status |
| var sessionExpiration = nfCommon.getSessionExpiration(jwt); |
| if (sessionExpiration) { |
| nfAuthorizationStorage.setToken(sessionExpiration); |
| } |
| deferred.resolve(); |
| }; |
| |
| if (nfAuthorizationStorage.hasToken()) { |
| deferred.resolve(); |
| } else { |
| $.ajax({ |
| type: 'POST', |
| url: config.urls.kerberos, |
| dataType: 'text' |
| }).done(function (jwt) { |
| successfulAuthentication(jwt); |
| }).fail(function () { |
| $.ajax({ |
| type: 'POST', |
| url: config.urls.oidc, |
| dataType: 'text' |
| }).done(function (jwt) { |
| successfulAuthentication(jwt) |
| }).fail(function () { |
| $.ajax({ |
| type: 'POST', |
| url: config.urls.saml, |
| dataType: 'text' |
| }).done(function (jwt) { |
| successfulAuthentication(jwt) |
| }).fail(function () { |
| deferred.reject(); |
| }); |
| }); |
| }); |
| } |
| }).promise(); |
| |
| // load the current user |
| return $.Deferred(function (deferred) { |
| ticketExchange.always(function () { |
| loadCurrentUser().done(function (currentUser) { |
| // if the user is logged, we want to determine if they were logged in using a certificate |
| if (currentUser.anonymous === false) { |
| // render the users name |
| $('#current-user').text(currentUser.identity).show(); |
| |
| // render the logout button if there is a token locally |
| if (nfAuthorizationStorage.hasToken()) { |
| $('#logout-link-container').show(); |
| } else { |
| // Check Access Configuration when Token not found for new browser tabs |
| $.ajax({ |
| type: 'GET', |
| url: config.urls.accessConfig, |
| dataType: 'json' |
| }).done(function (response) { |
| if (response.config.supportsLogin) { |
| // Show logout button when login supported |
| $('#logout-link-container').show(); |
| // Set default expiration when authenticated to enable logout status |
| var expiration = nfCommon.getDefaultExpiration(); |
| nfAuthorizationStorage.setToken(expiration); |
| } |
| }).fail(function () { |
| window.location = '../nifi/login'; |
| }); |
| } |
| } else { |
| // set the anonymous user label |
| nfCommon.setAnonymousUserLabel(); |
| } |
| deferred.resolve(); |
| }).fail(function (xhr, status, error) { |
| // there is no anonymous access and we don't know this user - open the login page which handles login/registration/etc |
| if (xhr.status === 401) { |
| nfAuthorizationStorage.removeToken(); |
| window.location = '../nifi/login'; |
| } else { |
| deferred.reject(xhr, status, error); |
| } |
| }); |
| }); |
| }).promise(); |
| }, |
| |
| /** |
| * Set the parameter context. |
| * |
| * @argument {string} pc The parameter context |
| */ |
| setParameterContext: function (pc) { |
| parameterContext = pc; |
| }, |
| |
| /** |
| * Get the parameter context id. |
| */ |
| getParameterContext: function () { |
| return parameterContext; |
| }, |
| |
| /** |
| * Set the group id. |
| * |
| * @argument {string} gi The group id |
| */ |
| setGroupId: function (gi) { |
| groupId = gi; |
| }, |
| |
| /** |
| * Get the group id. |
| */ |
| getGroupId: function () { |
| return groupId; |
| }, |
| |
| /** |
| * Set the group name. |
| * |
| * @argument {string} gn The group name |
| */ |
| setGroupName: function (gn) { |
| groupName = gn; |
| }, |
| |
| /** |
| * Get the group name. |
| */ |
| getGroupName: function () { |
| return groupName; |
| }, |
| |
| /** |
| * Set the parent group id. |
| * |
| * @argument {string} pgi The id of the parent group |
| */ |
| setParentGroupId: function (pgi) { |
| parentGroupId = pgi; |
| }, |
| |
| /** |
| * Get the parent group id. |
| */ |
| getParentGroupId: function () { |
| return parentGroupId; |
| }, |
| |
| /** |
| * Set whether the authorizer is managed. |
| * |
| * @param bool The boolean value representing whether the authorizer is managed |
| */ |
| setManagedAuthorizer: function (bool) { |
| managedAuthorizer = bool; |
| }, |
| |
| /** |
| * Returns whether the authorizer is managed. |
| */ |
| isManagedAuthorizer: function () { |
| return managedAuthorizer; |
| }, |
| |
| /** |
| * Set whether the authorizer is configurable. |
| * |
| * @param bool The boolean value representing whether the authorizer is configurable. |
| */ |
| setConfigurableAuthorizer: function(bool){ |
| configurableAuthorizer = bool; |
| }, |
| |
| /** |
| * Returns whether the authorizer is configurable. |
| */ |
| isConfigurableAuthorizer: function () { |
| return configurableAuthorizer; |
| }, |
| |
| /** |
| * Set whether the users and groups is configurable. |
| * |
| * @param bool The boolean value representing whether the users and groups is configurable. |
| */ |
| setConfigurableUsersAndGroups: function(bool){ |
| configurableUsersAndGroups = bool; |
| }, |
| |
| /** |
| * Returns whether the users and groups is configurable. |
| */ |
| isConfigurableUsersAndGroups: function () { |
| return configurableUsersAndGroups; |
| }, |
| |
| /** |
| * Whether the current user can read from this group. |
| * |
| * @returns {boolean} can write |
| */ |
| canRead: function () { |
| if (permissions === null) { |
| return false; |
| } else { |
| return permissions.canRead === true; |
| } |
| }, |
| |
| /** |
| * Whether the current user can write in this group. |
| * |
| * @returns {boolean} can write |
| */ |
| canWrite: function () { |
| if (permissions === null) { |
| return false; |
| } else { |
| return permissions.canWrite === true; |
| } |
| }, |
| |
| /** |
| * Starts polling. |
| * |
| * @argument {int} autoRefreshInterval The auto refresh interval |
| */ |
| startPolling: function (autoRefreshInterval) { |
| // set polling flag |
| polling = true; |
| poll(autoRefreshInterval); |
| }, |
| |
| View: (function () { |
| |
| // initialize the zoom behavior |
| var behavior; |
| var x = 0, y = 0, k = SCALE; |
| |
| return { |
| |
| init: function () { |
| var refreshed; |
| |
| var panning = false; |
| |
| // filters zoom events as programmatically modifying the translate or scale now triggers the handlers |
| var isBirdseyeEvent = function(sourceEvent) { |
| if (nfCommon.isDefinedAndNotNull(sourceEvent)) { |
| if (nfCommon.isDefinedAndNotNull(sourceEvent.subject)) { |
| return sourceEvent.subject.source === 'birdseye'; |
| } else { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| }; |
| |
| // see if the scale has changed during this zoom event, |
| // we want to only transition when zooming in/out as running |
| // the transitions during pan events is undesirable |
| var shouldTransition = function(sourceEvent) { |
| if (nfCommon.isDefinedAndNotNull(sourceEvent)) { |
| if (isBirdseyeEvent(sourceEvent)) { |
| return false; |
| } |
| |
| return sourceEvent.type === 'wheel' || sourceEvent.type === 'mousewheel'; |
| } else { |
| return true; |
| } |
| }; |
| |
| // define the behavior |
| behavior = d3.zoom() |
| .scaleExtent([MIN_SCALE, MAX_SCALE]) |
| .on('start', function () { |
| // hide the context menu |
| nfContextMenu.hide(); |
| }) |
| .on('zoom', function () { |
| // update the current translation and scale |
| if (!isNaN(d3.event.transform.x)) { |
| x = d3.event.transform.x; |
| } |
| if (!isNaN(d3.event.transform.y)) { |
| y = d3.event.transform.y; |
| } |
| if (!isNaN(d3.event.transform.k)) { |
| k = d3.event.transform.k; |
| } |
| |
| // indicate that we are panning to prevent deselection in zoom.end below |
| panning = true; |
| |
| // refresh the canvas |
| refreshed = nfCanvas.View.refresh({ |
| persist: false, |
| transition: shouldTransition(d3.event.sourceEvent), |
| refreshComponents: false, |
| refreshBirdseye: false |
| }); |
| }) |
| .on('end', function () { |
| if (!isBirdseyeEvent(d3.event.sourceEvent)) { |
| // ensure the canvas was actually refreshed |
| if (nfCommon.isDefinedAndNotNull(refreshed)) { |
| nfGraph.updateVisibility(); |
| |
| // refresh the birdseye |
| refreshed.done(function () { |
| nfBirdseye.refresh(); |
| }); |
| |
| // persist the users view |
| nfCanvasUtils.persistUserView(); |
| |
| // reset the refreshed deferred |
| refreshed = null; |
| } |
| |
| if (panning === false) { |
| // deselect as necessary if we are not panning |
| nfCanvasUtils.getSelection().classed('selected', false); |
| |
| // update URL deep linking params |
| nfCanvasUtils.setURLParameters(); |
| |
| // inform Angular app values have changed |
| nfNgBridge.digest(); |
| } |
| } |
| |
| panning = false; |
| }); |
| |
| // add the behavior to the canvas and disable dbl click zoom |
| svg.call(behavior).on('dblclick.zoom', null); |
| }, |
| |
| /** |
| * Whether or not a component should be rendered based solely on the current scale. |
| * |
| * @returns {Boolean} |
| */ |
| shouldRenderPerScale: function () { |
| return nfCanvas.View.getScale() >= MIN_SCALE_TO_RENDER; |
| }, |
| |
| /** |
| * Translates by the specified [x, y]. |
| * |
| * @param {array} translate [x, y] to translate by |
| */ |
| translate: function (translate) { |
| behavior.translateBy(svg, translate[0], translate[1]); |
| }, |
| |
| /** |
| * Scales by the specified scale. |
| * |
| * @param {number} scale The factor to scale by |
| */ |
| scale: function (scale) { |
| behavior.scaleBy(svg, scale); |
| }, |
| |
| /** |
| * Sets the current transform. |
| * |
| * @param translate |
| * @param scale |
| */ |
| transform: function (translate, scale) { |
| behavior.transform(svg, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)); |
| }, |
| |
| /** |
| * Gets the current translate. |
| */ |
| getTranslate: function () { |
| return [x, y]; |
| }, |
| |
| /** |
| * Gets the current scale. |
| */ |
| getScale: function () { |
| return k; |
| }, |
| |
| /** |
| * Zooms in a single zoom increment. |
| */ |
| zoomIn: function () { |
| nfCanvas.View.scale(INCREMENT); |
| }, |
| |
| /** |
| * Zooms out a single zoom increment. |
| */ |
| zoomOut: function () { |
| nfCanvas.View.scale(1 / INCREMENT); |
| }, |
| |
| /** |
| * Zooms to fit the entire graph on the canvas. |
| */ |
| fit: function () { |
| var translate = nfCanvas.View.getTranslate(); |
| var scale = nfCanvas.View.getScale(); |
| var newScale; |
| |
| // get the canvas normalized width and height |
| var canvasContainer = $('#canvas-container'); |
| var canvasWidth = canvasContainer.width(); |
| var canvasHeight = canvasContainer.height(); |
| |
| // get the bounding box for the graph |
| var graphBox = d3.select('#canvas').node().getBoundingClientRect(); |
| var graphWidth = graphBox.width / scale; |
| var graphHeight = graphBox.height / scale; |
| var graphLeft = graphBox.left / scale; |
| var graphTop = (graphBox.top - nfCanvas.CANVAS_OFFSET) / scale; |
| |
| // adjust the scale to ensure the entire graph is visible |
| if (graphWidth > canvasWidth || graphHeight > canvasHeight) { |
| newScale = Math.min(canvasWidth / graphWidth, canvasHeight / graphHeight); |
| |
| // ensure the scale is within bounds |
| newScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE); |
| } else { |
| newScale = 1; |
| |
| // since the entire graph will fit on the canvas, offset origin appropriately |
| graphLeft -= 100; |
| graphTop -= 50; |
| } |
| |
| // center as appropriate |
| nfCanvasUtils.centerBoundingBox({ |
| x: graphLeft - (translate[0] / scale), |
| y: graphTop - (translate[1] / scale), |
| width: canvasWidth / newScale, |
| height: canvasHeight / newScale, |
| scale: newScale |
| }); |
| }, |
| |
| /** |
| * Zooms to the actual size (1 to 1). |
| */ |
| actualSize: function () { |
| var translate = nfCanvas.View.getTranslate(); |
| var scale = nfCanvas.View.getScale(); |
| |
| // get the first selected component |
| var selection = nfCanvasUtils.getSelection(); |
| |
| // box to zoom towards |
| var box; |
| |
| // if components have been selected position the view accordingly |
| if (!selection.empty()) { |
| // gets the data for the first component |
| var selectionBox = selection.node().getBoundingClientRect(); |
| |
| // get the bounding box for the selected components |
| box = { |
| x: (selectionBox.left / scale) - (translate[0] / scale), |
| y: ((selectionBox.top - nfCanvas.CANVAS_OFFSET) / scale) - (translate[1] / scale), |
| width: selectionBox.width / scale, |
| height: selectionBox.height / scale, |
| scale: 1 |
| }; |
| } else { |
| // get the offset |
| var canvasContainer = $('#canvas-container'); |
| |
| // get the canvas normalized width and height |
| var screenWidth = canvasContainer.width() / scale; |
| var screenHeight = canvasContainer.height() / scale; |
| |
| // center around the center of the screen accounting for the translation accordingly |
| box = { |
| x: (screenWidth / 2) - (translate[0] / scale), |
| y: (screenHeight / 2) - (translate[1] / scale), |
| width: 1, |
| height: 1, |
| scale: 1 |
| }; |
| } |
| |
| // center as appropriate |
| nfCanvasUtils.centerBoundingBox(box); |
| }, |
| |
| /** |
| * Refreshes the view based on the configured translation and scale. |
| * |
| * @param {object} options Options for the refresh operation |
| */ |
| refresh: function (options) { |
| return $.Deferred(function (deferred) { |
| |
| var persist = true; |
| var transition = false; |
| var refreshComponents = true; |
| var refreshBirdseye = true; |
| |
| // extract the options if specified |
| if (nfCommon.isDefinedAndNotNull(options)) { |
| persist = nfCommon.isDefinedAndNotNull(options.persist) ? options.persist : persist; |
| transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition; |
| refreshComponents = nfCommon.isDefinedAndNotNull(options.refreshComponents) ? options.refreshComponents : refreshComponents; |
| refreshBirdseye = nfCommon.isDefinedAndNotNull(options.refreshBirdseye) ? options.refreshBirdseye : refreshBirdseye; |
| } |
| |
| // update component visibility |
| if (refreshComponents) { |
| nfGraph.updateVisibility(); |
| } |
| |
| // persist if appropriate |
| if (persist === true) { |
| nfCanvasUtils.persistUserView(); |
| } |
| |
| var t = nfCanvas.View.getTranslate(); |
| var s = nfCanvas.View.getScale(); |
| |
| // update the canvas |
| if (transition === true) { |
| canvas.transition() |
| .duration(500) |
| .attr('transform', function () { |
| return 'translate(' + t + ') scale(' + s + ')'; |
| }) |
| .on('end', function () { |
| // refresh birdseye if appropriate |
| if (refreshBirdseye === true) { |
| nfBirdseye.refresh(); |
| } |
| |
| deferred.resolve(); |
| }); |
| } else { |
| canvas.attr('transform', function () { |
| return 'translate(' + t + ') scale(' + s + ')'; |
| }); |
| |
| // refresh birdseye if appropriate |
| if (refreshBirdseye === true) { |
| nfBirdseye.refresh(); |
| } |
| |
| deferred.resolve(); |
| } |
| }).promise(); |
| } |
| }; |
| }()) |
| }; |
| |
| return nfCanvas; |
| })); |