wizardProgressPageController.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. /**
  20. * Mixin for wizard controller for showing command progress on wizard pages
  21. * This should
  22. * @type {Ember.Mixin}
  23. */
  24. App.wizardProgressPageControllerMixin = Em.Mixin.create({
  25. controllerName: '',
  26. clusterDeployState: 'WIZARD_DEPLOY',
  27. status: 'IN_PROGRESS',
  28. tasks: [],
  29. commands: [],
  30. currentRequestIds: [], //todo: replace with using requestIds from tasks
  31. logs: [],
  32. currentTaskId: null,
  33. POLL_INTERVAL: 4000,
  34. isSubmitDisabled: true,
  35. isBackButtonDisabled: true,
  36. stages: [],
  37. /**
  38. * List of statuses that inform about the end of request progress.
  39. * @type {String[]}
  40. */
  41. completedStatuses: ['COMPLETED', 'FAILED', 'TIMEDOUT', 'ABORTED'],
  42. currentPageRequestId: null,
  43. isSingleRequestPage: false,
  44. isCommandLevelRetry: function () {
  45. return !this.get('isSingleRequestPage');
  46. }.property('isSingleRequestPage'),
  47. showRetry: false,
  48. /**
  49. * Show whether tasks data was loaded
  50. * @type {Boolean}
  51. */
  52. isLoading: false,
  53. k: Em.K,
  54. /**
  55. * tasksMessagesPrefix should be overloaded by any controller including the mixin
  56. */
  57. tasksMessagesPrefix: '',
  58. loadStep: function () {
  59. this.clearStep();
  60. var self = this;
  61. if (!self.isSingleRequestPage) {
  62. this.initStep();
  63. } else {
  64. var requestIds = this.get('content.tasksRequestIds');
  65. var currentRequestId = requestIds && requestIds[0][0];
  66. if (!currentRequestId) {
  67. this.set('isLoaded', false);
  68. this.submitRequest();
  69. } else {
  70. self.set('currentPageRequestId', currentRequestId);
  71. self.doPollingForPageRequest();
  72. }
  73. }
  74. },
  75. initStep: function () {
  76. this.initializeTasks();
  77. if (!this.isSingleRequestPage) {
  78. this.loadTasks();
  79. }
  80. this.addObserver('tasks.@each.status', this, 'onTaskStatusChange');
  81. if (this.isSingleRequestPage) {
  82. var dfd = $.Deferred();
  83. var dfdObject = {
  84. deferred: dfd,
  85. isJqueryPromise: true
  86. };
  87. this.onTaskStatusChange(dfdObject);
  88. return dfd.promise();
  89. } else {
  90. this.onTaskStatusChange();
  91. }
  92. },
  93. clearStep: function () {
  94. this.removeObserver('tasks.@each.status', this, 'onTaskStatusChange');
  95. this.set('isSubmitDisabled', true);
  96. this.set('isBackButtonDisabled', true);
  97. this.set('tasks', []);
  98. this.set('currentRequestIds', []);
  99. this.set('isLoaded', false);
  100. },
  101. /**
  102. * Clear stages info for single page request.
  103. */
  104. clearStage: function() {
  105. this.setDBProperty('tasksRequestIds', null);
  106. this.setDBProperty('tasksStatuses', null);
  107. this.set('showRetry', false);
  108. this.set('content.tasksRequestIds', null);
  109. this.set('content.tasksStatuses', null);
  110. this.set('content.currentTaskId', null);
  111. this.get('stages').clear();
  112. },
  113. retry: function () {
  114. this.set('showRetry', false);
  115. this.get('tasks').setEach('status','PENDING');
  116. this.loadStep();
  117. },
  118. submitRequest: function () {
  119. return App.ajax.send({
  120. name: this.get('request.ajaxName'),
  121. data: this.get('request.ajaxData'),
  122. sender: this,
  123. error: 'onSingleRequestError',
  124. success: 'submitRequestSuccess',
  125. kdcCancelHandler: 'failTaskOnKdcCheck'
  126. });
  127. },
  128. submitRequestSuccess: function(data, result, request) {
  129. if (data) {
  130. this.set('currentPageRequestId', data.Requests.id);
  131. this.doPollingForPageRequest();
  132. } else {
  133. //Step has been successfully completed
  134. if (request.status === 200) {
  135. this.set('status', 'COMPLETED');
  136. this.set('isSubmitDisabled', false);
  137. this.set('isLoaded', true);
  138. }
  139. }
  140. },
  141. failTaskOnKdcCheck: function() {
  142. this.set('status', 'FAILED');
  143. this.set('isLoaded', true);
  144. this.set('showRetry', true);
  145. },
  146. doPollingForPageRequest: function () {
  147. App.ajax.send({
  148. name: 'admin.poll.kerberize.cluster.request',
  149. sender: this,
  150. data: {
  151. requestId: this.get('currentPageRequestId')
  152. },
  153. success: 'initializeStages'
  154. });
  155. },
  156. initializeStages: function (data) {
  157. var self = this;
  158. var stages = [];
  159. this.set('logs', []);
  160. data.stages.forEach(function (_stage) {
  161. stages.pushObject(Em.Object.create(_stage.Stage));
  162. }, this);
  163. if (!this.get('stages').length) {
  164. this.get('stages').pushObjects(stages);
  165. this.initStep().done(function(){
  166. self.updatePageWithPolledData(data);
  167. });
  168. } else {
  169. this.updatePageWithPolledData(data);
  170. }
  171. },
  172. updatePageWithPolledData: function(data) {
  173. // If all tasks completed no need to update each task status.
  174. // Preferable to skip polling of data for completed tasks after page refresh.
  175. if (this.get('status') === 'COMPLETED') return;
  176. var self = this;
  177. var tasks = [];
  178. var currentPageRequestId = this.get('currentPageRequestId');
  179. var currentTaskId = this.get('currentTaskId');
  180. var currentTask = this.get('tasks').findProperty('id', currentTaskId);
  181. var currentStage = data.stages.findProperty('Stage.stage_id', currentTask.get('stageId'));
  182. var tasksInCurrentStage = currentStage.tasks;
  183. this.set('logs',tasksInCurrentStage);
  184. this.setRequestIds(this.get('currentTaskId'), [this.get('currentPageRequestId')]);
  185. if (!tasksInCurrentStage.someProperty('Tasks.status', 'PENDING') && !tasksInCurrentStage.someProperty('Tasks.status', 'QUEUED') && !tasksInCurrentStage.someProperty('Tasks.status', 'IN_PROGRESS')) {
  186. this.set('currentRequestIds', []);
  187. if (tasksInCurrentStage.someProperty('Tasks.status', 'FAILED') || tasksInCurrentStage.someProperty('Tasks.status', 'TIMEDOUT') || tasksInCurrentStage.someProperty('Tasks.status', 'ABORTED')) {
  188. this.setTaskStatus(currentTaskId, 'FAILED');
  189. } else {
  190. this.setTaskStatus(currentTaskId, 'COMPLETED');
  191. }
  192. } else {
  193. var completedActions = tasksInCurrentStage.filterProperty('Tasks.status', 'COMPLETED').length
  194. + tasksInCurrentStage.filterProperty('Tasks.status', 'FAILED').length
  195. + tasksInCurrentStage.filterProperty('Tasks.status', 'ABORTED').length
  196. + tasksInCurrentStage.filterProperty('Tasks.status', 'TIMEDOUT').length;
  197. var queuedActions = tasksInCurrentStage.filterProperty('Tasks.status', 'QUEUED').length;
  198. var inProgressActions = tasksInCurrentStage.filterProperty('Tasks.status', 'IN_PROGRESS').length;
  199. var progress = Math.floor(((queuedActions * 0.09) + (inProgressActions * 0.35) + completedActions ) / tasksInCurrentStage.length * 100);
  200. this.get('tasks').findProperty('id', currentTaskId).set('progress', progress);
  201. }
  202. // start polling if current request not completed
  203. if (!(this.get('completedStatuses').contains(this.get('status')))) {
  204. window.setTimeout(function () {
  205. self.doPollingForPageRequest();
  206. }, self.POLL_INTERVAL);
  207. }
  208. },
  209. initializeTasks: function () {
  210. var self = this;
  211. var commands = this.isSingleRequestPage ? this.get('stages') : this.get('commands');
  212. var currentStep = App.router.get(this.get('content.controllerName') + '.currentStep');
  213. var tasksMessagesPrefix = this.get('tasksMessagesPrefix');
  214. // check that all stages have been completed for single request type
  215. var allStagesCompleted = commands.everyProperty('status', 'COMPLETED');
  216. for (var i = 0; i < commands.length; i++) {
  217. this.get('tasks').pushObject(Ember.Object.create({
  218. title: self.isSingleRequestPage ? commands[i].get('context') : Em.I18n.t(tasksMessagesPrefix + currentStep + '.task' + i + '.title'),
  219. // set COMPLETED status for task if all stages completed successfully
  220. status: allStagesCompleted ? 'COMPLETED' : 'PENDING',
  221. id: i,
  222. stageId: self.isSingleRequestPage ? commands[i].get('stage_id') : null,
  223. command: self.isSingleRequestPage ? 'k' : commands[i],
  224. showRetry: false,
  225. showRollback: false,
  226. name: self.isSingleRequestPage ? commands[i].get('context') : Em.I18n.t(tasksMessagesPrefix + currentStep + '.task' + i + '.title'),
  227. displayName: self.isSingleRequestPage ? commands[i].get('context') : Em.I18n.t(tasksMessagesPrefix + currentStep + '.task' + i + '.title'),
  228. progress: 0,
  229. isRunning: false,
  230. requestIds: self.isSingleRequestPage ? [this.get('stages')[0].request_id] : []
  231. }));
  232. }
  233. this.set('isLoaded', true);
  234. },
  235. loadTasks: function () {
  236. var self = this;
  237. var loadedStatuses = this.get('content.tasksStatuses');
  238. var loadedRequestIds = this.get('content.tasksRequestIds');
  239. if (loadedStatuses && loadedStatuses.length === this.get('tasks').length) {
  240. this.get('tasks').forEach(function (task, i) {
  241. self.setTaskStatus(task.get('id'), loadedStatuses[i]);
  242. self.setRequestIds(task.get('id'), loadedRequestIds[i]);
  243. });
  244. if (loadedStatuses.contains('IN_PROGRESS')) {
  245. var curTaskId = this.get('tasks')[loadedStatuses.indexOf('IN_PROGRESS')].get('id');
  246. this.set('currentRequestIds', this.get('content.requestIds'));
  247. this.set('currentTaskId', curTaskId);
  248. this.doPolling();
  249. } else if (loadedStatuses.contains('QUEUED')) {
  250. var curTaskId = this.get('tasks')[loadedStatuses.indexOf('QUEUED')].get('id');
  251. this.set('currentTaskId', curTaskId);
  252. this.runTask(curTaskId);
  253. }
  254. }
  255. },
  256. setTaskStatus: function (taskId, status) {
  257. this.get('tasks').findProperty('id', taskId).set('status', status);
  258. },
  259. setRequestIds: function (taskId, requestIds) {
  260. this.get('tasks').findProperty('id', taskId).set('requestIds', requestIds);
  261. },
  262. retryTask: function () {
  263. var task = this.get('tasks').findProperty('status', 'FAILED');
  264. task.set('showRetry', false);
  265. task.set('showRollback', false);
  266. task.set('status', 'PENDING');
  267. },
  268. onTaskStatusChange: function (dfdObject) {
  269. var statuses = this.get('tasks').mapProperty('status');
  270. var tasksRequestIds = this.get('tasks').mapProperty('requestIds');
  271. var requestIds = this.get('currentRequestIds');
  272. // save task info
  273. App.router.get(this.get('content.controllerName')).saveTasksStatuses(statuses);
  274. App.router.get(this.get('content.controllerName')).saveTasksRequestIds(tasksRequestIds);
  275. App.router.get(this.get('content.controllerName')).saveRequestIds(requestIds);
  276. // call saving of cluster status asynchronous
  277. // synchronous executing cause problems in Firefox
  278. var successCallbackData;
  279. if (dfdObject && dfdObject.isJqueryPromise) {
  280. successCallbackData = {deferred: dfdObject.deferred};
  281. }
  282. App.clusterStatus.setClusterStatus({
  283. clusterName: App.router.getClusterName(),
  284. clusterState: this.get('clusterDeployState'),
  285. wizardControllerName: this.get('content.controllerName'),
  286. localdb: App.db.data
  287. }, {successCallback: this.statusChangeCallback, sender: this, successCallbackData: successCallbackData});
  288. },
  289. /**
  290. * Method that called after saving persist data to server.
  291. * Switch task according its status.
  292. */
  293. statusChangeCallback: function (data) {
  294. if (!this.get('tasks').someProperty('status', 'IN_PROGRESS') && !this.get('tasks').someProperty('status', 'QUEUED') && !this.get('tasks').someProperty('status', 'FAILED')) {
  295. var nextTask = this.get('tasks').findProperty('status', 'PENDING');
  296. if (nextTask) {
  297. this.set('status', 'IN_PROGRESS');
  298. var taskStatus = this.isSingleRequestPage ? 'IN_PROGRESS' : 'QUEUED';
  299. this.setTaskStatus(nextTask.get('id'), taskStatus);
  300. this.set('currentTaskId', nextTask.get('id'));
  301. this.runTask(nextTask.get('id'));
  302. } else {
  303. this.set('status', 'COMPLETED');
  304. this.set('isSubmitDisabled', false);
  305. this.set('isBackButtonDisabled', false);
  306. }
  307. } else if (this.get('tasks').someProperty('status', 'FAILED')) {
  308. this.set('status', 'FAILED');
  309. this.set('isBackButtonDisabled', false);
  310. if (this.get('isCommandLevelRetry')) {
  311. this.get('tasks').findProperty('status', 'FAILED').set('showRetry', true);
  312. } else {
  313. this.set('showRetry', true);
  314. }
  315. if (App.supports.autoRollbackHA) {
  316. this.get('tasks').findProperty('status', 'FAILED').set('showRollback', true);
  317. }
  318. }
  319. this.get('tasks').filterProperty('status', 'COMPLETED').setEach('showRetry', false);
  320. this.get('tasks').filterProperty('status', 'COMPLETED').setEach('showRollback', false);
  321. if (data && data.deferred) {
  322. data.deferred.resolve();
  323. }
  324. },
  325. /**
  326. * Run command of appropriate task
  327. */
  328. runTask: function (taskId) {
  329. this[this.get('tasks').findProperty('id', taskId).get('command')]();
  330. },
  331. onTaskError: function () {
  332. this.setTaskStatus(this.get('currentTaskId'), 'FAILED');
  333. },
  334. onSingleRequestError: function (jqXHR, ajaxOptions, error, opt) {
  335. App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
  336. this.set('status', 'FAILED');
  337. this.set('isLoaded', true);
  338. this.set('showRetry', true);
  339. },
  340. onTaskCompleted: function () {
  341. this.setTaskStatus(this.get('currentTaskId'), 'COMPLETED');
  342. },
  343. /**
  344. * check whether component installed on specified hosts
  345. * @param {string} componentName
  346. * @param {string[]} hostNames
  347. * @return {$.ajax}
  348. */
  349. checkInstalledComponents: function (componentName, hostNames) {
  350. return App.ajax.send({
  351. name: 'host_component.installed.on_hosts',
  352. sender: this,
  353. data: {
  354. componentName: componentName,
  355. hostNames: hostNames.join(',')
  356. }
  357. });
  358. },
  359. /**
  360. * Create component on single or multiple hosts.
  361. *
  362. * @method createComponent
  363. * @param {string} componentName - name of the component
  364. * @param {(string|string[])} hostName - host/hosts where components should be installed
  365. * @param {string} serviceName - name of the services
  366. */
  367. createComponent: function (componentName, hostName, serviceName) {
  368. var hostNames = (Array.isArray(hostName)) ? hostName : [hostName];
  369. var self = this;
  370. this.set('showRetry', false);
  371. this.checkInstalledComponents(componentName, hostNames).then(function (data) {
  372. var hostsWithComponents = data.items.mapProperty('HostRoles.host_name');
  373. var result = hostNames.map(function(item) {
  374. return {
  375. componentName: componentName,
  376. hostName: item,
  377. hasComponent: hostsWithComponents.contains(item)
  378. };
  379. });
  380. var hostsWithoutComponents = result.filterProperty('hasComponent', false).mapProperty('hostName');
  381. var taskNum = 1;
  382. var requestData = {
  383. "RequestInfo": {
  384. "query": hostsWithoutComponents.map(function(item) {
  385. return 'Hosts/host_name=' + item;
  386. }).join('|')
  387. },
  388. "Body": {
  389. "host_components": [
  390. {
  391. "HostRoles": {
  392. "component_name": componentName
  393. }
  394. }
  395. ]
  396. }
  397. };
  398. if (!!hostsWithoutComponents.length) {
  399. App.ajax.send({
  400. name: 'wizard.step8.register_host_to_component',
  401. sender: self,
  402. data: {
  403. data: JSON.stringify(requestData),
  404. hostName: result.mapProperty('hostName'),
  405. componentName: componentName,
  406. serviceName: serviceName,
  407. taskNum: taskNum,
  408. cluster: App.get('clusterName')
  409. },
  410. success: 'onCreateComponent',
  411. error: 'onCreateComponent'
  412. });
  413. } else {
  414. self.onCreateComponent(null, null, {
  415. hostName: result.mapProperty('hostName'),
  416. componentName: componentName,
  417. serviceName: serviceName,
  418. taskNum: taskNum
  419. }, self);
  420. }
  421. });
  422. },
  423. onCreateComponent: function () {
  424. var hostName = arguments[2].hostName;
  425. var componentName = arguments[2].componentName;
  426. var taskNum = arguments[2].taskNum;
  427. var serviceName = arguments[2].serviceName;
  428. this.updateComponent(componentName, hostName, serviceName, "Install", taskNum);
  429. },
  430. onCreateComponentError: function (error) {
  431. if (error.responseText.indexOf('org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException') !== -1) {
  432. this.onCreateComponent();
  433. } else {
  434. this.onTaskError();
  435. }
  436. },
  437. /**
  438. * Update component status on selected hosts.
  439. *
  440. * @param {string} componentName
  441. * @param {(string|string[])} hostName
  442. * @param {string} serviceName
  443. * @param {string} context
  444. * @param {number} taskNum
  445. * @returns {$.ajax}
  446. */
  447. updateComponent: function (componentName, hostName, serviceName, context, taskNum) {
  448. if (!(hostName instanceof Array)) {
  449. hostName = [hostName];
  450. }
  451. var state = context.toLowerCase() == "start" ? "STARTED" : "INSTALLED";
  452. return App.ajax.send({
  453. name: 'common.host_components.update',
  454. sender: this,
  455. data: {
  456. HostRoles: {
  457. state: state
  458. },
  459. query: 'HostRoles/component_name=' + componentName + '&HostRoles/host_name.in(' + hostName.join(',') + ')&HostRoles/maintenance_state=OFF',
  460. context: context + " " + App.format.role(componentName),
  461. hostName: hostName,
  462. taskNum: taskNum || 1,
  463. componentName: componentName,
  464. serviceName: serviceName
  465. },
  466. success: 'startPolling',
  467. error: 'onTaskError'
  468. });
  469. },
  470. startPolling: function (data) {
  471. if (data) {
  472. this.get('currentRequestIds').push(data.Requests.id);
  473. var tasksCount = arguments[2].taskNum || 1;
  474. if (tasksCount === this.get('currentRequestIds').length) {
  475. this.setRequestIds(this.get('currentTaskId'), this.get('currentRequestIds'));
  476. this.doPolling();
  477. }
  478. } else {
  479. this.onTaskCompleted();
  480. }
  481. },
  482. doPolling: function () {
  483. this.setTaskStatus(this.get('currentTaskId'), 'IN_PROGRESS');
  484. var requestIds = this.get('currentRequestIds');
  485. this.set('logs', []);
  486. for (var i = 0; i < requestIds.length; i++) {
  487. App.ajax.send({
  488. name: 'background_operations.get_by_request',
  489. sender: this,
  490. data: {
  491. requestId: requestIds[i]
  492. },
  493. success: 'parseLogs',
  494. error: 'onTaskError'
  495. });
  496. }
  497. },
  498. parseLogs: function (logs) {
  499. this.get('logs').pushObject(logs.tasks);
  500. if (this.get('currentRequestIds').length === this.get('logs').length) {
  501. var tasks = [];
  502. this.get('logs').forEach(function (logs) {
  503. tasks.pushObjects(logs);
  504. }, this);
  505. var self = this;
  506. var currentTaskId = this.get('currentTaskId');
  507. if (!tasks.someProperty('Tasks.status', 'PENDING') && !tasks.someProperty('Tasks.status', 'QUEUED') && !tasks.someProperty('Tasks.status', 'IN_PROGRESS')) {
  508. this.set('currentRequestIds', []);
  509. if (tasks.someProperty('Tasks.status', 'FAILED') || tasks.someProperty('Tasks.status', 'TIMEDOUT') || tasks.someProperty('Tasks.status', 'ABORTED')) {
  510. this.setTaskStatus(currentTaskId, 'FAILED');
  511. } else {
  512. this.setTaskStatus(currentTaskId, 'COMPLETED');
  513. }
  514. } else {
  515. var actionsPerHost = tasks.length;
  516. var completedActions = tasks.filterProperty('Tasks.status', 'COMPLETED').length
  517. + tasks.filterProperty('Tasks.status', 'FAILED').length
  518. + tasks.filterProperty('Tasks.status', 'ABORTED').length
  519. + tasks.filterProperty('Tasks.status', 'TIMEDOUT').length;
  520. var queuedActions = tasks.filterProperty('Tasks.status', 'QUEUED').length;
  521. var inProgressActions = tasks.filterProperty('Tasks.status', 'IN_PROGRESS').length;
  522. var progress = Math.floor(((queuedActions * 0.09) + (inProgressActions * 0.35) + completedActions ) / actionsPerHost * 100);
  523. this.get('tasks').findProperty('id', currentTaskId).set('progress', progress);
  524. window.setTimeout(function () {
  525. self.doPolling();
  526. }, self.POLL_INTERVAL);
  527. }
  528. }
  529. },
  530. showHostProgressPopup: function (event) {
  531. var popupTitle = event.contexts[0].title;
  532. var requestIds = event.contexts[0].requestIds;
  533. var stageId = event.contexts[0].stageId;
  534. var hostProgressPopupController = App.router.get('highAvailabilityProgressPopupController');
  535. hostProgressPopupController.initPopup(popupTitle, requestIds, this, true, stageId);
  536. },
  537. done: function () {
  538. if (!this.get('isSubmitDisabled')) {
  539. this.removeObserver('tasks.@each.status', this, 'onTaskStatusChange');
  540. App.router.send('next');
  541. }
  542. },
  543. back: function () {
  544. if (!this.get('isBackButtonDisabled')) {
  545. this.removeObserver('tasks.@each.status', this, 'onTaskStatusChange');
  546. App.router.send('back');
  547. }
  548. }
  549. });