http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js new file mode 100644 index 0000000..6cefd4f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js @@ -0,0 +1,302 @@ +/* + * 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 nf, top */ + +$(document).ready(function () { + nf.Login.init(); +}); + +nf.Login = (function () { + + var supportsAnonymous = false; + + var config = { + urls: { + identity: '../nifi-api/controller/identity', + users: '../nifi-api/controller/users', + token: '../nifi-api/access/token', + accessStatus: '../nifi-api/access', + accessConfig: '../nifi-api/access/config' + } + }; + + var initializeMessage = function () { + $('#login-message-container').show(); + }; + + var showLogin = function () { + // reset the forms + $('#username').val(''); + $('#password').val(''); + $('#login-submission-button').text('Log in'); + + // update the form visibility + $('#login-container').show(); + $('#nifi-registration-container').hide(); + + // set the focus + $('#username').focus(); + }; + + var initializeNiFiRegistration = function () { + $('#nifi-registration-justification').count({ + charCountField: '#remaining-characters' + }); + + // toggle between signup and login + $('#login-to-account-link').on('click', function () { + showLogin(); + }); + }; + + var showNiFiRegistration = function () { + // reset the forms + $('#login-submission-button').text('Submit'); + $('#nifi-registration-justification').val(''); + + // update the form visibility + $('#login-container').hide(); + $('#nifi-registration-container').show(); + }; + + var initializeSubmission = function () { + $('#login-submission-button').on('click', function () { + if ($('#login-container').is(':visible')) { + login(); + } else if ($('#nifi-registration-container').is(':visible')) { + submitJustification(); + } + }); + + $('#login-submission-container').show(); + }; + + var login = function () { + // show the logging message... + $('#login-progress-label').text('Logging in...'); + $('#login-progress-container').show(); + $('#login-submission-container').hide(); + + // login submit + $.ajax({ + type: 'POST', + url: config.urls.token, + data: { + 'username': $('#username').val(), + 'password': $('#password').val() + } + }).done(function (jwt) { + // get the payload and store the token with the appropirate expiration + var token = nf.Common.getJwtPayload(jwt); + var expiration = parseInt(token['exp'], 10) * nf.Common.MILLIS_PER_SECOND; + nf.Storage.setItem('jwt', jwt, expiration); + + // check to see if they actually have access now + $.ajax({ + type: 'GET', + url: config.urls.identity, + dataType: 'json' + }).done(function (response) { + if (response.identity === 'anonymous') { + showLogoutLink(); + + // schedule automatic token refresh + nf.Common.scheduleTokenRefresh(); + + // show the user + $('#nifi-user-submit-justification').text(token['preferred_username']); + + // show the registration form + initializeNiFiRegistration(); + showNiFiRegistration(); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + } else { + // reload as appropriate - no need to schedule token refresh as the page is reloading + if (top !== window) { + parent.window.location = '/nifi'; + } else { + window.location = '/nifi'; + } + } + }).fail(function (xhr, status, error) { + showLogoutLink(); + + // schedule automatic token refresh + nf.Common.scheduleTokenRefresh(); + + // show the user + $('#nifi-user-submit-justification').text(token['preferred_username']); + + if (xhr.status === 401) { + initializeNiFiRegistration(); + showNiFiRegistration(); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + } else { + $('#login-message-title').text('Unable to log in'); + $('#login-message').text(xhr.responseText); + + // update visibility + $('#login-container').hide(); + $('#login-submission-container').hide(); + $('#login-progress-container').hide(); + $('#login-message-container').show(); + } + }); + }).fail(function (xhr, status, error) { + nf.Dialog.showOkDialog({ + dialogContent: nf.Common.escapeHtml(xhr.responseText), + overlayBackground: false + }); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + }); + }; + + var submitJustification = function () { + // show the logging message... + $('#login-progress-label').text('Submitting...'); + $('#login-progress-container').show(); + $('#login-submission-container').hide(); + + // attempt to create the nifi account registration + $.ajax({ + type: 'POST', + url: config.urls.users, + data: { + 'justification': $('#nifi-registration-justification').val() + } + }).done(function (response) { + var markup = 'An administrator will process your request shortly.'; + if (supportsAnonymous === true) { + markup += '<br/><br/>In the meantime you can continue accessing anonymously.'; + } + + $('#login-message-title').text('Thanks!'); + $('#login-message').html(markup); + }).fail(function (xhr, status, error) { + $('#login-message-title').text('Unable to submit justification'); + $('#login-message').text(xhr.responseText); + }).always(function () { + // update form visibility + $('#nifi-registration-container').hide(); + $('#login-submission-container').hide(); + $('#login-progress-container').hide(); + $('#login-message-container').show(); + }); + }; + + var showLogoutLink = function () { + nf.Common.showLogoutLink(); + }; + + return { + /** + * Initializes the login page. + */ + init: function () { + nf.Storage.init(); + + if (nf.Storage.getItem('jwt') !== null) { + showLogoutLink(); + } + + // access status + var accessStatus = $.ajax({ + type: 'GET', + url: config.urls.accessStatus, + dataType: 'json' + }).fail(function (xhr, status, error) { + $('#login-message-title').text('Unable to check Access Status'); + $('#login-message').text(xhr.responseText); + initializeMessage(); + }); + + // access config + var accessConfigXhr = $.ajax({ + type: 'GET', + url: config.urls.accessConfig, + dataType: 'json' + }); + + $.when(accessStatus, accessConfigXhr).done(function (accessStatusResult, accessConfigResult) { + var accessStatusResponse = accessStatusResult[0]; + var accessStatus = accessStatusResponse.accessStatus; + + var accessConfigResponse = accessConfigResult[0]; + var accessConfig = accessConfigResponse.config; + + // record whether this NiFi supports anonymous access + supportsAnonymous = accessConfig.supportsAnonymous; + + // possible login states + var needsLogin = false; + var needsNiFiRegistration = false; + var showMessage = false; + + // handle the status appropriately + if (accessStatus.status === 'UNKNOWN') { + needsLogin = true; + } else if (accessStatus.status === 'UNREGISTERED') { + needsNiFiRegistration = true; + + $('#nifi-user-submit-justification').text(accessStatus.username); + } else if (accessStatus.status === 'NOT_ACTIVE') { + showMessage = true; + + $('#login-message-title').text('Access Denied'); + $('#login-message').text(accessStatus.message); + } else if (accessStatus.status === 'ACTIVE') { + showMessage = true; + + $('#login-message-title').text('Success'); + $('#login-message').text('Your account is active and you are already logged in.'); + } + + // if login is required, verify its supported + if (accessConfig.supportsLogin === false && needsLogin === true) { + $('#login-message-title').text('Access Denied'); + $('#login-message').text('This NiFi is not configured to support login.'); + showMessage = true; + needsLogin = false; + } + + // initialize the page as appropriate + if (showMessage === true) { + initializeMessage(); + } else if (needsLogin === true) { + showLogin(); + } else if (needsNiFiRegistration === true) { + initializeNiFiRegistration(); + showNiFiRegistration(); + } + + if (needsLogin === true || needsNiFiRegistration === true) { + initializeSubmission(); + } + }); + } + }; +}()); \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js index 06c34f9..321044f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js @@ -50,948 +50,1110 @@ $(document).ready(function () { // hide the loading indicator $('div.loading-container').removeClass('ajax-loading'); }); - - // initialize the tooltips - $('img.setting-icon').qtip(nf.Common.config.tooltipConfig); -}); - -// Define a common utility class used across the entire application. -nf.Common = { - config: { - sensitiveText: 'Sensitive value set', - tooltipConfig: { - style: { - classes: 'nifi-tooltip' - }, - show: { - solo: true, - effect: false - }, - hide: { - effect: false - }, - position: { - at: 'top right', - my: 'bottom left' - } - } - }, - - /** - * Determines if the current broswer supports SVG. - */ - SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, - - /** - * The authorities for the current user. - */ - authorities: undefined, - /** - * Sets the authorities for the current user. - * - * @argument {array} roles The current users authorities - */ - setAuthorities: function (roles) { - nf.Common.authorities = roles; - }, - - /** - * Loads a script at the specified URL. Supports caching the script on the browser. - * - * @param {string} url - */ - cachedScript: function (url) { - return $.ajax({ - dataType: 'script', - cache: true, - url: url - }); - }, - - /** - * Determines whether the current user can access provenance. - * - * @returns {boolean} - */ - canAccessProvenance: function () { - var canAccessProvenance = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_PROVENANCE') { - canAccessProvenance = true; - return false; - } - }); - } - return canAccessProvenance; - }, - - /** - * Returns whether or not the current user is a DFM. - */ - isDFM: function () { - var dfm = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_DFM') { - dfm = true; + // include jwt when possible + $.ajaxSetup({ + 'beforeSend': function(xhr) { + var hadToken = nf.Storage.hasItem('jwt'); + + // get the token to include in all requests + var token = nf.Storage.getItem('jwt'); + if (token !== null) { + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + } else { + // if the current user was logged in with a token and the token just expired, reload + if (hadToken === true) { return false; } - }); + } } - return dfm; - }, + }); + + // initialize the tooltips + $('img.setting-icon').qtip(nf.Common.config.tooltipConfig); - /** - * Returns whether or not the current user is a DFM. - */ - isAdmin: function () { - var admin = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_ADMIN') { - admin = true; - return false; - } - }); - } - return admin; - }, + // shows the logout link in the message-pane when appropriate and schedule token refresh + if (nf.Storage.getItem('jwt') !== null) { + $('#user-logout-container').show(); + nf.Common.scheduleTokenRefresh(); + } - /** - * Adds a mouse over effect for the specified selector using - * the specified styles. - * - * @argument {string} selector The selector for the element to add a hover effect for - * @argument {string} normalStyle The css style for the normal state - * @argument {string} overStyle The css style for the over state - */ - addHoverEffect: function (selector, normalStyle, overStyle) { - $(document).on('mouseenter', selector, function () { - $(this).removeClass(normalStyle).addClass(overStyle); - }).on('mouseleave', selector, function () { - $(this).removeClass(overStyle).addClass(normalStyle); - }); - return $(selector).addClass(normalStyle); - }, + // handle logout + $('#user-logout').on('click', function () { + nf.Storage.removeItem('jwt'); + window.location = '/nifi/login'; + }); +}); + +// Define a common utility class used across the entire application. +nf.Common = (function () { + // interval for cancelling token refresh when necessary + var tokenRefreshInterval = null; - /** - * Method for handling ajax errors. - * - * @argument {object} xhr The XmlHttpRequest - * @argument {string} status The status of the request - * @argument {string} error The error - */ - handleAjaxError: function (xhr, status, error) { - // show the account registration page if necessary - if (xhr.status === 401 && $('#registration-pane').length) { - // show the registration pane - $('#registration-pane').show(); - - // close the canvas - nf.Common.closeCanvas(); - return; - } + return { + ANONYMOUS_USER_TEXT: 'Anonymous user', - // if an error occurs while the splash screen is visible close the canvas show the error message - if ($('#splash').is(':visible')) { - $('#message-title').text('An unexpected error has occurred'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Please check the logs.'); - } else { - $('#message-content').text(xhr.responseText); + config: { + sensitiveText: 'Sensitive value set', + tooltipConfig: { + style: { + classes: 'nifi-tooltip' + }, + show: { + solo: true, + effect: false + }, + hide: { + effect: false + }, + position: { + at: 'top right', + my: 'bottom left' + } } + }, - // show the error pane - $('#message-pane').show(); + /** + * Determines if the current broswer supports SVG. + */ + SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, - // close the canvas - nf.Common.closeCanvas(); - return; - } - - // status code 400, 404, and 409 are expected response codes for common errors. - if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { - nf.Dialog.showOkDialog({ - dialogContent: nf.Common.escapeHtml(xhr.responseText), - overlayBackground: false + /** + * The authorities for the current user. + */ + authorities: undefined, + + /** + * Sets the authorities for the current user. + * + * @argument {array} roles The current users authorities + */ + setAuthorities: function (roles) { + nf.Common.authorities = roles; + }, + + /** + * Loads a script at the specified URL. Supports caching the script on the browser. + * + * @param {string} url + */ + cachedScript: function (url) { + return $.ajax({ + dataType: 'script', + cache: true, + url: url }); - } else { - if (xhr.status < 99 || xhr.status === 12007 || xhr.status === 12029) { - var content = 'Please ensure the application is running and check the logs for any errors.'; - if (nf.Common.isDefinedAndNotNull(status)) { - if (status === 'timeout') { - content = 'Request has timed out. Please ensure the application is running and check the logs for any errors.'; - } else if (status === 'abort') { - content = 'Request has been aborted.'; - } else if (status === 'No Transport') { - content = 'Request transport mechanism failed. Please ensure the host where the application is running is accessible.'; + }, + + /** + * Automatically refresh tokens by checking once an hour if its going to expire soon. + */ + scheduleTokenRefresh: function () { + // if we are currently polling for token refresh, cancel it + if (tokenRefreshInterval !== null) { + clearInterval(tokenRefreshInterval); + } + + // set the interval to one hour + var interval = nf.Common.MILLIS_PER_MINUTE; + + var checkExpiration = function () { + var expiration = nf.Storage.getItemExpiration('jwt'); + + // ensure there is an expiration and token present + if (expiration !== null) { + var expirationDate = new Date(expiration); + var now = new Date(); + + // get the time remainging plus a little bonus time to reload the token + var timeRemaining = expirationDate.valueOf() - now.valueOf() - (30 * nf.Common.MILLIS_PER_SECOND); + if (timeRemaining < interval) { + if ($('#current-user').text() !== nf.Common.ANONYMOUS_USER_TEXT && !$('#anonymous-user-alert').is(':visible')) { + // if the token will expire before the next interval minus some bonus time, notify the user to re-login + $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, { + content: 'Your session will expire soon. Please log in again to avoid being automatically logged out.', + position: { + my: 'top right', + at: 'bottom left' + } + })); + } } } - $('#message-title').text('Unable to communicate with NiFi'); - $('#message-content').text(content); - } else if (xhr.status === 401) { - $('#message-title').text('Unauthorized'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Authorization is required to use this NiFi.'); + }; + + // perform initial check + checkExpiration(); + + // schedule subsequent checks + tokenRefreshInterval = setInterval(checkExpiration, interval); + }, + + /** + * Sets the anonymous user label. + */ + setAnonymousUserLabel: function () { + var anonymousUserAlert = $('#anonymous-user-alert'); + if (anonymousUserAlert.data('qtip')) { + anonymousUserAlert.qtip('api').destroy(true); + } + + // alert user's of anonymous access + anonymousUserAlert.show().qtip($.extend({}, nf.Common.config.tooltipConfig, { + content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.', + position: { + my: 'top right', + at: 'bottom left' + } + })); + + // render the anonymous user text + $('#current-user').text(nf.Common.ANONYMOUS_USER_TEXT).show(); + }, + + /** + * Extracts the subject from the specified jwt. If the jwt is not as expected + * an empty string is returned. + * + * @param {string} jwt + * @returns {string} + */ + getJwtPayload: function (jwt) { + if (nf.Common.isDefinedAndNotNull(jwt)) { + var segments = jwt.split(/\./); + if (segments.length !== 3) { + return ''; + } + + var rawPayload = $.base64.atob(segments[1]); + var payload = JSON.parse(rawPayload); + + if (nf.Common.isDefinedAndNotNull(payload)) { + return payload; } else { - $('#message-content').text(xhr.responseText); + return null; } - } else if (xhr.status === 403) { - $('#message-title').text('Forbidden'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Unable to authorize you to use this NiFi.'); + } + + return null; + }, + + /** + * Determines whether the current user can access provenance. + * + * @returns {boolean} + */ + canAccessProvenance: function () { + var canAccessProvenance = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_PROVENANCE') { + canAccessProvenance = true; + return false; + } + }); + } + return canAccessProvenance; + }, + + /** + * Returns whether or not the current user is a DFM. + */ + isDFM: function () { + var dfm = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_DFM') { + dfm = true; + return false; + } + }); + } + return dfm; + }, + + /** + * Returns whether or not the current user is a DFM. + */ + isAdmin: function () { + var admin = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_ADMIN') { + admin = true; + return false; + } + }); + } + return admin; + }, + + /** + * Adds a mouse over effect for the specified selector using + * the specified styles. + * + * @argument {string} selector The selector for the element to add a hover effect for + * @argument {string} normalStyle The css style for the normal state + * @argument {string} overStyle The css style for the over state + */ + addHoverEffect: function (selector, normalStyle, overStyle) { + $(document).on('mouseenter', selector, function () { + $(this).removeClass(normalStyle).addClass(overStyle); + }).on('mouseleave', selector, function () { + $(this).removeClass(overStyle).addClass(normalStyle); + }); + return $(selector).addClass(normalStyle); + }, + + /** + * Method for handling ajax errors. + * + * @argument {object} xhr The XmlHttpRequest + * @argument {string} status The status of the request + * @argument {string} error The error + */ + handleAjaxError: function (xhr, status, error) { + if (status === 'canceled') { + if ($('#splash').is(':visible')) { + $('#message-title').text('Session Expired'); + $('#message-content').text('Your session has expired. Please reload to log in again.'); + + // show the error pane + $('#message-pane').show(); } else { - $('#message-content').text(xhr.responseText); + nf.Dialog.showOkDialog({ + dialogContent: 'Your session has expired. Please press Ok to log in again.', + overlayBackground: false, + okHandler: function () { + window.location = '/nifi'; + } + }); } - } else if (xhr.status === 500) { - $('#message-title').text('An unexpected error has occurred'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('An error occurred communicating with the application core. Please check the logs and fix any configuration issues before restarting.'); + + // close the canvas + nf.Common.closeCanvas(); + return; + } + + // if an error occurs while the splash screen is visible close the canvas show the error message + if ($('#splash').is(':visible')) { + if (xhr.status === 401) { + $('#message-title').text('Unauthorized'); + } else if (xhr.status === 403) { + $('#message-title').text('Access Denied'); } else { - $('#message-content').text(xhr.responseText); + $('#message-title').text('An unexpected error has occurred'); } - } else if (xhr.status === 200 || xhr.status === 201) { - $('#message-title').text('Parse Error'); + if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Unable to interpret response from NiFi.'); + $('#message-content').text('Please check the logs.'); } else { $('#message-content').text(xhr.responseText); } + + // show the error pane + $('#message-pane').show(); + + // close the canvas + nf.Common.closeCanvas(); + return; + } + + // status code 400, 404, and 409 are expected response codes for common errors. + if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { + nf.Dialog.showOkDialog({ + dialogContent: nf.Common.escapeHtml(xhr.responseText), + overlayBackground: false + }); } else { - $('#message-title').text(xhr.status + ': Unexpected Response'); - $('#message-content').text('An unexpected error has occurred. Please check the logs.'); + if (xhr.status < 99 || xhr.status === 12007 || xhr.status === 12029) { + var content = 'Please ensure the application is running and check the logs for any errors.'; + if (nf.Common.isDefinedAndNotNull(status)) { + if (status === 'timeout') { + content = 'Request has timed out. Please ensure the application is running and check the logs for any errors.'; + } else if (status === 'abort') { + content = 'Request has been aborted.'; + } else if (status === 'No Transport') { + content = 'Request transport mechanism failed. Please ensure the host where the application is running is accessible.'; + } + } + $('#message-title').text('Unable to communicate with NiFi'); + $('#message-content').text(content); + } else if (xhr.status === 401) { + $('#message-title').text('Unauthorized'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Authorization is required to use this NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 403) { + $('#message-title').text('Access Denied'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Unable to authorize you to use this NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 500) { + $('#message-title').text('An unexpected error has occurred'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('An error occurred communicating with the application core. Please check the logs and fix any configuration issues before restarting.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 200 || xhr.status === 201) { + $('#message-title').text('Parse Error'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Unable to interpret response from NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else { + $('#message-title').text(xhr.status + ': Unexpected Response'); + $('#message-content').text('An unexpected error has occurred. Please check the logs.'); + } + + // show the error pane + $('#message-pane').show(); + + // close the canvas + nf.Common.closeCanvas(); } + }, - // show the error pane - $('#message-pane').show(); + /** + * Closes the canvas by removing the splash screen and stats poller. + */ + closeCanvas: function () { + nf.Common.showLogoutLink(); + + // ensure this javascript has been loaded in the nf canvas page + if (nf.Common.isDefinedAndNotNull(nf.Canvas)) { + // hide the splash screen if required + if ($('#splash').is(':visible')) { + nf.Canvas.hideSplash(); + } - // close the canvas - nf.Common.closeCanvas(); - } - }, - - /** - * Closes the canvas by removing the splash screen and stats poller. - */ - closeCanvas: function () { - // ensure this javascript has been loaded in the nf canvas page - if (nf.Common.isDefinedAndNotNull(nf.Canvas)) { - // hide the splash screen if required - if ($('#splash').is(':visible')) { - nf.Canvas.hideSplash(); + // hide the context menu + nf.ContextMenu.hide(); + + // shut off the auto refresh + nf.Canvas.stopRevisionPolling(); + nf.Canvas.stopStatusPolling(); } + }, - // hide the context menu - nf.ContextMenu.hide(); + /** + * Shows the logout link if appropriate. + */ + showLogoutLink: function () { + if (nf.Storage.getItem('jwt') === null) { + $('#user-logout-container').hide(); + } else { + $('#user-logout-container').show(); + } + }, - // shut off the auto refresh - nf.Canvas.stopRevisionPolling(); - nf.Canvas.stopStatusPolling(); - } - }, - - /** - * Populates the specified field with the specified value. If the value is - * undefined, the field will read 'No value set.' If the value is an empty - * string, the field will read 'Empty string set.' - * - * @argument {string} target The dom Id of the target - * @argument {string} value The value - */ - populateField: function (target, value) { - if (nf.Common.isUndefined(value) || nf.Common.isNull(value)) { - return $('#' + target).addClass('unset').text('No value set'); - } else if (value === '') { - return $('#' + target).addClass('blank').text('Empty string set'); - } else { - return $('#' + target).text(value); - } - }, - - /** - * Clears the specified field. Removes any style that may have been applied - * by a preceeding call to populateField. - * - * @argument {string} target The dom Id of the target - */ - clearField: function (target) { - return $('#' + target).removeClass('unset blank').text(''); - }, - - /** - * Cleans up any tooltips that have been created for the specified container. - * - * @param {jQuery} container - * @param {string} tooltipTarget - */ - cleanUpTooltips: function(container, tooltipTarget) { - container.find(tooltipTarget).each(function () { - var tip = $(this); - if (tip.data('qtip')) { - var api = tip.qtip('api'); - api.destroy(true); - } - }); - }, - - /** - * Removes all read only property detail dialogs. - */ - removeAllPropertyDetailDialogs: function () { - var propertyDetails = $('body').children('div.property-detail'); - propertyDetails.find('div.nfel-editor').nfeditor('destroy'); - propertyDetails.hide().remove(); - }, - - /** - * Formats the tooltip for the specified property. - * - * @param {object} propertyDescriptor The property descriptor - * @param {object} propertyHistory The property history - * @returns {string} - */ - formatPropertyTooltip: function (propertyDescriptor, propertyHistory) { - var tipContent = []; - - // show the property description if applicable - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - if (!nf.Common.isBlank(propertyDescriptor.description)) { - tipContent.push(nf.Common.escapeHtml(propertyDescriptor.description)); - } - if (!nf.Common.isBlank(propertyDescriptor.defaultValue)) { - tipContent.push('<b>Default value:</b> ' + nf.Common.escapeHtml(propertyDescriptor.defaultValue)); - } - if (!nf.Common.isBlank(propertyDescriptor.supportsEl)) { - tipContent.push('<b>Supports expression language:</b> ' + nf.Common.escapeHtml(propertyDescriptor.supportsEl)); + /** + * Populates the specified field with the specified value. If the value is + * undefined, the field will read 'No value set.' If the value is an empty + * string, the field will read 'Empty string set.' + * + * @argument {string} target The dom Id of the target + * @argument {string} value The value + */ + populateField: function (target, value) { + if (nf.Common.isUndefined(value) || nf.Common.isNull(value)) { + return $('#' + target).addClass('unset').text('No value set'); + } else if (value === '') { + return $('#' + target).addClass('blank').text('Empty string set'); + } else { + return $('#' + target).text(value); } - } + }, - if (nf.Common.isDefinedAndNotNull(propertyHistory)) { - if (!nf.Common.isEmpty(propertyHistory.previousValues)) { - var history = []; - $.each(propertyHistory.previousValues, function (_, previousValue) { - history.push('<li>' + nf.Common.escapeHtml(previousValue.previousValue) + ' - ' + nf.Common.escapeHtml(previousValue.timestamp) + ' (' + nf.Common.escapeHtml(previousValue.userName) + ')</li>'); - }); - tipContent.push('<b>History:</b><ul class="property-info">' + history.join('') + '</ul>'); + /** + * Clears the specified field. Removes any style that may have been applied + * by a preceeding call to populateField. + * + * @argument {string} target The dom Id of the target + */ + clearField: function (target) { + return $('#' + target).removeClass('unset blank').text(''); + }, + + /** + * Cleans up any tooltips that have been created for the specified container. + * + * @param {jQuery} container + * @param {string} tooltipTarget + */ + cleanUpTooltips: function(container, tooltipTarget) { + container.find(tooltipTarget).each(function () { + var tip = $(this); + if (tip.data('qtip')) { + var api = tip.qtip('api'); + api.destroy(true); + } + }); + }, + + /** + * Removes all read only property detail dialogs. + */ + removeAllPropertyDetailDialogs: function () { + var propertyDetails = $('body').children('div.property-detail'); + propertyDetails.find('div.nfel-editor').nfeditor('destroy'); + propertyDetails.hide().remove(); + }, + + /** + * Formats the tooltip for the specified property. + * + * @param {object} propertyDescriptor The property descriptor + * @param {object} propertyHistory The property history + * @returns {string} + */ + formatPropertyTooltip: function (propertyDescriptor, propertyHistory) { + var tipContent = []; + + // show the property description if applicable + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + if (!nf.Common.isBlank(propertyDescriptor.description)) { + tipContent.push(nf.Common.escapeHtml(propertyDescriptor.description)); + } + if (!nf.Common.isBlank(propertyDescriptor.defaultValue)) { + tipContent.push('<b>Default value:</b> ' + nf.Common.escapeHtml(propertyDescriptor.defaultValue)); + } + if (!nf.Common.isBlank(propertyDescriptor.supportsEl)) { + tipContent.push('<b>Supports expression language:</b> ' + nf.Common.escapeHtml(propertyDescriptor.supportsEl)); + } } - } - if (tipContent.length > 0) { - return tipContent.join('<br/><br/>'); - } else { - return null; - } - }, - - /** - * Formats the specified property (name and value) accordingly. - * - * @argument {string} name The name of the property - * @argument {string} value The value of the property - */ - formatProperty: function (name, value) { - return '<div><span class="label">' + nf.Common.formatValue(name) + ': </span>' + nf.Common.formatValue(value) + '</div>'; - }, - - /** - * Formats the specified value accordingly. - * - * @argument {string} value The value of the property - */ - formatValue: function (value) { - if (nf.Common.isDefinedAndNotNull(value)) { - if (value === '') { - return '<span class="blank">Empty string set</span>'; + if (nf.Common.isDefinedAndNotNull(propertyHistory)) { + if (!nf.Common.isEmpty(propertyHistory.previousValues)) { + var history = []; + $.each(propertyHistory.previousValues, function (_, previousValue) { + history.push('<li>' + nf.Common.escapeHtml(previousValue.previousValue) + ' - ' + nf.Common.escapeHtml(previousValue.timestamp) + ' (' + nf.Common.escapeHtml(previousValue.userName) + ')</li>'); + }); + tipContent.push('<b>History:</b><ul class="property-info">' + history.join('') + '</ul>'); + } + } + + if (tipContent.length > 0) { + return tipContent.join('<br/><br/>'); } else { - return nf.Common.escapeHtml(value); + return null; } - } else { - return '<span class="unset">No value set</span>'; - } - }, - - /** - * HTML escapes the specified string. If the string is null - * or undefined, an empty string is returned. - * - * @returns {string} - */ - escapeHtml: (function () { - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' - }; - - return function (string) { - if (nf.Common.isDefinedAndNotNull(string)) { - return String(string).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); + }, + + /** + * Formats the specified property (name and value) accordingly. + * + * @argument {string} name The name of the property + * @argument {string} value The value of the property + */ + formatProperty: function (name, value) { + return '<div><span class="label">' + nf.Common.formatValue(name) + ': </span>' + nf.Common.formatValue(value) + '</div>'; + }, + + /** + * Formats the specified value accordingly. + * + * @argument {string} value The value of the property + */ + formatValue: function (value) { + if (nf.Common.isDefinedAndNotNull(value)) { + if (value === '') { + return '<span class="blank">Empty string set</span>'; + } else { + return nf.Common.escapeHtml(value); + } } else { - return ''; + return '<span class="unset">No value set</span>'; } - }; - }()), - - /** - * Determines if the specified property is sensitive. - * - * @argument {object} propertyDescriptor The property descriptor - */ - isSensitiveProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.sensitive === true; - } else { - return false; - } - }, - - /** - * Determines if the specified property is required. - * - * @param {object} propertyDescriptor The property descriptor - */ - isRequiredProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.required === true; - } else { - return false; - } - }, - - /** - * Determines if the specified property is required. - * - * @param {object} propertyDescriptor The property descriptor - */ - isDynamicProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.dynamic === true; - } else { - return false; - } - }, - - /** - * Gets the allowable values for the specified property. - * - * @argument {object} propertyDescriptor The property descriptor - */ - getAllowableValues: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.allowableValues; - } else { - return null; - } - }, - - /** - * Returns whether the specified property supports EL. - * - * @param {object} propertyDescriptor The property descriptor - */ - supportsEl: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.supportsEl === true; - } else { - return false; - } - }, - - /** - * Creates a form inline in order to submit the specified params to the specified URL - * using the specified method. - * - * @param {string} url The URL - * @param {object} params An object with the params to include in the submission - */ - post: function (url, params) { - // temporarily override beforeunload - var previousBeforeUnload = window.onbeforeunload; - window.onbeforeunload = null; - - // create a form for submission - var form = $('<form></form>').attr({ - 'method': 'POST', - 'action': url, - 'style': 'display: none;' - }); - - // add each parameter when specified - if (nf.Common.isDefinedAndNotNull(params)) { - $.each(params, function (name, value) { - $('<textarea></textarea>').attr('name', name).val(value).appendTo(form); - }); - } + }, - // submit the form and clean up - form.appendTo('body').submit().remove(); + /** + * HTML escapes the specified string. If the string is null + * or undefined, an empty string is returned. + * + * @returns {string} + */ + escapeHtml: (function () { + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; - // restore previous beforeunload if necessary - if (previousBeforeUnload !== null) { - window.onbeforeunload = previousBeforeUnload; - } - }, - - /** - * Formats the specified array as an unordered list. If the array is not an - * array, null is returned. - * - * @argument {array} array The array to convert into an unordered list - */ - formatUnorderedList: function (array) { - if ($.isArray(array)) { - var ul = $('<ul class="result"></ul>'); - $.each(array, function (_, item) { - var li = $('<li></li>').appendTo(ul); - if (item instanceof jQuery) { - li.append(item); + return function (string) { + if (nf.Common.isDefinedAndNotNull(string)) { + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); } else { - li.text(item); + return ''; } + }; + }()), + + /** + * Determines if the specified property is sensitive. + * + * @argument {object} propertyDescriptor The property descriptor + */ + isSensitiveProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.sensitive === true; + } else { + return false; + } + }, + + /** + * Determines if the specified property is required. + * + * @param {object} propertyDescriptor The property descriptor + */ + isRequiredProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.required === true; + } else { + return false; + } + }, + + /** + * Determines if the specified property is required. + * + * @param {object} propertyDescriptor The property descriptor + */ + isDynamicProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.dynamic === true; + } else { + return false; + } + }, + + /** + * Gets the allowable values for the specified property. + * + * @argument {object} propertyDescriptor The property descriptor + */ + getAllowableValues: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.allowableValues; + } else { + return null; + } + }, + + /** + * Returns whether the specified property supports EL. + * + * @param {object} propertyDescriptor The property descriptor + */ + supportsEl: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.supportsEl === true; + } else { + return false; + } + }, + + /** + * Creates a form inline in order to submit the specified params to the specified URL + * using the specified method. + * + * @param {string} url The URL + * @param {object} params An object with the params to include in the submission + */ + post: function (url, params) { + // temporarily override beforeunload + var previousBeforeUnload = window.onbeforeunload; + window.onbeforeunload = null; + + // create a form for submission + var form = $('<form></form>').attr({ + 'method': 'POST', + 'action': url, + 'style': 'display: none;' }); - return ul; - } else { - return null; - } - }, - - /** - * Extracts the contents of the specified str after the strToFind. If the - * strToFind is not found or the last part of the str, an empty string is - * returned. - * - * @argument {string} str The full string - * @argument {string} strToFind The substring to find - */ - substringAfterLast: function (str, strToFind) { - var result = ''; - var indexOfStrToFind = str.lastIndexOf(strToFind); - if (indexOfStrToFind >= 0) { - var indexAfterStrToFind = indexOfStrToFind + strToFind.length; - if (indexAfterStrToFind < str.length) { - result = str.substr(indexAfterStrToFind); + + // add each parameter when specified + if (nf.Common.isDefinedAndNotNull(params)) { + $.each(params, function (name, value) { + $('<textarea></textarea>').attr('name', name).val(value).appendTo(form); + }); } - } - return result; - }, - - /** - * Updates the mouse pointer. - * - * @argument {string} domId The id of the element for the new cursor style - * @argument {boolean} isMouseOver Whether or not the mouse is over the element - */ - setCursor: function (domId, isMouseOver) { - if (isMouseOver) { - $('#' + domId).addClass('pointer'); - } else { - $('#' + domId).removeClass('pointer'); - } - }, - - /** - * Constants for time duration formatting. - */ - MILLIS_PER_DAY: 86400000, - MILLIS_PER_HOUR: 3600000, - MILLIS_PER_MINUTE: 60000, - MILLIS_PER_SECOND: 1000, - - /** - * Formats the specified duration. - * - * @param {integer} duration in millis - */ - formatDuration: function (duration) { - // don't support sub millisecond resolution - duration = duration < 1 ? 0 : duration; - - // determine the number of days in the specified duration - var days = duration / nf.Common.MILLIS_PER_DAY; - days = days >= 1 ? parseInt(days, 10) : 0; - duration %= nf.Common.MILLIS_PER_DAY; - - // remaining duration should be less than 1 day, get number of hours - var hours = duration / nf.Common.MILLIS_PER_HOUR; - hours = hours >= 1 ? parseInt(hours, 10) : 0; - duration %= nf.Common.MILLIS_PER_HOUR; - - // remaining duration should be less than 1 hour, get number of minutes - var minutes = duration / nf.Common.MILLIS_PER_MINUTE; - minutes = minutes >= 1 ? parseInt(minutes, 10) : 0; - duration %= nf.Common.MILLIS_PER_MINUTE; - - // remaining duration should be less than 1 minute, get number of seconds - var seconds = duration / nf.Common.MILLIS_PER_SECOND; - seconds = seconds >= 1 ? parseInt(seconds, 10) : 0; - - // remaining duration is the number millis (don't support sub millisecond resolution) - duration = Math.floor(duration % nf.Common.MILLIS_PER_SECOND); - - // format the time - var time = nf.Common.pad(hours, 2, '0') + - ':' + - nf.Common.pad(minutes, 2, '0') + - ':' + - nf.Common.pad(seconds, 2, '0') + - '.' + - nf.Common.pad(duration, 3, '0'); - - // only include days if appropriate - if (days > 0) { - return days + ' days and ' + time; - } else { - return time; - } - }, - - /** - * Constants for formatting data size. - */ - BYTES_IN_KILOBYTE: 1024, - BYTES_IN_MEGABYTE: 1048576, - BYTES_IN_GIGABYTE: 1073741824, - BYTES_IN_TERABYTE: 1099511627776, - - /** - * Formats the specified number of bytes into a human readable string. - * - * @param {integer} dataSize - * @returns {string} - */ - formatDataSize: function (dataSize) { - // check terabytes - var dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_TERABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " TB"; - } - // check gigabytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_GIGABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " GB"; - } + // submit the form and clean up + form.appendTo('body').submit().remove(); - // check megabytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_MEGABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " MB"; - } + // restore previous beforeunload if necessary + if (previousBeforeUnload !== null) { + window.onbeforeunload = previousBeforeUnload; + } + }, - // check kilobytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_KILOBYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " KB"; - } + /** + * Formats the specified array as an unordered list. If the array is not an + * array, null is returned. + * + * @argument {array} array The array to convert into an unordered list + */ + formatUnorderedList: function (array) { + if ($.isArray(array)) { + var ul = $('<ul class="result"></ul>'); + $.each(array, function (_, item) { + var li = $('<li></li>').appendTo(ul); + if (item instanceof jQuery) { + li.append(item); + } else { + li.text(item); + } + }); + return ul; + } else { + return null; + } + }, - // default to bytes - return parseFloat(dataSize).toFixed(2) + " bytes"; - }, - - /** - * Formats the specified integer as a string (adding commas). At this - * point this does not take into account any locales. - * - * @param {integer} integer - */ - formatInteger: function (integer) { - var string = integer + ''; - var regex = /(\d+)(\d{3})/; - while (regex.test(string)) { - string = string.replace(regex, '$1' + ',' + '$2'); - } - return string; - }, - - /** - * Formats the specified float using two demical places. - * - * @param {float} f - */ - formatFloat: function (f) { - if (nf.Common.isUndefinedOrNull(f)) { - return 0.00 + ''; - } - return f.toFixed(2) + ''; - }, - - /** - * Pads the specified value to the specified width with the specified character. - * If the specified value is already wider than the specified width, the original - * value is returned. - * - * @param {integer} value - * @param {integer} width - * @param {string} character - * @returns {string} - */ - pad: function (value, width, character) { - var s = value + ''; - - // pad until wide enough - while (s.length < width) { - s = character + s; - } + /** + * Extracts the contents of the specified str after the strToFind. If the + * strToFind is not found or the last part of the str, an empty string is + * returned. + * + * @argument {string} str The full string + * @argument {string} strToFind The substring to find + */ + substringAfterLast: function (str, strToFind) { + var result = ''; + var indexOfStrToFind = str.lastIndexOf(strToFind); + if (indexOfStrToFind >= 0) { + var indexAfterStrToFind = indexOfStrToFind + strToFind.length; + if (indexAfterStrToFind < str.length) { + result = str.substr(indexAfterStrToFind); + } + } + return result; + }, - return s; - }, - - /** - * Formats the specified DateTime. - * - * @param {Date} date - * @returns {String} - */ - formatDateTime: function (date) { - return nf.Common.pad(date.getMonth() + 1, 2, '0') + - '/' + - nf.Common.pad(date.getDate(), 2, '0') + - '/' + - nf.Common.pad(date.getFullYear(), 2, '0') + - ' ' + - nf.Common.pad(date.getHours(), 2, '0') + - ':' + - nf.Common.pad(date.getMinutes(), 2, '0') + - ':' + - nf.Common.pad(date.getSeconds(), 2, '0') + - '.' + - nf.Common.pad(date.getMilliseconds(), 3, '0'); - }, - - /** - * Parses the specified date time into a Date object. The resulting - * object does not account for timezone and should only be used for - * performing relative comparisons. - * - * @param {string} rawDateTime - * @returns {Date} - */ - parseDateTime: function (rawDateTime) { - // handle non date values - if (!nf.Common.isDefinedAndNotNull(rawDateTime)) { - return new Date(); - } - if (rawDateTime === 'No value set') { - return new Date(); - } - if (rawDateTime === 'Empty string set') { - return new Date(); - } + /** + * Updates the mouse pointer. + * + * @argument {string} domId The id of the element for the new cursor style + * @argument {boolean} isMouseOver Whether or not the mouse is over the element + */ + setCursor: function (domId, isMouseOver) { + if (isMouseOver) { + $('#' + domId).addClass('pointer'); + } else { + $('#' + domId).removeClass('pointer'); + } + }, - // parse the date time - var dateTime = rawDateTime.split(/ /); + /** + * Constants for time duration formatting. + */ + MILLIS_PER_DAY: 86400000, + MILLIS_PER_HOUR: 3600000, + MILLIS_PER_MINUTE: 60000, + MILLIS_PER_SECOND: 1000, - // ensure the correct number of tokens - if (dateTime.length !== 3) { - return new Date(); - } + /** + * Formats the specified duration. + * + * @param {integer} duration in millis + */ + formatDuration: function (duration) { + // don't support sub millisecond resolution + duration = duration < 1 ? 0 : duration; - // get the date and time - var date = dateTime[0].split(/\//); - var time = dateTime[1].split(/:/); + // determine the number of days in the specified duration + var days = duration / nf.Common.MILLIS_PER_DAY; + days = days >= 1 ? parseInt(days, 10) : 0; + duration %= nf.Common.MILLIS_PER_DAY; - // ensure the correct number of tokens - if (date.length !== 3 || time.length !== 3) { - return new Date(); - } + // remaining duration should be less than 1 day, get number of hours + var hours = duration / nf.Common.MILLIS_PER_HOUR; + hours = hours >= 1 ? parseInt(hours, 10) : 0; + duration %= nf.Common.MILLIS_PER_HOUR; - // detect if there is millis - var seconds = time[2].split(/\./); - if (seconds.length === 2) { - return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)); - } else { - return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(time[2], 10), 0); - } - }, - - /** - * Parses the specified duration and returns the total number of millis. - * - * @param {string} rawDuration - * @returns {number} The number of millis - */ - parseDuration: function (rawDuration) { - var duration = rawDuration.split(/:/); - - // ensure the appropriate number of tokens - if (duration.length !== 3) { - return 0; - } + // remaining duration should be less than 1 hour, get number of minutes + var minutes = duration / nf.Common.MILLIS_PER_MINUTE; + minutes = minutes >= 1 ? parseInt(minutes, 10) : 0; + duration %= nf.Common.MILLIS_PER_MINUTE; - // detect if there is millis - var seconds = duration[2].split(/\./); - if (seconds.length === 2) { - return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)).getTime(); - } else { - return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(duration[2], 10), 0).getTime(); - } - }, - - /** - * Parses the specified size. - * - * @param {string} rawSize - * @returns {int} - */ - parseSize: function (rawSize) { - var tokens = rawSize.split(/ /); - var size = parseFloat(tokens[0].replace(/,/g, '')); - var units = tokens[1]; - - if (units === 'KB') { - return size * 1024; - } else if (units === 'MB') { - return size * 1024 * 1024; - } else if (units === 'GB') { - return size * 1024 * 1024 * 1024; - } else if (units === 'TB') { - return size * 1024 * 1024 * 1024 * 1024; - } else { - return size; - } - }, - - /** - * Parses the specified count. - * - * @param {string} rawCount - * @returns {int} - */ - parseCount: function (rawCount) { - // extract the count - var count = rawCount.split(/ /, 1); - - // ensure the string was split successfully - if (count.length !== 1) { - return 0; - } + // remaining duration should be less than 1 minute, get number of seconds + var seconds = duration / nf.Common.MILLIS_PER_SECOND; + seconds = seconds >= 1 ? parseInt(seconds, 10) : 0; - // convert the count to an integer - var intCount = parseInt(count[0].replace(/,/g, ''), 10); + // remaining duration is the number millis (don't support sub millisecond resolution) + duration = Math.floor(duration % nf.Common.MILLIS_PER_SECOND); - // ensure it was parsable as an integer - if (isNaN(intCount)) { - return 0; - } - return intCount; - }, - - /** - * Determines if the specified object is defined and not null. - * - * @argument {object} obj The object to test - */ - isDefinedAndNotNull: function (obj) { - return !nf.Common.isUndefined(obj) && !nf.Common.isNull(obj); - }, - - /** - * Determines if the specified object is undefined or null. - * - * @param {object} obj The object to test - */ - isUndefinedOrNull: function (obj) { - return nf.Common.isUndefined(obj) || nf.Common.isNull(obj); - }, - - /** - * Determines if the specified object is undefined. - * - * @argument {object} obj The object to test - */ - isUndefined: function (obj) { - return typeof obj === 'undefined'; - }, - - /** - * Determines whether the specified string is blank (or null or undefined). - * - * @argument {string} str The string to test - */ - isBlank: function (str) { - return nf.Common.isUndefined(str) || nf.Common.isNull(str) || $.trim(str) === ''; - }, - - /** - * Determines if the specified object is null. - * - * @argument {object} obj The object to test - */ - isNull: function (obj) { - return obj === null; - }, - - /** - * Determines if the specified array is empty. If the specified arg is not an - * array, then true is returned. - * - * @argument {array} arr The array to test - */ - isEmpty: function (arr) { - return $.isArray(arr) ? arr.length === 0 : true; - }, - - /** - * Determines if these are the same bulletins. If both arguments are not - * arrays, false is returned. - * - * @param {array} bulletins - * @param {array} otherBulletins - * @returns {boolean} - */ - doBulletinsDiffer: function (bulletins, otherBulletins) { - if ($.isArray(bulletins) && $.isArray(otherBulletins)) { - if (bulletins.length === otherBulletins.length) { - for (var i = 0; i < bulletins.length; i++) { - if (bulletins[i].id !== otherBulletins[i].id) { - return true; + // format the time + var time = nf.Common.pad(hours, 2, '0') + + ':' + + nf.Common.pad(minutes, 2, '0') + + ':' + + nf.Common.pad(seconds, 2, '0') + + '.' + + nf.Common.pad(duration, 3, '0'); + + // only include days if appropriate + if (days > 0) { + return days + ' days and ' + time; + } else { + return time; + } + }, + + /** + * Constants for formatting data size. + */ + BYTES_IN_KILOBYTE: 1024, + BYTES_IN_MEGABYTE: 1048576, + BYTES_IN_GIGABYTE: 1073741824, + BYTES_IN_TERABYTE: 1099511627776, + + /** + * Formats the specified number of bytes into a human readable string. + * + * @param {integer} dataSize + * @returns {string} + */ + formatDataSize: function (dataSize) { + // check terabytes + var dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_TERABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " TB"; + } + + // check gigabytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_GIGABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " GB"; + } + + // check megabytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_MEGABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " MB"; + } + + // check kilobytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_KILOBYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " KB"; + } + + // default to bytes + return parseFloat(dataSize).toFixed(2) + " bytes"; + }, + + /** + * Formats the specified integer as a string (adding commas). At this + * point this does not take into account any locales. + * + * @param {integer} integer + */ + formatInteger: function (integer) { + var string = integer + ''; + var regex = /(\d+)(\d{3})/; + while (regex.test(string)) { + string = string.replace(regex, '$1' + ',' + '$2'); + } + return string; + }, + + /** + * Formats the specified float using two demical places. + * + * @param {float} f + */ + formatFloat: function (f) { + if (nf.Common.isUndefinedOrNull(f)) { + return 0.00 + ''; + } + return f.toFixed(2) + ''; + }, + + /** + * Pads the specified value to the specified width with the specified character. + * If the specified value is already wider than the specified width, the original + * value is returned. + * + * @param {integer} value + * @param {integer} width + * @param {string} character + * @returns {string} + */ + pad: function (value, width, character) { + var s = value + ''; + + // pad until wide enough + while (s.length < width) { + s = character + s; + } + + return s; + }, + + /** + * Formats the specified DateTime. + * + * @param {Date} date + * @returns {String} + */ + formatDateTime: function (date) { + return nf.Common.pad(date.getMonth() + 1, 2, '0') + + '/' + + nf.Common.pad(date.getDate(), 2, '0') + + '/' + + nf.Common.pad(date.getFullYear(), 2, '0') + + ' ' + + nf.Common.pad(date.getHours(), 2, '0') + + ':' + + nf.Common.pad(date.getMinutes(), 2, '0') + + ':' + + nf.Common.pad(date.getSeconds(), 2, '0') + + '.' + + nf.Common.pad(date.getMilliseconds(), 3, '0'); + }, + + /** + * Parses the specified date time into a Date object. The resulting + * object does not account for timezone and should only be used for + * performing relative comparisons. + * + * @param {string} rawDateTime + * @returns {Date} + */ + parseDateTime: function (rawDateTime) { + // handle non date values + if (!nf.Common.isDefinedAndNotNull(rawDateTime)) { + return new Date(); + } + if (rawDateTime === 'No value set') { + return new Date(); + } + if (rawDateTime === 'Empty string set') { + return new Date(); + } + + // parse the date time + var dateTime = rawDateTime.split(/ /); + + // ensure the correct number of tokens + if (dateTime.length !== 3) { + return new Date(); + } + + // get the date and time + var date = dateTime[0].split(/\//); + var time = dateTime[1].split(/:/); + + // ensure the correct number of tokens + if (date.length !== 3 || time.length !== 3) { + return new Date(); + } + + // detect if there is millis + var seconds = time[2].split(/\./); + if (seconds.length === 2) { + return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)); + } else { + return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(time[2], 10), 0); + } + }, + + /** + * Parses the specified duration and returns the total number of millis. + * + * @param {string} rawDuration + * @returns {number} The number of millis + */ + parseDuration: function (rawDuration) { + var duration = rawDuration.split(/:/); + + // ensure the appropriate number of tokens + if (duration.length !== 3) { + return 0; + } + + // detect if there is millis + var seconds = duration[2].split(/\./); + if (seconds.length === 2) { + return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)).getTime(); + } else { + return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(duration[2], 10), 0).getTime(); + } + }, + + /** + * Parses the specified size. + * + * @param {string} rawSize + * @returns {int} + */ + parseSize: function (rawSize) { + var tokens = rawSize.split(/ /); + var size = parseFloat(tokens[0].replace(/,/g, '')); + var units = tokens[1]; + + if (units === 'KB') { + return size * 1024; + } else if (units === 'MB') { + return size * 1024 * 1024; + } else if (units === 'GB') { + return size * 1024 * 1024 * 1024; + } else if (units === 'TB') { + return size * 1024 * 1024 * 1024 * 1024; + } else { + return size; + } + }, + + /** + * Parses the specified count. + * + * @param {string} rawCount + * @returns {int} + */ + parseCount: function (rawCount) { + // extract the count + var count = rawCount.split(/ /, 1); + + // ensure the string was split successfully + if (count.length !== 1) { + return 0; + } + + // convert the count to an integer + var intCount = parseInt(count[0].replace(/,/g, ''), 10); + + // ensure it was parsable as an integer + if (isNaN(intCount)) { + return 0; + } + return intCount; + }, + + /** + * Determines if the specified object is defined and not null. + * + * @argument {object} obj The object to test + */ + isDefinedAndNotNull: function (obj) { + return !nf.Common.isUndefined(obj) && !nf.Common.isNull(obj); + }, + + /** + * Determines if the specified object is undefined or null. + * + * @param {object} obj The object to test + */ + isUndefinedOrNull: function (obj) { + return nf.Common.isUndefined(obj) || nf.Common.isNull(obj); + }, + + /** + * Determines if the specified object is undefined. + * + * @argument {object} obj The object to test + */ + isUndefined: function (obj) { + return typeof obj === 'undefined'; + }, + + /** + * Determines whether the specified string is blank (or null or undefined). + * + * @argument {string} str The string to test + */ + isBlank: function (str) { + return nf.Common.isUndefined(str) || nf.Common.isNull(str) || $.trim(str) === ''; + }, + + /** + * Determines if the specified object is null. + * + * @argument {object} obj The object to test + */ + isNull: function (obj) { + return obj === null; + }, + + /** + * Determines if the specified array is empty. If the specified arg is not an + * array, then true is returned. + * + * @argument {array} arr The array to test + */ + isEmpty: function (arr) { + return $.isArray(arr) ? arr.length === 0 : true; + }, + + /** + * Determines if these are the same bulletins. If both arguments are not + * arrays, false is returned. + * + * @param {array} bulletins + * @param {array} otherBulletins + * @returns {boolean} + */ + doBulletinsDiffer: function (bulletins, otherBulletins) { + if ($.isArray(bulletins) && $.isArray(otherBulletins)) { + if (bulletins.length === otherBulletins.length) { + for (var i = 0; i < bulletins.length; i++) { + if (bulletins[i].id !== otherBulletins[i].id) { + return true; + } } + } else { + return true; } - } else { + } else if ($.isArray(bulletins) || $.isArray(otherBulletins)) { return true; } - } else if ($.isArray(bulletins) || $.isArray(otherBulletins)) { - return true; + return false; + }, + + /** + * Formats the specified bulletin list. + * + * @argument {array} bulletins The bulletins + * @return {array} The jQuery objects + */ + getFormattedBulletins: function (bulletins) { + var formattedBulletins = []; + $.each(bulletins, function (j, bulletin) { + // format the node address + var nodeAddress = ''; + if (nf.Common.isDefinedAndNotNull(bulletin.nodeAddress)) { + nodeAddress = '- ' + nf.Common.escapeHtml(bulletin.nodeAddress) + ' - '; + } + + // set the bulletin message (treat as text) + var bulletinMessage = $('<pre></pre>').css({ + 'white-space': 'pre-wrap' + }).text(bulletin.message); + + // create the bulletin message + var formattedBulletin = $('<div>' + + nf.Common.escapeHtml(bulletin.timestamp) + ' ' + + nodeAddress + ' ' + + '<b>' + nf.Common.escapeHtml(bulletin.level) + '</b> ' + + '</div>').append(bulletinMessage); + + formattedBulletins.push(formattedBulletin); + }); + return formattedBulletins; } - return false; - }, - - /** - * Formats the specified bulletin list. - * - * @argument {array} bulletins The bulletins - * @return {array} The jQuery objects - */ - getFormattedBulletins: function (bulletins) { - var formattedBulletins = []; - $.each(bulletins, function (j, bulletin) { - // format the node address - var nodeAddress = ''; - if (nf.Common.isDefinedAndNotNull(bulletin.nodeAddress)) { - nodeAddress = '- ' + nf.Common.escapeHtml(bulletin.nodeAddress) + ' - '; - } - - // set the bulletin message (treat as text) - var bulletinMessage = $('<pre></pre>').css({ - 'white-space': 'pre-wrap' - }).text(bulletin.message); - - // create the bulletin message - var formattedBulletin = $('<div>' + - nf.Common.escapeHtml(bulletin.timestamp) + ' ' + - nodeAddress + ' ' + - '<b>' + nf.Common.escapeHtml(bulletin.level) + '</b> ' + - '</div>').append(bulletinMessage); - - formattedBulletins.push(formattedBulletin); - }); - return formattedBulletins; - } -}; + }; +}()); http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js index 13f5cf3..9a926ef 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js @@ -23,15 +23,6 @@ $(document).ready(function () { // configure the ok dialog $('#nf-ok-dialog').modal({ - buttons: [{ - buttonText: 'Ok', - handler: { - click: function () { - // close the dialog - $('#nf-ok-dialog').modal('hide'); - } - } - }], handler: { close: function () { // clear the content @@ -78,6 +69,20 @@ nf.Dialog = (function () { var content = $('<p></p>').append(options.dialogContent); $('#nf-ok-dialog-content').append(content); + // update the button model + $('#nf-ok-dialog').modal('setButtonModel', [{ + buttonText: 'Ok', + handler: { + click: function () { + // close the dialog + $('#nf-ok-dialog').modal('hide'); + if (typeof options.okHandler === 'function') { + options.okHandler.call(this); + } + } + } + }]); + // show the dialog $('#nf-ok-dialog').modal('setHeaderText', options.headerText).modal('setOverlayBackground', options.overlayBackground).modal('show'); },
