AMBARI-13692. Refactor HostProgressPopup (onechiporenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/76dd478e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/76dd478e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/76dd478e Branch: refs/heads/branch-dev-patch-upgrade Commit: 76dd478e5d701ae2c64989d5e07a9caada3ddfb6 Parents: 6321a0d Author: Oleg Nechiporenko <[email protected]> Authored: Tue Nov 3 14:50:27 2015 +0200 Committer: Oleg Nechiporenko <[email protected]> Committed: Tue Nov 3 14:50:27 2015 +0200 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 1 + ambari-web/app/utils/host_progress_popup.js | 1246 +++++++----------- ambari-web/app/views.js | 1 + .../common/host_progress_popup_body_view.js | 749 +++++++++++ .../test/utils/host_progress_popup_test.js | 112 ++ .../host_progress_popup_body_view_test.js | 58 + 6 files changed, 1388 insertions(+), 779 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/76dd478e/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index cb292a5..6617c97 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -266,6 +266,7 @@ var files = [ 'test/views/main/admin/highAvailability/nameNode/step6_view_test', 'test/views/main/admin/highAvailability/nameNode/step8_view_test', 'test/views/main/admin/highAvailability/nameNode/wizard_view_test', + 'test/views/common/host_progress_popup_body_view_test', 'test/views/common/configs/config_history_flow_test', 'test/views/common/configs/overriddenProperty_view_test', 'test/views/common/configs/service_config_view_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/76dd478e/ambari-web/app/utils/host_progress_popup.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/host_progress_popup.js b/ambari-web/app/utils/host_progress_popup.js index e834bbe..831baeb 100644 --- a/ambari-web/app/utils/host_progress_popup.js +++ b/ambari-web/app/utils/host_progress_popup.js @@ -19,37 +19,112 @@ var App = require('app'); var batchUtils = require('utils/batch_scheduled_requests'); var date = require('utils/date/date'); +var dataUtils = require('utils/data_manipulation'); + +/** + * Host information shown in the operations popup + * @typedef {Em.Object} wrappedHost + * @property {string} name + * @property {string} publicName + * @property {string} displayName + * @property {number} progress + * @property {boolean} isInProgress + * @property {string} serviceName + * @property {string} status + * @property {number} isVisible + * @property {string} icon + * @property {string} barColor + * @property {string} barWidth + */ + +/** + * Task information shown in the operations popup + * @typedef {Em.Object} wrappedTask + * @property {string} id + * @property {string} hostName + * @property {string} command + * @property {string} commandDetail + * @property {string} status + * @property {string} role + * @property {string} stderr + * @property {string} stdout + * @property {number} request_id + * @property {boolean} isVisible + * @property {string} startTime + * @property {string} duration + * @property {string} icon + */ + +/** + * Service information shown in the operations popup + * @typedef {Em.Object} wrappedService + * @property {string} id + * @property {string} displayName + * @property {string} progress + * @property {string} status + * @property {boolean} isRunning + * @property {string} name + * @property {boolean} isVisible + * @property {string} startTime + * @property {string} duration + * @property {string} icon + * @property {string} barColor + * @property {boolean} isInProgress + * @property {string} barWidth + * @property {number} sourceRequestScheduleId + * @property {string} contextCommand + */ /** * App.HostPopup is for the popup that shows up upon clicking already-performed or currently-in-progress operations + * Allows to abort executing operations + * + * @type {Em.Object} + * @class {HostPopup} */ App.HostPopup = Em.Object.create({ name: 'hostPopup', + /** + * @type {object[]} + */ servicesInfo: [], + + /** + * @type {?wrappedHost[]} + */ hosts: null, + + /** + * @type {?object[]} + */ inputData: null, /** * @type {string} */ - serviceName: "", + serviceName: '', /** - * @type {Number} + * @type {?Number} */ currentServiceId: null, + + /** + * @type {?Number} + */ previousServiceId: null, /** * @type {string} */ - popupHeaderName: "", + popupHeaderName: '', operationInfo: null, + /** - * @type {App.Controller} + * @type {?App.Controller} */ dataSourceController: null, @@ -59,15 +134,18 @@ App.HostPopup = Em.Object.create({ isBackgroundOperations: false, /** - * @type {string} + * @type {?string} */ currentHostName: null, /** - * @type {App.ModalPopup} + * @type {?App.ModalPopup} */ isPopup: null, + /** + * @type {object} + */ detailedProperties: { stdout: 'stdout', stderr: 'stderr', @@ -75,6 +153,35 @@ App.HostPopup = Em.Object.create({ errorLog: 'error_log' }, + /** + * @type {object} + */ + barColorMap: { + 'FAILED': 'progress-danger', + 'ABORTED': 'progress-warning', + 'TIMEDOUT': 'progress-warning', + 'IN_PROGRESS': 'progress-info', + 'COMPLETED': 'progress-success' + }, + + /** + * map to get css class with styles by service status + * + * @type {object} + */ + statusesStyleMap: { + 'FAILED': ['FAILED', 'icon-exclamation-sign', 'progress-danger', false], + 'ABORTED': ['ABORTED', 'icon-minus', 'progress-warning', false], + 'TIMEDOUT': ['TIMEDOUT', 'icon-time', 'progress-warning', false], + 'IN_PROGRESS': ['IN_PROGRESS', 'icon-cogs', 'progress-info', true], + 'COMPLETED': ['SUCCESS', 'icon-ok', 'progress-success', false] + }, + + /** + * View with "Abort Request"-button + * + * @type {Em.View} + */ abortIcon: Em.View.extend({ tagName: 'i', classNames: ['abort-icon', 'icon-remove-circle', 'pointer'], @@ -87,24 +194,36 @@ App.HostPopup = Em.Object.create({ placement: "top", title: Em.I18n.t('hostPopup.bgop.abortRequest.title') }); + }, + willDestroyElement: function () { + $(this.get('element')).tooltip('destroy'); } }), + /** + * View with status icon (and tooltip on it) + * + * @type {Em.View} + */ statusIcon: Em.View.extend({ tagName: 'i', classNames: ["service-status"], classNameBindings: ['servicesInfo.status', 'servicesInfo.icon', 'additionalClass'], attributeBindings: ['data-original-title'], - 'data-original-title': function() { + 'data-original-title': function () { return this.get('servicesInfo.status'); }.property('servicesInfo.status'), didInsertElement: function () { App.tooltip($(this.get('element'))); + }, + willDestroyElement: function () { + $(this.get('element')).tooltip('destroy'); } }), /** * Determines if background operation can be aborted depending on its status + * * @param status * @returns {boolean} */ @@ -115,13 +234,15 @@ App.HostPopup = Em.Object.create({ /** * Send request to abort operation + * + * @method abortRequest */ abortRequest: function (serviceInfo) { var requestName = serviceInfo.get('name'); var self = this; App.showConfirmationPopup(function () { serviceInfo.set('isAbortable', false); - App.ajax.send({ + return App.ajax.send({ name: 'background_operations.abort_request', sender: self, data: { @@ -138,30 +259,36 @@ App.HostPopup = Em.Object.create({ /** * Method called on successful sending request to abort operation + * + * @return {App.ModalPopup} + * @method abortRequestSuccessCallback */ abortRequestSuccessCallback: function (response, request, data) { - App.ModalPopup.show({ + return App.ModalPopup.show({ header: Em.I18n.t('hostPopup.bgop.abortRequest.modal.header'), - bodyClass: Em.View.extend({ - template: Em.Handlebars.compile(Em.I18n.t('hostPopup.bgop.abortRequest.modal.body').format(data.requestName)) - }), + body: Em.I18n.t('hostPopup.bgop.abortRequest.modal.body').format(data.requestName), secondary: null }); }, /** * Method called on unsuccessful sending request to abort operation + * + * @method abortRequestErrorCallback */ abortRequestErrorCallback: function (xhr, textStatus, error, opt, data) { data.serviceInfo.set('isAbortable', this.isAbortableByStatus(data.serviceInfo.status)); App.ajax.defaultErrorHandler(xhr, opt.url, 'PUT', xhr.status); }, + /** * Entering point of this component + * * @param {String} serviceName * @param {Object} controller * @param {Boolean} isBackgroundOperations * @param {Integer} requestId + * @method initPopup */ initPopup: function (serviceName, controller, isBackgroundOperations, requestId) { if (!isBackgroundOperations) { @@ -169,12 +296,15 @@ App.HostPopup = Em.Object.create({ this.set("popupHeaderName", serviceName); } - this.set('currentServiceId', requestId); - this.set("serviceName", serviceName); - this.set("dataSourceController", controller); - this.set("isBackgroundOperations", isBackgroundOperations); - this.set("inputData", this.get("dataSourceController.services")); - if(isBackgroundOperations){ + this.setProperties({ + currentServiceId: requestId, + serviceName: serviceName, + dataSourceController: controller, + isBackgroundOperations: isBackgroundOperations, + inputData: this.get("dataSourceController.services") + }); + + if (isBackgroundOperations) { this.onServiceUpdate(); } else { this.onHostUpdate(); @@ -184,172 +314,127 @@ App.HostPopup = Em.Object.create({ /** * clear info popup data + * + * @method clearHostPopup */ clearHostPopup: function () { - this.set('servicesInfo', []); - this.set('hosts', null); - this.set('inputData', null); - this.set('serviceName', ""); - this.set('currentServiceId', null); - this.set('previousServiceId', null); - this.set('popupHeaderName', ""); - this.set('dataSourceController', null); - this.set('currentHostName', null); + this.setProperties({ + servicesInfo: [], + host: null, + inputData: null, + serviceName: '', + currentServiceId: null, + previousServiceId: null, + popupHeaderName: '', + dataSourceController: null, + currentHostName: null + }); this.get('isPopup') ? this.get('isPopup').remove() : null; }, /** * Depending on tasks status - * @param {Array} tasks - * @return {Array} [Status, Icon type, Progressbar color, is IN_PROGRESS] + * + * @param {object[]} tasks + * @return {*[]} [Status, Icon type, Progressbar color, is IN_PROGRESS] + * @method getStatus */ - getStatus: function(tasks){ + getStatus: function (tasks) { var isCompleted = true; - var status; var tasksLength = tasks.length; - var isFailed = false; - var isAborted = false; - var isTimedout = false; - var isInProgress = false; for (var i = 0; i < tasksLength; i++) { - if (tasks[i].Tasks.status !== 'COMPLETED') { + var taskStatus = tasks[i].Tasks.status; + if (taskStatus !== 'COMPLETED') { isCompleted = false; } - if(tasks[i].Tasks.status === 'FAILED'){ - isFailed = true; + if (taskStatus === 'FAILED') { + return ['FAILED', 'icon-exclamation-sign', 'progress-danger', false]; } - if (tasks[i].Tasks.status === 'ABORTED') { - isAborted = true; + if (taskStatus === 'ABORTED') { + return ['ABORTED', 'icon-minus', 'progress-warning', false]; } - if (tasks[i].Tasks.status === 'TIMEDOUT') { - isTimedout = true; + if (taskStatus === 'TIMEDOUT') { + return ['TIMEDOUT', 'icon-time', 'progress-warning', false]; } - if (tasks[i].Tasks.status === 'IN_PROGRESS') { - isInProgress = true; + if (taskStatus === 'IN_PROGRESS') { + return ['IN_PROGRESS', 'icon-cogs', 'progress-info', true] } } - if (isFailed) { - status = ['FAILED', 'icon-exclamation-sign', 'progress-danger', false]; - } else if (isAborted) { - status = ['ABORTED', 'icon-minus', 'progress-warning', false]; - } else if (isTimedout) { - status = ['TIMEDOUT', 'icon-time', 'progress-warning', false]; - } else if (isInProgress) { - status = ['IN_PROGRESS', 'icon-cogs', 'progress-info', true]; - } - if(status){ - return status; - } else if(isCompleted){ + if (isCompleted) { return ['SUCCESS', 'icon-ok', 'progress-success', false]; - } else { - return ['PENDING', 'icon-cog', 'progress-info', true]; } + return ['PENDING', 'icon-cog', 'progress-info', true]; }, /** * Progress of host or service depending on tasks status - * @param {Array} tasks + * If no tasks, progress is 0 + * + * @param {?object[]} tasks * @return {Number} percent of completion + * @method getProgress */ getProgress: function (tasks) { - if (!tasks || tasks.length === 0) return 0; - - var completedActions = 0; - var queuedActions = 0; - var inProgressActions = 0; - - tasks.forEach(function (task) { - if (['COMPLETED', 'FAILED', 'ABORTED', 'TIMEDOUT'].contains(task.Tasks.status)) { - completedActions++; - } else if (task.Tasks.status === 'QUEUED') { - queuedActions++; - } else if (task.Tasks.status === 'IN_PROGRESS') { - inProgressActions++; - } - }); - return Math.ceil(((queuedActions * 0.09) + (inProgressActions * 0.35) + completedActions ) / tasks.length * 100); + if (!tasks || !tasks.length) { + return 0; + } + + var groupedByStatus = dataUtils.groupPropertyValues(tasks, 'Tasks.status'); + + var completedActions = Em.getWithDefault(groupedByStatus, 'COMPLETED.length', 0) + + Em.getWithDefault(groupedByStatus, 'FAILED.length', 0) + + Em.getWithDefault(groupedByStatus, 'ABORTED.length', 0) + + Em.getWithDefault(groupedByStatus, 'TIMEDOUT.length', 0); + var queuedActions = Em.getWithDefault(groupedByStatus, 'QUEUED.length', 0); + var inProgressActions = Em.getWithDefault(groupedByStatus, 'IN_PROGRESS.length', 0); + + return Math.ceil((queuedActions * 0.09 + inProgressActions * 0.35 + completedActions ) / tasks.length * 100); }, /** * Count number of operations for select box options - * @param {Object[]} obj - * @param {Object[]} categories + * + * @param {?Object[]} obj + * @param {progressPopupCategoryObject[]} categories + * @method setSelectCount */ setSelectCount: function (obj, categories) { - if (!obj) return; - var countAll = obj.length; - var countPending = 0; - var countInProgress = 0; - var countFailed = 0; - var countCompleted = 0; - var countAborted = 0; - var countTimedout = 0; - obj.forEach(function(item){ - switch (item.status){ - case 'pending': - countPending++; - break; - case 'queued': - countPending++; - break; - case 'in_progress': - countInProgress++; - break; - case 'failed': - countFailed++; - break; - case 'success': - countCompleted++; - break; - case 'completed': - countCompleted++; - break; - case 'aborted': - countAborted++; - break; - case 'timedout': - countTimedout++; - break; - } - }); - - categories.findProperty("value", 'all').set("count", countAll); - categories.findProperty("value", 'pending').set("count", countPending); - categories.findProperty("value", 'in_progress').set("count", countInProgress); - categories.findProperty("value", 'failed').set("count", countFailed); - categories.findProperty("value", 'completed').set("count", countCompleted); - categories.findProperty("value", 'aborted').set("count", countAborted); - categories.findProperty("value", 'timedout').set("count", countTimedout); + if (!obj) { + return; + } + var groupedByStatus = dataUtils.groupPropertyValues(obj, 'status'); + + categories.findProperty("value", 'all').set("count", obj.length); + categories.findProperty("value", 'pending').set("count", Em.getWithDefault(groupedByStatus, 'pending.length', 0) + Em.getWithDefault(groupedByStatus, 'queued.length', 0)); + categories.findProperty("value", 'in_progress').set("count", Em.getWithDefault(groupedByStatus, 'in_progress.length', 0)); + categories.findProperty("value", 'failed').set("count", Em.getWithDefault(groupedByStatus, 'failed.length', 0)); + categories.findProperty("value", 'completed').set("count", Em.getWithDefault(groupedByStatus, 'success.length', 0) + Em.getWithDefault(groupedByStatus, 'completed.length', 0)); + categories.findProperty("value", 'aborted').set("count", Em.getWithDefault(groupedByStatus, 'aborted.length', 0)); + categories.findProperty("value", 'timedout').set("count", Em.getWithDefault(groupedByStatus, 'timedout.length', 0)); }, /** * For Background operation popup calculate number of running Operations, and set popup header + * * @param {bool} isServiceListHidden + * @method setBackgroundOperationHeader */ setBackgroundOperationHeader: function (isServiceListHidden) { if (this.get('isBackgroundOperations') && !isServiceListHidden) { - var numRunning = App.router.get('backgroundOperationsController.allOperationsCount'); + var numRunning = App.router.get('backgroundOperationsController.allOperationsCount'); this.set("popupHeaderName", numRunning + Em.I18n.t('hostPopup.header.postFix').format(numRunning == 1 ? "" : "s")); } }, - // map to get css class with styles by service status - statusesStyleMap: { - 'FAILED': ['FAILED', 'icon-exclamation-sign', 'progress-danger', false], - 'ABORTED': ['ABORTED', 'icon-minus', 'progress-warning', false], - 'TIMEDOUT': ['TIMEDOUT', 'icon-time', 'progress-warning', false], - 'IN_PROGRESS': ['IN_PROGRESS', 'icon-cogs', 'progress-info', true], - 'COMPLETED': ['SUCCESS', 'icon-ok', 'progress-success', false] - }, - /** * Create services obj data structure for popup * Set data for services + * * @param {bool} isServiceListHidden + * @method onServiceUpdate */ onServiceUpdate: function (isServiceListHidden) { if (this.get('isBackgroundOperations') && this.get("inputData")) { - var statuses = this.get('statusesStyleMap'); var servicesInfo = this.get("servicesInfo"); var currentServices = []; this.get("inputData").forEach(function (service, index) { @@ -360,7 +445,8 @@ App.HostPopup = Em.Object.create({ updatedService = existedService; if (existedService) { updatedService = this.updateService(existedService, service); - } else { + } + else { updatedService = this.createService(service); servicesInfo.insertAt(index, updatedService); } @@ -373,13 +459,16 @@ App.HostPopup = Em.Object.create({ /** * Create service object from transmitted data - * @param service + * + * @param {object} service + * @return {wrappedService} + * @method createService */ createService: function (service) { var statuses = this.get('statusesStyleMap'); var pendingStatus = ['PENDING', 'icon-cog', 'progress-info', true]; var status = statuses[service.status] || pendingStatus; - return Ember.Object.create({ + return Em.Object.create({ id: service.id, displayName: service.displayName, progress: service.progress, @@ -400,9 +489,11 @@ App.HostPopup = Em.Object.create({ /** * Update properties of existed service with new data - * @param service - * @param newData - * @returns {Ember.Object} + * + * @param {wrappedService} service + * @param {object} newData + * @returns {wrappedService} + * @method updateService */ updateService: function (service, newData) { var statuses = this.get('statusesStyleMap'); @@ -426,8 +517,10 @@ App.HostPopup = Em.Object.create({ /** * remove old requests * as API returns 10, or 20 , or 30 ...etc latest request, the requests that absent in response should be removed - * @param services - * @param currentServicesIds + * + * @param {wrappedService[]} services + * @param {number[]} currentServicesIds + * @method removeOldServices */ removeOldServices: function (services, currentServicesIds) { services.forEach(function (service, index, services) { @@ -438,15 +531,17 @@ App.HostPopup = Em.Object.create({ }, /** - * create task Ember object + * Wrap task as Ember-object + * * @param {Object} _task - * @return {Em.Object} + * @return {wrappedTask} + * @method createTask */ createTask: function (_task) { return Em.Object.create({ id: _task.Tasks.id, hostName: _task.Tasks.host_name, - command: ( _task.Tasks.command.toLowerCase() != 'service_check') ? _task.Tasks.command.toLowerCase() : '', + command: _task.Tasks.command.toLowerCase() == 'service_check' ? '' : _task.Tasks.command.toLowerCase(), commandDetail: App.format.commandDetail(_task.Tasks.command_detail, _task.Tasks.request_inputs), status: App.format.taskStatus(_task.Tasks.status), role: App.format.role(_task.Tasks.role), @@ -474,172 +569,243 @@ App.HostPopup = Em.Object.create({ /** * Create hosts and tasks data structure for popup * Set data for hosts and tasks + * + * @method onHostUpdate */ onHostUpdate: function () { + var inputData = this.get('inputData'); var self = this; - var inputData = this.get("inputData"); - if (inputData) { - var hostsArr = []; - var hostsData; - var hostsMap = {}; - - if(this.get('isBackgroundOperations') && this.get("currentServiceId")){ - //hosts popup for Background Operations - hostsData = inputData.findProperty("id", this.get("currentServiceId")); - } else if (this.get("serviceName")) { + if (this.get("inputData")) { + var hostsMap = this._getHostsMap(); + var existedHosts = self.get('hosts'); + + if (existedHosts && existedHosts.length && this.get('currentServiceId') === this.get('previousServiceId')) { + this._processingExistingHostsWithSameService(hostsMap); + } + else { + var hostsArr = this._hostMapProcessing(hostsMap); + hostsArr = hostsArr.sortProperty('name'); + hostsArr.setEach("serviceName", this.get("serviceName")); + self.set("hosts", hostsArr); + self.set('previousServiceId', this.get('currentServiceId')); + } + } + var operation = this.get('servicesInfo').findProperty('name', this.get('serviceName')); + this.set('operationInfo', !operation || (operation && operation.get('progress') == 100) ? null : operation); + }, + + /** + * Generate hosts map for further processing <code>inputData</code> + * + * @returns {object} + * @private + * @method _getHostsMap + */ + _getHostsMap: function () { + var hostsData; + var hostsMap = {}; + var inputData = this.get('inputData'); + if (this.get('isBackgroundOperations') && this.get("currentServiceId")) { + //hosts popup for Background Operations + hostsData = inputData.findProperty("id", this.get("currentServiceId")); + } + else { + if (this.get("serviceName")) { //hosts popup for Wizards hostsData = inputData.findProperty("name", this.get("serviceName")); } - if (hostsData) { - if (hostsData.hostsMap) { - //hosts data come from Background Operations as object map - hostsMap = hostsData.hostsMap; - } else if (hostsData.hosts) { + } + + if (hostsData) { + if (hostsData.hostsMap) { + //hosts data come from Background Operations as object map + hostsMap = hostsData.hostsMap; + } + else { + if (hostsData.hosts) { //hosts data come from Wizard as array - hostsData.hosts.forEach(function (_host) { - hostsMap[_host.name] = _host; - }); + hostsMap = hostsData.hosts.toMapByProperty('name'); } } - var existedHosts = self.get('hosts'); + } + return hostsMap; + }, - if (existedHosts && (existedHosts.length > 0) && this.get('currentServiceId') === this.get('previousServiceId')) { - existedHosts.forEach(function (host) { - var newHostInfo = hostsMap[host.get('name')]; - //update only hosts with changed tasks or currently opened tasks of host - if (newHostInfo && (!this.get('isBackgroundOperations') || newHostInfo.isModified || this.get('currentHostName') === host.get('name'))) { - var hostStatus = self.getStatus(newHostInfo.logTasks); - var hostProgress = self.getProgress(newHostInfo.logTasks); - host.set('status', App.format.taskStatus(hostStatus[0])); - host.set('icon', hostStatus[1]); - host.set('barColor', hostStatus[2]); - host.set('isInProgress', hostStatus[3]); - host.set('progress', hostProgress); - host.set('barWidth', "width:" + hostProgress + "%;"); - host.set('logTasks', newHostInfo.logTasks); - var existTasks = host.get('tasks'); - if (existTasks) { - newHostInfo.logTasks.forEach(function (_task) { - var existTask = existTasks.findProperty('id', _task.Tasks.id); - if (existTask) { - var status = _task.Tasks.status; - existTask.set('status', App.format.taskStatus(status)); - Em.keys(this.get('detailedProperties')).forEach(function (key) { - var value = _task.Tasks[this.get('detailedProperties')[key]]; - if (!Em.isNone(value)) { - existTask.set(key, value); - } - }, this); - existTask.set('startTime', date.startTime(_task.Tasks.start_time)); - existTask.set('duration', date.durationSummary(_task.Tasks.start_time, _task.Tasks.end_time)); - // Puts some command information to render it - var isRebalanceHDFSTask = (_task.Tasks.command === 'CUSTOM_COMMAND' && _task.Tasks.custom_command_name === 'REBALANCEHDFS'); - existTask.set('isRebalanceHDFSTask', isRebalanceHDFSTask); - if(isRebalanceHDFSTask){ - var structuredOut = _task.Tasks.structured_out; - if (!structuredOut || structuredOut === 'null') { - structuredOut = {}; - } - - var barColorMap = { - 'FAILED': 'progress-danger', - 'ABORTED': 'progress-warning', - 'TIMEDOUT': 'progress-warning', - 'IN_PROGRESS': 'progress-info', - 'COMPLETED': 'progress-success' - }; - - existTask.set('dataMoved', structuredOut['dataMoved'] || '0'); - existTask.set('dataLeft', structuredOut['dataLeft'] || '0'); - existTask.set('dataBeingMoved', structuredOut['dataBeingMoved'] || '0'); - existTask.set('barColor', barColorMap[status]); - existTask.set('isInProgress', status == 'IN_PROGRESS'); - existTask.set('isNotComplete', ['QUEUED', 'IN_PROGRESS'].contains(status)); - existTask.set('completionProgressStyle', 'width:' + (structuredOut['completePercent'] || 0) * 100 + '%;'); - - existTask.set('command', _task.Tasks.command); - existTask.set('custom_command_name', _task.Tasks.custom_command_name); - } - } else { - existTasks.pushObject(this.createTask(_task)); + /** + * + * @param {object} hostsMap + * @returns {wrappedHost[]} + * @private + * @method _hostMapProcessing + */ + _hostMapProcessing: function (hostsMap) { + var self = this; + var hostsArr = []; + for (var hostName in hostsMap) { + if (!hostsMap.hasOwnProperty(hostName)) { + continue; + } + var _host = hostsMap[hostName]; + var tasks = _host.logTasks; + var hostInfo = Em.Object.create({ + name: hostName, + publicName: _host.publicName, + displayName: function () { + return this.get('name').length < 43 ? this.get('name') : (this.get('name').substr(0, 40) + '...'); + }.property('name'), + progress: 0, + status: App.format.taskStatus("PENDING"), + serviceName: _host.serviceName, + isVisible: true, + icon: "icon-cog", + barColor: "progress-info", + barWidth: "width:0%;" + }); + + if (tasks.length) { + tasks = tasks.sortProperty('Tasks.id'); + var hostStatus = self.getStatus(tasks); + var hostProgress = self.getProgress(tasks); + hostInfo.setProperties({ + status: App.format.taskStatus(hostStatus[0]), + icon: hostStatus[1], + barColor: hostStatus[2], + isInProgress: hostStatus[3], + progress: hostProgress, + barWidth: "width:" + hostProgress + "%;" + }); + } + hostInfo.set('logTasks', tasks); + hostsArr.push(hostInfo); + } + return hostsArr; + }, + + /** + * + * @param {object} hostsMap + * @private + * @method _processingExistingHostsWithSameService + */ + _processingExistingHostsWithSameService: function (hostsMap) { + var self = this; + var barColorMap = this.get('barColorMap'); + var existedHosts = self.get('hosts'); + var detailedProperties = this.get('detailedProperties'); + var detailedPropertiesKeys = Em.keys(detailedProperties); + existedHosts.forEach(function (host) { + var newHostInfo = hostsMap[host.get('name')]; + //update only hosts with changed tasks or currently opened tasks of host + var hostShouldBeUpdated = !this.get('isBackgroundOperations') || newHostInfo.isModified || this.get('currentHostName') === host.get('name'); + if (newHostInfo && hostShouldBeUpdated) { + var hostStatus = self.getStatus(newHostInfo.logTasks); + var hostProgress = self.getProgress(newHostInfo.logTasks); + host.setProperties({ + status: App.format.taskStatus(hostStatus[0]), + icon: hostStatus[1], + barColor: hostStatus[2], + isInProgress: hostStatus[3], + progress: hostProgress, + barWidth: "width:" + hostProgress + "%;", + logTasks: newHostInfo.logTasks + }); + var existTasks = host.get('tasks'); + if (existTasks) { + newHostInfo.logTasks.forEach(function (_task) { + var existTask = existTasks.findProperty('id', _task.Tasks.id); + if (existTask) { + var status = _task.Tasks.status; + detailedPropertiesKeys.forEach(function (key) { + var name = detailedProperties[key]; + var value = _task.Tasks[name]; + if (!Em.isNone(value)) { + existTask.set(key, value); } }, this); + existTask.setProperties({ + status: App.format.taskStatus(status), + startTime: date.startTime(_task.Tasks.start_time), + duration: date.durationSummary(_task.Tasks.start_time, _task.Tasks.end_time) + }); + existTask = self._handleRebalanceHDFS(_task, existTask); } - } - }, this); - } else { - for (var hostName in hostsMap) { - var _host = hostsMap[hostName]; - var tasks = _host.logTasks; - var hostInfo = Ember.Object.create({ - name: hostName, - publicName: _host.publicName, - displayName: function () { - return this.get('name').length < 43 ? this.get('name') : (this.get('name').substr(0, 40) + '...'); - }.property('name'), - progress: 0, - status: App.format.taskStatus("PENDING"), - serviceName: _host.serviceName, - isVisible: true, - icon: "icon-cog", - barColor: "progress-info", - barWidth: "width:0%;" - }); - - if (tasks.length) { - tasks = tasks.sortProperty('Tasks.id'); - var hostStatus = self.getStatus(tasks); - var hostProgress = self.getProgress(tasks); - hostInfo.set('status', App.format.taskStatus(hostStatus[0])); - hostInfo.set('icon', hostStatus[1]); - hostInfo.set('barColor', hostStatus[2]); - hostInfo.set('isInProgress', hostStatus[3]); - hostInfo.set('progress', hostProgress); - hostInfo.set('barWidth', "width:" + hostProgress + "%;"); - } - hostInfo.set('logTasks', tasks); - hostsArr.push(hostInfo); + else { + existTasks.pushObject(this.createTask(_task)); + } + }, this); } - - hostsArr = hostsArr.sortProperty('name'); - hostsArr.setEach("serviceName", this.get("serviceName")); - self.set("hosts", hostsArr); - self.set('previousServiceId', this.get('currentServiceId')); } - } + }, this); + }, - var operation = this.get('servicesInfo').findProperty('name', this.get('serviceName')); - if (!operation || (operation && operation.get('progress') == 100)) { - this.set('operationInfo', null); - } else { - this.set('operationInfo', operation); + /** + * Custom processing for "Rebalance HDFS"-task + * + * @param {object} task + * @param {object} existTask + * @returns {object} + * @private + * @method _handleRebalanceHDFS + */ + _handleRebalanceHDFS: function (task, existTask) { + var barColorMap = this.get('barColorMap'); + var isRebalanceHDFSTask = task.Tasks.command === 'CUSTOM_COMMAND' && task.Tasks.custom_command_name === 'REBALANCEHDFS'; + existTask.set('isRebalanceHDFSTask', isRebalanceHDFSTask); + if (isRebalanceHDFSTask) { + var structuredOut = task.Tasks.structured_out || {}; + var status = task.Tasks.status; + existTask.setProperties({ + dataMoved: structuredOut['dataMoved'] || '0', + dataLeft: structuredOut['dataLeft'] || '0', + dataBeingMoved: structuredOut['dataBeingMoved'] || '0', + barColor: barColorMap[status], + isInProgress: status == 'IN_PROGRESS', + isNotComplete: ['QUEUED', 'IN_PROGRESS'].contains(status), + completionProgressStyle: 'width:' + (structuredOut['completePercent'] || 0) * 100 + '%;', + command: task.Tasks.command, + custom_command_name: task.Tasks.custom_command_name + }); } + return existTask; }, /** * Show popup + * * @return {App.ModalPopup} PopupObject For testing purposes */ createPopup: function () { var self = this; var servicesInfo = this.get("servicesInfo"); var isBackgroundOperations = this.get('isBackgroundOperations'); - var categoryObject = Em.Object.extend({ - value: '', - count: 0, - labelPath: '', - label: function(){ - return Em.I18n.t(this.get('labelPath')).format(this.get('count')); - }.property('count') - }); - self.set('isPopup', App.ModalPopup.show({ + this.set('isPopup', App.ModalPopup.show({ + + /** + * @type {boolean} + */ isLogWrapHidden: true, + + /** + * @type {boolean} + */ isTaskListHidden: true, + + /** + * @type {boolean} + */ isHostListHidden: true, + + /** + * @type {boolean} + */ isServiceListHidden: false, + /** + * @type {boolean} + */ isHideBodyScroll: true, + /** * no need to track is it loaded when popup contain only list of hosts * @type {bool} @@ -652,9 +818,12 @@ App.HostPopup = Em.Object.create({ */ isOpen: false, + /** + * @type {object} + */ detailedProperties: self.get('detailedProperties'), - didInsertElement: function(){ + didInsertElement: function () { this._super(); this.set('isOpen', true); }, @@ -664,10 +833,10 @@ App.HostPopup = Em.Object.create({ */ headerClass: Em.View.extend({ controller: this, - template: Ember.Handlebars.compile('{{popupHeaderName}} ' + - '{{#unless view.parentView.isHostListHidden}}{{#if controller.operationInfo.isAbortable}}' + - '{{view controller.abortIcon servicesInfoBinding="controller.operationInfo"}}' + - '{{/if}}{{/unless}}') + template: Em.Handlebars.compile('{{popupHeaderName}} ' + + '{{#unless view.parentView.isHostListHidden}}{{#if controller.operationInfo.isAbortable}}' + + '{{view controller.abortIcon servicesInfoBinding="controller.operationInfo"}}' + + '{{/if}}{{/unless}}') }), /** @@ -677,18 +846,22 @@ App.HostPopup = Em.Object.create({ /** * for the checkbox: do not show this dialog again + * * @type {bool} */ hasFooterCheckbox: true, /** * Auto-display BG-popup + * * @type {bool} */ - isNotShowBgChecked : null, + isNotShowBgChecked: null, /** * Save user pref about auto-display BG-popup + * + * @method updateNotShowBgChecked */ updateNotShowBgChecked: function () { var curVal = !this.get('isNotShowBgChecked'); @@ -698,9 +871,13 @@ App.HostPopup = Em.Object.create({ }.observes('isNotShowBgChecked'), autoHeight: false, + + /** + * @method closeModelPopup + */ closeModelPopup: function () { this.set('isOpen', false); - if(isBackgroundOperations){ + if (isBackgroundOperations) { $(this.get('element')).detach(); App.router.get('backgroundOperationsController').set('levelInfo.name', 'REQUESTS_LIST'); } else { @@ -708,513 +885,24 @@ App.HostPopup = Em.Object.create({ self.set('isPopup', null); } }, + onPrimary: function () { this.closeModelPopup(); }, + onClose: function () { this.closeModelPopup(); }, - secondary: null, - - bodyClass: App.TableView.extend({ - templateName: require('templates/common/host_progress_popup'), - showTextArea: false, - isServiceEmptyList: true, - isTasksEmptyList: true, - controller: this, - sourceRequestScheduleId: -1, - sourceRequestScheduleRunning: false, - sourceRequestScheduleAborted: false, - sourceRequestScheduleCommand: null, - hosts: self.get('hosts'), - services: self.get('servicesInfo'), - filterMap: { - pending: ["pending", "queued"], - in_progress: ["in_progress", "upgrading"], - failed: ["failed"], - completed: ["completed", "success"], - aborted: ["aborted"], - timedout: ["timedout"] - }, - - pagination: true, - isPaginate: false, - /** - * Select box, display names and values - */ - categories: [ - categoryObject.create({value: 'all', labelPath: 'hostPopup.status.category.all'}), - categoryObject.create({value: 'pending', labelPath: 'hostPopup.status.category.pending'}), - categoryObject.create({value: 'in_progress', labelPath: 'hostPopup.status.category.inProgress'}), - categoryObject.create({value: 'failed', labelPath: 'hostPopup.status.category.failed'}), - categoryObject.create({value: 'completed', labelPath: 'hostPopup.status.category.success'}), - categoryObject.create({value: 'aborted', labelPath: 'hostPopup.status.category.aborted'}), - categoryObject.create({value: 'timedout', labelPath: 'hostPopup.status.category.timedout'}) - ], - - /** - * Selected option is bound to this values - */ - serviceCategory: null, - hostCategory: null, - taskCategory: null, - /** - * flag to indicate whether level data has already been loaded - * applied only to HOSTS_LIST and TASK_DETAILS levels, whereas async query used to obtain data - */ - isLevelLoaded: true, - isHostEmptyList: function() { - return !this.get('pageContent.length'); - }.property('pageContent.length'), - - currentHost: function () { - return this.get('hosts') && this.get('hosts').findProperty('name', this.get('controller.currentHostName')); - }.property('controller.currentHostName'), - - tasks: function () { - var currentHost = this.get('currentHost'); - if (currentHost) { - return currentHost.get('tasks'); - } - return []; - }.property('currentHost.tasks', '[email protected]'), - - willDestroyElement: function () { - if (this.get('controller.dataSourceController.name') == 'highAvailabilityProgressPopupController') { - this.set('controller.dataSourceController.isTaskPolling', false); - } - }, - - /** - * Preset values on init - */ - setOnStart: function () { - this.set('serviceCategory', this.get('categories').findProperty('value','all')); - if (this.get("controller.isBackgroundOperations")) { - this.get('controller').setSelectCount(this.get("services"), this.get('categories')); - this.updateHostInfo(); - } else { - this.set("parentView.isHostListHidden", false); - this.set("parentView.isServiceListHidden", true); - } - }, - - /** - * force popup to show list of operations - */ - resetState: function(){ - if(this.get('parentView.isOpen')){ - this.set('parentView.isLogWrapHidden', true); - this.set('parentView.isTaskListHidden', true); - this.set('parentView.isHostListHidden', true); - this.set('parentView.isServiceListHidden', false); - this.get("controller").setBackgroundOperationHeader(false); - this.setOnStart(); - } - }.observes('parentView.isOpen'), - - /** - * When popup is opened, and data after polling has changed, update this data in component - */ - updateHostInfo: function () { - if(!this.get('parentView.isOpen')) return; - this.set('parentView.isLoaded', false); - this.get("controller").set("inputData", this.get("controller.dataSourceController.services")); - this.get("controller").onServiceUpdate(this.get('parentView.isServiceListHidden')); - this.get("controller").onHostUpdate(); - this.set('parentView.isLoaded', true); - this.set("hosts", this.get("controller.hosts")); - this.set("services", this.get("controller.servicesInfo")); - this.set('isLevelLoaded', true); - }.observes("controller.dataSourceController.serviceTimestamp"), - - /** - * Depending on service filter, set which services should be shown - */ - visibleServices: function () { - if (this.get("services")) { - this.set("isServiceEmptyList", true); - if (this.get('serviceCategory.value')) { - var filter = this.get('serviceCategory.value'); - var services = this.get('services'); - this.set("isServiceEmptyList", this.setVisibility(filter, services)); - } - } - }.observes('serviceCategory', 'services', '[email protected]'), - - /** - * Depending on hosts filter, set which hosts should be shown - */ - filter: function() { - var _this = this, - filter = this.get('hostCategory.value'), - hosts = this.get('hosts') || []; - if (!filter || !hosts.length) return; - if (filter === 'all') { - this.set('filteredContent', hosts); - } else { - this.set('filteredContent', hosts.filter(function(item) { - return _this.get('filterMap')[filter].contains(item.status); - })); - } - }.observes('hosts.length', 'hostCategory.value'), - - /** - * Reset startIndex property back to 1 when filter type has been changed. - */ - resetIndex: function() { - if (this.get('hostCategory.value')) this.set('startIndex', 1) - }.observes('hostCategory.value'), - - /** - * Depending on tasks filter, set which tasks should be shown - */ - visibleTasks: function () { - this.set("isTasksEmptyList", true); - if (this.get('taskCategory.value') && this.get('tasks')) { - var filter = this.get('taskCategory.value'); - var tasks = this.get('tasks'); - this.set("isTasksEmptyList", this.setVisibility(filter, tasks)); - } - }.observes('taskCategory', 'tasks', '[email protected]'), - - /** - * Depending on selected filter type, set object visibility value - * @param filter - * @param obj - * @return {bool} isEmptyList - */ - setVisibility: function (filter, obj) { - var isEmptyList = true; - if (filter == "all") { - obj.setEach("isVisible", true); - isEmptyList = !(obj.length > 0); - } else { - obj.forEach(function(item){ - item.set('isVisible', this.get('filterMap')[filter].contains(item.status)); - isEmptyList = (isEmptyList) ? !item.get('isVisible') : false; - }, this) - } - return isEmptyList; - }, - - /** - * Depending on currently viewed tab, call setSelectCount function - */ - updateSelectView: function () { - var isPaginate; - if (!this.get('parentView.isHostListHidden')) { - //since lazy loading used for hosts, we need to get hosts info directly from controller, that always contains entire array of data - this.get('controller').setSelectCount(this.get("controller.hosts"), this.get('categories')); - isPaginate = true; - } else if (!this.get('parentView.isTaskListHidden')) { - this.get('controller').setSelectCount(this.get("tasks"), this.get('categories')); - } else if (!this.get('parentView.isServiceListHidden')) { - this.get('controller').setSelectCount(this.get("services"), this.get('categories')); - } - this.set('isPaginate', !!isPaginate); - }.observes('[email protected]', '[email protected]', 'parentView.isTaskListHidden', 'parentView.isHostListHidden', 'services.length', '[email protected]'), - - /** - * control data uploading, depending on which display level is showed - * @param levelName - */ - switchLevel: function (levelName) { - var dataSourceController = this.get('controller.dataSourceController'); - if (this.get("controller.isBackgroundOperations")) { - var levelInfo = dataSourceController.get('levelInfo'); - levelInfo.set('taskId', this.get('openedTaskId')); - levelInfo.set('requestId', this.get('controller.currentServiceId')); - levelInfo.set('name', levelName); - if (levelName === 'HOSTS_LIST') { - this.set('isLevelLoaded', dataSourceController.requestMostRecent()); - this.set('hostCategory', this.get('categories').findProperty('value','all')); - } else if (levelName === 'TASK_DETAILS') { - dataSourceController.requestMostRecent(); - this.set('isLevelLoaded', false); - } else if (levelName === 'REQUESTS_LIST') { - this.set('serviceCategory', this.get('categories').findProperty('value','all')); - this.get('controller.hosts').clear(); - dataSourceController.requestMostRecent(); - } else { - this.set('taskCategory', this.get('categories').findProperty('value','all')); - } - } else if (dataSourceController.get('name') == 'highAvailabilityProgressPopupController') { - if (levelName === 'TASK_DETAILS') { - this.set('isLevelLoaded', false); - dataSourceController.startTaskPolling(this.get('openedTask.request_id'), this.get('openedTask.id')); - Em.keys(this.get('parentView.detailedProperties')).forEach(function (key) { - dataSourceController.addObserver('taskInfo.' + this.get('parentView.detailedProperties')[key], this, 'updateTaskInfo'); - }, this); - } else { - dataSourceController.stopTaskPolling(); - } - } - }, - updateTaskInfo: function () { - var dataSourceController = this.get('controller.dataSourceController'); - var openedTask = this.get('openedTask'); - if (openedTask && openedTask.get('id') == dataSourceController.get('taskInfo.id')) { - this.set('isLevelLoaded', true); - Em.keys(this.get('parentView.detailedProperties')).forEach(function (key) { - openedTask.set(key, dataSourceController.get('taskInfo.' + key)); - }, this); - } - }, - /** - * Onclick handler for button <-Tasks - */ - backToTaskList: function () { - this.destroyClipBoard(); - this.set("openedTaskId", 0); - this.set("parentView.isLogWrapHidden", true); - this.set("parentView.isTaskListHidden", false); - this.switchLevel("TASKS_LIST"); - }, - - /** - * Onclick handler for button <-Hosts - */ - backToHostList: function () { - this.set("parentView.isHostListHidden", false); - this.set("parentView.isTaskListHidden", true); - this.get("controller").set("popupHeaderName", this.get("controller.serviceName")); - this.get("controller").set("operationInfo", this.get('controller.servicesInfo').findProperty('name', this.get('controller.serviceName'))); - this.switchLevel("HOSTS_LIST"); - }, - - /** - * Onclick handler for button <-Services - */ - backToServiceList: function () { - this.get("controller").set("serviceName", ""); - this.set("parentView.isHostListHidden", true); - this.set("parentView.isServiceListHidden", false); - this.set("parentView.isTaskListHidden", true); - this.set("parentView.isLogWrapHidden", true); - this.set("hosts", null); - this.get("controller").setBackgroundOperationHeader(false); - this.switchLevel("REQUESTS_LIST"); - }, - /** - * Onclick handler for Show more .. - */ - requestMoreOperations: function () { - var BGOController = App.router.get('backgroundOperationsController'); - var count = BGOController.get('operationsCount'); - BGOController.set('operationsCount', (count + 10)); - BGOController.requestMostRecent(); - }, - - setShowMoreAvailable: function () { - if (this.get('parentView.isOpen')) { - this.set('isShowMore', App.router.get("backgroundOperationsController.isShowMoreAvailable")); - } - }.observes('parentView.isOpen', 'App.router.backgroundOperationsController.isShowMoreAvailable'), - isShowMore: true, - - /** - * Onclick handler for selected Service - * @param {Object} event - */ - gotoHosts: function (event) { - this.get("controller").set("serviceName", event.context.get("name")); - this.get("controller").set("currentServiceId", event.context.get("id")); - this.get("controller").set("currentHostName", null); - this.get("controller").onHostUpdate(); - this.switchLevel("HOSTS_LIST"); - var servicesInfo = this.get("controller.hosts"); - this.set("controller.popupHeaderName", event.context.get("name")); - this.set("controller.operationInfo", event.context); - - //apply lazy loading on cluster with more than 100 nodes - if (servicesInfo.length > 100) { - this.set('hosts', servicesInfo.slice(0, 50)); - } else { - this.set('hosts', servicesInfo); - } - this.set("parentView.isServiceListHidden", true); - this.set("parentView.isHostListHidden", false); - this.set("parentView.isTaskListHidden", true); - $(".modal").scrollTop(0); - $(".modal-body").scrollTop(0); - if (servicesInfo.length > 100) { - Em.run.next(this, function(){ - this.set('hosts', this.get('hosts').concat(servicesInfo.slice(50, servicesInfo.length))); - }); - } - // Determine if source request schedule is present - this.set('sourceRequestScheduleId', event.context.get("sourceRequestScheduleId")); - this.set('sourceRequestScheduleCommand', event.context.get('contextCommand')); - this.refreshRequestScheduleInfo(); - }, - - isRequestSchedule : function() { - var id = this.get('sourceRequestScheduleId'); - return id != null && !isNaN(id) && id > -1; - }.property('sourceRequestScheduleId'), - - refreshRequestScheduleInfo : function() { - var self = this; - var id = this.get('sourceRequestScheduleId'); - batchUtils.getRequestSchedule(id, function(data) { - if (data != null && data.RequestSchedule != null && - data.RequestSchedule.status != null) { - switch (data.RequestSchedule.status) { - case 'DISABLED': - self.set('sourceRequestScheduleRunning', false); - self.set('sourceRequestScheduleAborted', true); - break; - case 'COMPLETED': - self.set('sourceRequestScheduleRunning', false); - self.set('sourceRequestScheduleAborted', false); - break; - case 'SCHEDULED': - self.set('sourceRequestScheduleRunning', true); - self.set('sourceRequestScheduleAborted', false); - break; - } - } else { - self.set('sourceRequestScheduleRunning', false); - self.set('sourceRequestScheduleAborted', false); - } - }, function(xhr, textStatus, error, opt) { - self.set('sourceRequestScheduleRunning', false); - self.set('sourceRequestScheduleAborted', false); - }); - }.observes('sourceRequestScheduleId'), - - /** - * Attempts to abort the current request schedule - */ - doAbortRequestSchedule: function(event){ - var self = this; - var id = event.context; - batchUtils.doAbortRequestSchedule(id, function(){ - self.refreshRequestScheduleInfo(); - }); - }, - - requestScheduleAbortLabel : function() { - var label = Em.I18n.t("common.abort"); - var command = this.get('sourceRequestScheduleCommand'); - if (command != null && "ROLLING-RESTART" == command) { - label = Em.I18n.t("hostPopup.bgop.abort.rollingRestart"); - } - return label; - }.property('sourceRequestScheduleCommand'), - - /** - * Onclick handler for selected Host - * @param {Object} event - */ - gotoTasks: function (event) { - var tasksInfo = []; - event.context.logTasks.forEach(function (_task) { - tasksInfo.pushObject(this.get("controller").createTask(_task)); - }, this); - if (tasksInfo.length) { - this.get("controller").set("popupHeaderName", event.context.publicName); - this.get("controller").set("currentHostName", event.context.publicName); - } - this.switchLevel("TASKS_LIST"); - this.set('currentHost.tasks', tasksInfo); - this.set("parentView.isHostListHidden", true); - this.set("parentView.isTaskListHidden", false); - $(".modal").scrollTop(0); - $(".modal-body").scrollTop(0); - }, - - stopRebalanceHDFS: function () { - var hostPopup = this; - return App.showConfirmationPopup(function () { - App.ajax.send({ - name : 'cancel.background.operation', - sender : hostPopup, - data : { - requestId : hostPopup.get('controller.currentServiceId') - } - }); - hostPopup.backToServiceList(); - }); - }, - /** - * Onclick handler for selected Task - */ - openTaskLogInDialog: function () { - if ($(".task-detail-log-clipboard").length > 0) { - this.destroyClipBoard(); - } - var newWindow = window.open(); - var newDocument = newWindow.document; - newDocument.write($(".task-detail-log-info").html()); - newDocument.close(); - }, - - openedTaskId: 0, - - /** - * Return task detail info of opened task - */ - openedTask: function () { - if (!(this.get('openedTaskId') && this.get('tasks'))) { - return Ember.Object.create(); - } - return this.get('tasks').findProperty('id', this.get('openedTaskId')); - }.property('tasks', '[email protected]', '[email protected]', 'openedTaskId'), - - /** - * Onclick event for show task detail info - * @param {Object} event - */ - toggleTaskLog: function (event) { - var taskInfo = event.context; - this.set("parentView.isLogWrapHidden", false); - if ($(".task-detail-log-clipboard").length > 0) { - this.destroyClipBoard(); - } - this.set("parentView.isHostListHidden", true); - this.set("parentView.isTaskListHidden", true); - this.set('openedTaskId', taskInfo.id); - this.switchLevel("TASK_DETAILS"); - $(".modal").scrollTop(0); - $(".modal-body").scrollTop(0); - }, - - /** - * Onclick event for copy to clipboard button - */ - textTrigger: function () { - $(".task-detail-log-clipboard").length > 0 ? this.destroyClipBoard() : this.createClipBoard(); - }, - - /** - * Create Clip Board - */ - createClipBoard: function () { - var logElement = $(".task-detail-log-maintext"); - $(".task-detail-log-clipboard-wrap").html('<textarea class="task-detail-log-clipboard"></textarea>'); - $(".task-detail-log-clipboard") - .html("stderr: \n" + $(".stderr").html() + "\n stdout:\n" + $(".stdout").html()) - .css("display", "block") - .width(logElement.width()) - .height(logElement.height()) - .select(); - logElement.css("display", "none") - }, - - /** - * Destroy Clip Board - */ - destroyClipBoard: function () { - $(".task-detail-log-clipboard").remove(); - $(".task-detail-log-maintext").css("display", "block"); - } + secondary: null, + bodyClass: App.HostProgressPopupBodyView.extend({ + controller: self }) + })); - return self.get('isPopup'); + + return this.get('isPopup'); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/76dd478e/ambari-web/app/views.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js index d6132e6..3a446bb 100644 --- a/ambari-web/app/views.js +++ b/ambari-web/app/views.js @@ -36,6 +36,7 @@ require('views/common/modal_popups/invalid_KDC_popup'); require('views/common/modal_popups/dependent_configs_list_popup'); require('views/common/modal_popups/select_groups_popup'); require('views/common/editable_list'); +require('views/common/host_progress_popup_body_view'); require('views/common/rolling_restart_view'); require('views/common/select_custom_date_view'); require('views/common/metric'); http://git-wip-us.apache.org/repos/asf/ambari/blob/76dd478e/ambari-web/app/views/common/host_progress_popup_body_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/host_progress_popup_body_view.js b/ambari-web/app/views/common/host_progress_popup_body_view.js new file mode 100644 index 0000000..86e1fc8 --- /dev/null +++ b/ambari-web/app/views/common/host_progress_popup_body_view.js @@ -0,0 +1,749 @@ +/** + * 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. + */ + +var App = require('app'); +var batchUtils = require('utils/batch_scheduled_requests'); +var date = require('utils/date/date'); + +/** + * Option for "filter by state" dropdown + * @typedef {object} progressPopupCategoryObject + * @property {string} value "all|pending|in progress|failed|completed|aborted|timedout" + * @property {number} count number of items with <code>state</code> equal to <code>this.value</code> + * @property {string} labelPath key in the messages.js + * @property {string} label localized label + */ +var categoryObject = Em.Object.extend({ + value: '', + count: 0, + labelPath: '', + label: function () { + return Em.I18n.t(this.get('labelPath')).format(this.get('count')); + }.property('count', 'labelPath') +}); + +/** + * @class HostProgressPopupBodyView + * @type {Em.View} + */ +App.HostProgressPopupBodyView = App.TableView.extend({ + + templateName: require('templates/common/host_progress_popup'), + + /** + * @type {boolean} + */ + showTextArea: false, + + /** + * @type {boolean} + */ + isServiceEmptyList: true, + + /** + * @type {boolean} + */ + isTasksEmptyList: true, + + /** + * @type {number} + */ + sourceRequestScheduleId: -1, + + /** + * @type {boolean} + */ + sourceRequestScheduleRunning: false, + + /** + * @type {boolean} + */ + sourceRequestScheduleAborted: false, + + /** + * @type {?string} + */ + sourceRequestScheduleCommand: null, + + /** + * Alias for <code>controller.hosts</code> + * + * @type {wrappedHost[]} + */ + hosts: function () { + return this.get('controller.hosts') + }.property('controller.hosts.[]'), + + /** + * Alias for <code>controller.servicesInfo</code> + * + * @type {wrappedService[]} + */ + services: function () { + return this.get('controller.servicesInfo'); + }.property('controller.servicesInfo.[]'), + + /** + * @type {number} + */ + openedTaskId: 0, + + /** + * Return task detail info of opened task + * + * @type {wrappedTask} + */ + openedTask: function () { + if (!(this.get('openedTaskId') && this.get('tasks'))) { + return Em.Object.create(); + } + return this.get('tasks').findProperty('id', this.get('openedTaskId')); + }.property('tasks', '[email protected]', '[email protected]', 'openedTaskId'), + + /** + * @type {object} + */ + filterMap: { + pending: ["pending", "queued"], + in_progress: ["in_progress", "upgrading"], + failed: ["failed"], + completed: ["completed", "success"], + aborted: ["aborted"], + timedout: ["timedout"] + }, + + /** + * Determines if "Show More ..."-link should be shown + * @type {boolean} + */ + isShowMore: true, + + /** + * @type {boolean} + */ + pagination: true, + + /** + * @type {boolean} + */ + isPaginate: false, + + /** + * Select box, display names and values + * + * @type {progressPopupCategoryObject[]} + */ + categories: [ + categoryObject.create({value: 'all', labelPath: 'hostPopup.status.category.all'}), + categoryObject.create({value: 'pending', labelPath: 'hostPopup.status.category.pending'}), + categoryObject.create({value: 'in_progress', labelPath: 'hostPopup.status.category.inProgress'}), + categoryObject.create({value: 'failed', labelPath: 'hostPopup.status.category.failed'}), + categoryObject.create({value: 'completed', labelPath: 'hostPopup.status.category.success'}), + categoryObject.create({value: 'aborted', labelPath: 'hostPopup.status.category.aborted'}), + categoryObject.create({value: 'timedout', labelPath: 'hostPopup.status.category.timedout'}) + ], + + /** + * Selected option is bound to this values + * @type {?progressPopupCategoryObject} + */ + serviceCategory: null, + + /** + * @type {?progressPopupCategoryObject} + */ + hostCategory: null, + + /** + * @type {?progressPopupCategoryObject} + */ + taskCategory: null, + + /** + * flag to indicate whether level data has already been loaded + * applied only to HOSTS_LIST and TASK_DETAILS levels, whereas async query used to obtain data + * + * @type {boolean} + */ + isLevelLoaded: true, + + /** + * <code>switchLevel</code> for some <code>controller.dataSourceController</code> should be customized + * So, this map contains information about customize-methods + * Format: key - <code>dataSourceController.name</code>, value - method name in this view + * Method is called with same arguments as <code>switchLevel</code> is + * + * @type {object} + */ + customControllersSwitchLevelMap: { + highAvailabilityProgressPopupController: '_switchLevelForHAProgressPopupController' + }, + + /** + * @type {boolean} + */ + isHostEmptyList: function () { + return !this.get('pageContent.length'); + }.property('pageContent.length'), + + /** + * @type {wrappedHost} + */ + currentHost: function () { + return this.get('hosts') && this.get('hosts').findProperty('name', this.get('controller.currentHostName')); + }.property('controller.currentHostName'), + + /** + * Tasks for current shown host (<code>currentHost</code>) + * + * @type {wrappedTask[]} + */ + tasks: function () { + var currentHost = this.get('currentHost'); + return currentHost ? currentHost.get('tasks') : []; + }.property('currentHost.tasks', '[email protected]'), + + + /** + * Message about aborting operation + * Custom for Rolling Restart + * + * @type {string} + */ + requestScheduleAbortLabel: function () { + return 'ROLLING-RESTART' == this.get('sourceRequestScheduleCommand') ? + Em.I18n.t("hostPopup.bgop.abort.rollingRestart"): + Em.I18n.t("common.abort"); + }.property('sourceRequestScheduleCommand'), + + + willDestroyElement: function () { + if (this.get('controller.dataSourceController.name') == 'highAvailabilityProgressPopupController') { + this.set('controller.dataSourceController.isTaskPolling', false); + } + }, + + /** + * Preset values on init + * + * @method setOnStart + */ + setOnStart: function () { + this.set('serviceCategory', this.get('categories').findProperty('value', 'all')); + if (this.get("controller.isBackgroundOperations")) { + this.get('controller').setSelectCount(this.get("services"), this.get('categories')); + this.updateHostInfo(); + } + else { + this.set("parentView.isHostListHidden", false); + this.set("parentView.isServiceListHidden", true); + } + }, + + /** + * force popup to show list of operations + * + * @method resetState + */ + resetState: function () { + if (this.get('parentView.isOpen')) { + this.get('parentView').setProperties({ + isLogWrapHidden: true, + isTaskListHidden: true, + isHostListHidden: true, + isServiceListHidden: false + }); + this.get("controller").setBackgroundOperationHeader(false); + this.setOnStart(); + } + }.observes('parentView.isOpen'), + + /** + * When popup is opened, and data after polling has changed, update this data in component + * + * @method updateHostInfo + */ + updateHostInfo: function () { + if (!this.get('parentView.isOpen')) { + return; + } + this.set('parentView.isLoaded', false); + this.get("controller").set("inputData", this.get("controller.dataSourceController.services")); + this.get("controller").onServiceUpdate(this.get('parentView.isServiceListHidden')); + this.get("controller").onHostUpdate(); + this.set('parentView.isLoaded', true); + this.set("hosts", this.get("controller.hosts")); + this.set("services", this.get("controller.servicesInfo")); + this.set('isLevelLoaded', true); + }.observes("controller.dataSourceController.serviceTimestamp"), + + /** + * Depending on service filter, set which services should be shown + * + * @method visibleServices + */ + visibleServices: function () { + if (this.get("services")) { + this.set("isServiceEmptyList", true); + if (this.get('serviceCategory.value')) { + var filter = this.get('serviceCategory.value'); + var services = this.get('services'); + this.set("isServiceEmptyList", this.setVisibility(filter, services)); + } + } + }.observes('serviceCategory', 'services', '[email protected]'), + + /** + * Depending on hosts filter, set which hosts should be shown + * + * @method filter + */ + filter: function () { + var filter = this.get('hostCategory.value'); + var hosts = this.get('hosts') || []; + var filterMap = this.get('filterMap'); + if (!filter || !hosts.length) { + return; + } + if (filter === 'all') { + this.set('filteredContent', hosts); + } + else { + this.set('filteredContent', hosts.filter(function (item) { + return filterMap[filter].contains(item.status); + })); + } + }.observes('hosts.length', 'hostCategory.value'), + + /** + * Reset startIndex property back to 1 when filter type has been changed. + * + * @method resetIndex + */ + resetIndex: function () { + if (this.get('hostCategory.value')) { + this.set('startIndex', 1); + } + }.observes('hostCategory.value'), + + /** + * Depending on tasks filter, set which tasks should be shown + * + * @method visibleTasks + */ + visibleTasks: function () { + this.set("isTasksEmptyList", true); + if (this.get('taskCategory.value') && this.get('tasks')) { + var filter = this.get('taskCategory.value'); + var tasks = this.get('tasks'); + this.set("isTasksEmptyList", this.setVisibility(filter, tasks)); + } + }.observes('taskCategory', 'tasks', '[email protected]'), + + /** + * Depending on selected filter type, set object visibility value + * + * @param filter + * @param obj + * @return {bool} isEmptyList + * @method setVisibility + */ + setVisibility: function (filter, obj) { + var isEmptyList = true; + var filterMap = this.get('filterMap'); + if (filter == "all") { + obj.setEach("isVisible", true); + isEmptyList = !obj.length; + } + else { + obj.forEach(function (item) { + item.set('isVisible', filterMap[filter].contains(item.status)); + isEmptyList = (isEmptyList) ? !item.get('isVisible') : false; + }, this) + } + return isEmptyList; + }, + + /** + * Depending on currently viewed tab, call setSelectCount function + * + * @method updateSelectView + */ + updateSelectView: function () { + var isPaginate; + if (this.get('parentView.isHostListHidden')) { + if (this.get('parentView.isTaskListHidden')) { + if (!this.get('parentView.isServiceListHidden')) { + this.get('controller').setSelectCount(this.get("services"), this.get('categories')); + } + } + else { + this.get('controller').setSelectCount(this.get("tasks"), this.get('categories')); + } + } + else { + //since lazy loading used for hosts, we need to get hosts info directly from controller, that always contains entire array of data + this.get('controller').setSelectCount(this.get("controller.hosts"), this.get('categories')); + isPaginate = true; + + } + this.set('isPaginate', !!isPaginate); + }.observes('[email protected]', '[email protected]', 'parentView.isTaskListHidden', 'parentView.isHostListHidden', 'services.length', '[email protected]'), + + /** + * control data uploading, depending on which display level is showed + * + * @param {string} levelName + * @method switchLevel + */ + switchLevel: function (levelName) { + var dataSourceController = this.get('controller.dataSourceController'); + var args = [].slice.call(arguments); + if (this.get("controller.isBackgroundOperations")) { + var levelInfo = dataSourceController.get('levelInfo'); + levelInfo.set('taskId', this.get('openedTaskId')); + levelInfo.set('requestId', this.get('controller.currentServiceId')); + levelInfo.set('name', levelName); + if (levelName === 'HOSTS_LIST') { + this.set('isLevelLoaded', dataSourceController.requestMostRecent()); + this.set('hostCategory', this.get('categories').findProperty('value', 'all')); + } + else { + if (levelName === 'TASK_DETAILS') { + dataSourceController.requestMostRecent(); + this.set('isLevelLoaded', false); + } + else { + if (levelName === 'REQUESTS_LIST') { + this.set('serviceCategory', this.get('categories').findProperty('value', 'all')); + this.get('controller.hosts').clear(); + dataSourceController.requestMostRecent(); + } + else { + this.set('taskCategory', this.get('categories').findProperty('value', 'all')); + } + } + } + } + else { + var customControllersSwitchLevelMap = this.get('customControllersSwitchLevelMap'); + Em.tryInvoke(this, customControllersSwitchLevelMap[dataSourceController.get('name')], args); + } + }, + + /** + * Switch-level custom method for <code>highAvailabilityProgressPopupController</code> + * + * @param {string} levelName + * @private + */ + _switchLevelForHAProgressPopupController: function (levelName) { + var dataSourceController = this.get('controller.dataSourceController'); + if (levelName === 'TASK_DETAILS') { + this.set('isLevelLoaded', false); + dataSourceController.startTaskPolling(this.get('openedTask.request_id'), this.get('openedTask.id')); + Em.keys(this.get('parentView.detailedProperties')).forEach(function (key) { + dataSourceController.addObserver('taskInfo.' + this.get('parentView.detailedProperties')[key], this, 'updateTaskInfo'); + }, this); + } + else { + dataSourceController.stopTaskPolling(); + } + }, + + /** + * @method updateTaskInfo + */ + updateTaskInfo: function () { + var dataSourceController = this.get('controller.dataSourceController'); + var openedTask = this.get('openedTask'); + if (openedTask && openedTask.get('id') == dataSourceController.get('taskInfo.id')) { + this.set('isLevelLoaded', true); + Em.keys(this.get('parentView.detailedProperties')).forEach(function (key) { + openedTask.set(key, dataSourceController.get('taskInfo.' + key)); + }, this); + } + }, + + /** + * Onclick handler for button <-Tasks + * + * @method backToTaskList + */ + backToTaskList: function () { + this.destroyClipBoard(); + this.set("openedTaskId", 0); + this.set("parentView.isLogWrapHidden", true); + this.set("parentView.isTaskListHidden", false); + this.switchLevel("TASKS_LIST"); + }, + + /** + * Onclick handler for button <-Hosts + * + * @method backToHostList + */ + backToHostList: function () { + this.set("parentView.isHostListHidden", false); + this.set("parentView.isTaskListHidden", true); + this.get("controller").set("popupHeaderName", this.get("controller.serviceName")); + this.get("controller").set("operationInfo", this.get('controller.servicesInfo').findProperty('name', this.get('controller.serviceName'))); + this.switchLevel("HOSTS_LIST"); + }, + + /** + * Onclick handler for button <-Services + * + * @method backToServiceList + */ + backToServiceList: function () { + this.get("controller").set("serviceName", ""); + this.set("parentView.isHostListHidden", true); + this.set("parentView.isServiceListHidden", false); + this.set("parentView.isTaskListHidden", true); + this.set("parentView.isLogWrapHidden", true); + this.set("hosts", null); + this.get("controller").setBackgroundOperationHeader(false); + this.switchLevel("REQUESTS_LIST"); + }, + + /** + * Onclick handler for Show more .. + * + * @method requestMoreOperations + */ + requestMoreOperations: function () { + var BGOController = App.router.get('backgroundOperationsController'); + var count = BGOController.get('operationsCount'); + BGOController.set('operationsCount', (count + 10)); + BGOController.requestMostRecent(); + }, + + /** + * @method setShowMoreAvailable + */ + setShowMoreAvailable: function () { + if (this.get('parentView.isOpen')) { + this.set('isShowMore', App.router.get("backgroundOperationsController.isShowMoreAvailable")); + } + }.observes('parentView.isOpen', 'App.router.backgroundOperationsController.isShowMoreAvailable'), + + /** + * Onclick handler for selected Service + * + * @param {{context: wrappedService}} event + * @method gotoHosts + */ + gotoHosts: function (event) { + this.get("controller").set("serviceName", event.context.get("name")); + this.get("controller").set("currentServiceId", event.context.get("id")); + this.get("controller").set("currentHostName", null); + this.get("controller").onHostUpdate(); + this.switchLevel("HOSTS_LIST"); + var servicesInfo = this.get("controller.hosts"); + this.set("controller.popupHeaderName", event.context.get("name")); + this.set("controller.operationInfo", event.context); + + //apply lazy loading on cluster with more than 100 nodes + this.set('hosts', servicesInfo.length > 100 ? servicesInfo.slice(0, 50) : servicesInfo); + this.set("parentView.isServiceListHidden", true); + this.set("parentView.isHostListHidden", false); + this.set("parentView.isTaskListHidden", true); + $(".modal").scrollTop(0); + $(".modal-body").scrollTop(0); + if (servicesInfo.length > 100) { + Em.run.next(this, function () { + this.set('hosts', this.get('hosts').concat(servicesInfo.slice(50, servicesInfo.length))); + }); + } + // Determine if source request schedule is present + this.set('sourceRequestScheduleId', event.context.get("sourceRequestScheduleId")); + this.set('sourceRequestScheduleCommand', event.context.get('contextCommand')); + this.refreshRequestScheduleInfo(); + }, + + /** + * @type {boolean} + */ + isRequestSchedule: function () { + var id = this.get('sourceRequestScheduleId'); + return id != null && !isNaN(id) && id > -1; + }.property('sourceRequestScheduleId'), + + /** + * @method refreshRequestScheduleInfo + */ + refreshRequestScheduleInfo: function () { + var self = this; + var id = this.get('sourceRequestScheduleId'); + batchUtils.getRequestSchedule(id, function (data) { + var status = Em.get(data || {}, 'RequestSchedule.status'); + if (status) { + switch (status) { + case 'DISABLED': + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', true); + break; + case 'COMPLETED': + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + break; + case 'SCHEDULED': + self.set('sourceRequestScheduleRunning', true); + self.set('sourceRequestScheduleAborted', false); + break; + } + } + else { + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + } + }, function () { + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + }); + }.observes('sourceRequestScheduleId'), + + /** + * Attempts to abort the current request schedule + * + * @param {{context: number}} event + * @method doAbortRequestSchedule + */ + doAbortRequestSchedule: function (event) { + var self = this; + var id = event.context; + batchUtils.doAbortRequestSchedule(id, function () { + self.refreshRequestScheduleInfo(); + }); + }, + + /** + * Onclick handler for selected Host + * + * @param {{context: wrappedHost}} event + * @method gotoTasks + */ + gotoTasks: function (event) { + var tasksInfo = []; + event.context.logTasks.forEach(function (_task) { + tasksInfo.pushObject(this.get("controller").createTask(_task)); + }, this); + if (tasksInfo.length) { + this.get("controller").set("popupHeaderName", event.context.publicName); + this.get("controller").set("currentHostName", event.context.publicName); + } + this.switchLevel("TASKS_LIST"); + this.set('currentHost.tasks', tasksInfo); + this.set("parentView.isHostListHidden", true); + this.set("parentView.isTaskListHidden", false); + $(".modal").scrollTop(0); + $(".modal-body").scrollTop(0); + }, + + /** + * @method stopRebalanceHDFS + * @returns {App.ModalPopup} + */ + stopRebalanceHDFS: function () { + var hostPopup = this; + return App.showConfirmationPopup(function () { + App.ajax.send({ + name: 'cancel.background.operation', + sender: hostPopup, + data: { + requestId: hostPopup.get('controller.currentServiceId') + } + }); + hostPopup.backToServiceList(); + }); + }, + + /** + * Onclick handler for selected Task + * + * @method openTaskLogInDialog + */ + openTaskLogInDialog: function () { + if ($(".task-detail-log-clipboard").length) { + this.destroyClipBoard(); + } + var newWindow = window.open(); + var newDocument = newWindow.document; + newDocument.write($(".task-detail-log-info").html()); + newDocument.close(); + }, + + /** + * Onclick event for show task detail info + * + * @param {{context: wrappedTask}} event + * @method toggleTaskLog + */ + toggleTaskLog: function (event) { + var taskInfo = event.context; + this.set("parentView.isLogWrapHidden", false); + if ($(".task-detail-log-clipboard").length) { + this.destroyClipBoard(); + } + this.set("parentView.isHostListHidden", true); + this.set("parentView.isTaskListHidden", true); + this.set('openedTaskId', taskInfo.id); + this.switchLevel("TASK_DETAILS"); + $(".modal").scrollTop(0); + $(".modal-body").scrollTop(0); + }, + + /** + * Onclick event for copy to clipboard button + * + * @method textTrigger + */ + textTrigger: function () { + $(".task-detail-log-clipboard").length ? this.destroyClipBoard() : this.createClipBoard(); + }, + + /** + * Create Clip Board + * + * @method createClipBoard + */ + createClipBoard: function () { + var logElement = $(".task-detail-log-maintext"); + $(".task-detail-log-clipboard-wrap").html('<textarea class="task-detail-log-clipboard"></textarea>'); + $(".task-detail-log-clipboard") + .html("stderr: \n" + $(".stderr").html() + "\n stdout:\n" + $(".stdout").html()) + .css('display', 'block') + .width(logElement.width()) + .height(logElement.height()) + .select(); + + logElement.css("display", "none"); + }, + + /** + * Destroy Clip Board + * + * @method destroyClipBoard + */ + destroyClipBoard: function () { + $(".task-detail-log-clipboard").remove(); + $(".task-detail-log-maintext").css("display", "block"); + } + +}); \ No newline at end of file
