Browse Source

AMBARI-5199. Replace $.ajax with App.ajax anywhere where it's possible. (onechiporenko)

Oleg Nechiporenko 11 years ago
parent
commit
0fbdb6cc2b

+ 16 - 16
ambari-web/app/app.js

@@ -58,6 +58,7 @@ module.exports = Em.Application.create({
     if (falconService) {
       return falconService.get('hostComponents').findProperty('componentName', 'FALCON_SERVER').get('host.hostName');
     }
+    return '';
   }.property().volatile(),
 
   clusterName: null,
@@ -79,7 +80,7 @@ module.exports = Em.Application.create({
    * If High Availability is enabled
    * Based on <code>clusterStatus.isInstalled</code>, stack version, <code>SNameNode</code> availability
    *
-   * @type {Boolean}
+   * @type {bool}
    */
   isHaEnabled: function() {
     if (!this.get('isHadoop2Stack')) return false;
@@ -242,18 +243,17 @@ module.exports = Em.Application.create({
    * @type {Em.Object}
    */
   components: function() {
-    var self = this;
-    return Ember.Object.create({
-      allComponents:self.StackServiceComponent.find().mapProperty('componentName'),
-      reassignable: self.StackServiceComponent.find().filterProperty('isReassignable',true).mapProperty('componentName'),
-      restartable: self.StackServiceComponent.find().filterProperty('isRestartable',true).mapProperty('componentName'),
-      deletable: self.StackServiceComponent.find().filterProperty('isDeletable',true).mapProperty('componentName'),
-      rollinRestartAllowed: self.StackServiceComponent.find().filterProperty('isRollinRestartAllowed',true).mapProperty('componentName'),
-      decommissionAllowed: self.StackServiceComponent.find().filterProperty('isDecommissionAllowed',true).mapProperty('componentName'),
-      addableToHost: self.StackServiceComponent.find().filterProperty('isAddableToHost',true).mapProperty('componentName'),
-      slaves: self.StackServiceComponent.find().filterProperty('isMaster',false).filterProperty('isClient',false).mapProperty('componentName'),
-      masters: self.StackServiceComponent.find().filterProperty('isMaster',true).mapProperty('componentName'),
-      clients: self.StackServiceComponent.find().filterProperty('isClient',true).mapProperty('componentName')
-    })
-  }.property()
-});
+    return Em.Object.create({
+      allComponents:this.StackServiceComponent.find().mapProperty('componentName'),
+      reassignable: this.StackServiceComponent.find().filterProperty('isReassignable',true).mapProperty('componentName'),
+      restartable: this.StackServiceComponent.find().filterProperty('isRestartable',true).mapProperty('componentName'),
+      deletable: this.StackServiceComponent.find().filterProperty('isDeletable',true).mapProperty('componentName'),
+      rollinRestartAllowed: this.StackServiceComponent.find().filterProperty('isRollinRestartAllowed',true).mapProperty('componentName'),
+      decommissionAllowed: this.StackServiceComponent.find().filterProperty('isDecommissionAllowed',true).mapProperty('componentName'),
+      addableToHost: this.StackServiceComponent.find().filterProperty('isAddableToHost',true).mapProperty('componentName'),
+      slaves: this.StackServiceComponent.find().filterProperty('isMaster',false).filterProperty('isClient',false).mapProperty('componentName'),
+      masters: this.StackServiceComponent.find().filterProperty('isMaster',true).mapProperty('componentName'),
+      clients: this.StackServiceComponent.find().filterProperty('isClient',true).mapProperty('componentName')
+    });
+  }.property('App.router.clusterController.isLoaded')
+});

+ 2 - 1
ambari-web/app/assets/test/tests.js

@@ -73,7 +73,8 @@ require('test/utils/configs/defaults_providers/yarn_defaults_provider_test');
 require('test/utils/configs/defaults_providers/tez_defaults_provider_test');
 require('test/utils/configs/defaults_providers/hive_defaults_provider_test');
 require('test/utils/configs/validators/service_configs_validator_test');
-require('test/utils/ajax_test');
+require('test/utils/ajax/ajax_test');
+require('test/utils/ajax/ajax_queue_test');
 require('test/utils/batch_scheduled_requests_test');
 require('test/utils/config_test');
 require('test/utils/date_test');

+ 33 - 66
ambari-web/app/controllers/main/admin/user.js

@@ -19,91 +19,58 @@
 var App = require('app');
 
 App.MainAdminUserController = Em.Controller.extend({
-  name:'mainAdminUserController',
+
+  name: 'mainAdminUserController',
 
   /**
-   * send request to the server to delete user if selected user is not current user
-   * @param event
+   * Send request to the server to delete user if selected user is not current user
+   * @param {object} event
+   * @method deleteRecord
    */
-  deleteRecord:function (event) {
+  deleteRecord: function (event) {
     var self = this;
     if (event.context.get('userName') == App.get('router').getLoginName()) {
       App.ModalPopup.show({
-        header:Em.I18n.t('admin.users.delete.yourself.header'),
-        body:Em.I18n.t('admin.users.delete.yourself.message'),
-        onPrimary:function (event) {
-          this.hide();
-        },
-        secondary:false
+        header: Em.I18n.t('admin.users.delete.yourself.header'),
+        body: Em.I18n.t('admin.users.delete.yourself.message'),
+        secondary: false
       });
-
       return;
     }
 
     App.ModalPopup.show({
-      header:Em.I18n.t('admin.users.delete.header').format(event.context.get('userName')),
-      body:Em.I18n.t('question.sure').format(''),
-      primary:Em.I18n.t('yes'),
-      secondary:Em.I18n.t('no'),
-
-      onPrimary:function () {
-        self.sendCommandToServer('/users/' +  event.context.get("userName"), "DELETE" ,{},
-          function (success) {
-
-            if (!success) {
-              return;
-            }
-
-            event.context.deleteRecord();
-
-            try {
-              App.store.commit();
-            } catch (err) {
+      header: Em.I18n.t('admin.users.delete.header').format(event.context.get('userName')),
+      body: Em.I18n.t('question.sure').format(''),
+      primary: Em.I18n.t('yes'),
+      secondary: Em.I18n.t('no'),
 
-            }
-          });
-        this.hide();
-      },
-      onSecondary:function () {
+      onPrimary: function () {
+        App.ajax.send({
+          name: 'admin.user.delete',
+          sender: self,
+          data: {
+            user: event.context.get("userName"),
+            event: event
+          },
+          success: 'deleteUserSuccessCallback'
+        });
         this.hide();
       }
     });
   },
 
   /**
-   * send request to the server and call callback function with true if request was success or false if request was failed
-   * @param url
-   * @param method
-   * @param postData
-   * @param callback
+   * Success callback for delete user request
+   * @param {object} data
+   * @param {object} opt
+   * @param {object} params
+   * @method deleteUserSuccessCallback
    */
-  sendCommandToServer : function(url, method, postData, callback){
-    if (App.testMode) {
-      url = '/data/users/users.json';
-      method = 'GET';
-      postData = undefined;
-    } else {
-      url = App.apiPrefix + url;
+  deleteUserSuccessCallback: function (data, opt, params) {
+    params.event.context.deleteRecord();
+    try {
+      App.store.commit();
+    } catch (err) {
     }
-
-    $.ajax({
-      type: method,
-      url: url,
-      data: JSON.stringify(postData),
-      dataType: 'json',
-      timeout: App.timeout,
-      success: function(data){
-          callback(true, '');
-      },
-
-      error: function (request, ajaxOptions, error) {
-        var message = $.parseJSON(request.responseText).message;
-        message = message.substr(message.lastIndexOf(':') + 1);
-        callback(false, message);
-        console.log(message);
-      },
-
-      statusCode: require('data/statusCodes')
-    });
   }
 });

File diff suppressed because it is too large
+ 459 - 278
ambari-web/app/controllers/main/host/details.js


File diff suppressed because it is too large
+ 323 - 225
ambari-web/app/controllers/wizard/step8_controller.js


+ 220 - 160
ambari-web/app/controllers/wizard/step9_controller.js

@@ -18,6 +18,7 @@
 var App = require('app');
 
 App.WizardStep9Controller = Em.Controller.extend({
+
   name: 'wizardStep9Controller',
 
   /**
@@ -33,17 +34,20 @@ App.WizardStep9Controller = Em.Controller.extend({
    *      isNoTasksForInstall: {Boolean} Gets set when no task is scheduled for the host on install phase.
    *    }
    *  </code>
+   *  @type {Array.<{name: string, status: string, logTasks: object[], message: string, progress: number, isNoTasksForInstall: bool}>}
    */
 
   hosts: [],
 
   /**
    * overall progress of <Install,Start and Test> page shown as progress bar on the top of the page
+   * @type {string}
    */
   progress: '0',
 
   /*
    * json file for the mock data to be used in mock mode
+   * @type {string}
    */
   mockDataPrefix: '/data/wizard/deploy/5_hosts',
 
@@ -51,15 +55,20 @@ App.WizardStep9Controller = Em.Controller.extend({
    * Current Request data polled from the API: api/v1/clusters/{clusterName}/requests/{RequestId}?fields=tasks/Tasks/command,
    * tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,
    * tasks/Tasks/status&minimal_response=true
+   * @type {Object[]}
    */
   polledData: [],
 
   /*
    * This flag is only used in UI mock mode as a counter for number of polls.
+   * @type {number}
    */
   numPolls: 1,
 
-  // Interval in milliseconds between API calls While polling the request status for Install and, Start and Test tasks
+  /**
+   * Interval in milliseconds between API calls While polling the request status for Install and, Start and Test tasks
+   * @type {number}
+   */
   POLL_INTERVAL: 4000,
 
   /**
@@ -70,16 +79,21 @@ App.WizardStep9Controller = Em.Controller.extend({
    *     componentNames: {Sting} Name of all components that are on the host
    *   }
    * </code>
+   * @type {Object[]}
    */
   hostsWithHeartbeatLost: [],
 
-   // Flag is set in the start all services error callback function
+  /**
+   * Flag is set in the start all services error callback function
+   * @type {bool}
+   */
   startCallFailed: false,
 
   /*
    * Status of the page. Possible values: <info, warning, failed and success>.
    * This property is used in the step-9 view for displaying the appropriate color of the overall progress bar and
    * the appropriate result message at the bottom of the page
+   * @type {string}
    */
   status: 'info',
 
@@ -87,6 +101,7 @@ App.WizardStep9Controller = Em.Controller.extend({
    * This computed property is used to determine if the Next button and back button on the page should be disabled. The property is
    * used in the template to grey out Next and Back buttons. Although clicking on the greyed out button do trigger the event and
    * calls submit and back function of the controller.
+   * @type {bool}
    */
   isSubmitDisabled: function () {
     var validStates = ['STARTED', 'START FAILED'];
@@ -97,24 +112,9 @@ App.WizardStep9Controller = Em.Controller.extend({
     return !validStates.contains(this.get('content.cluster.status'));
   }.property('content.cluster.status'),
 
-  /**
-   * This function is called when a click event happens on Next button of <Install, Start and Test> page
-   */
-  submit: function () {
-    App.router.send('next');
-  },
-
-  /**
-   * This function is called when a click event happens on back button of <Install, Start and Test> page
-   */
-  back: function () {
-    if (!this.get('isSubmitDisabled')) {
-      App.router.send('back');
-    }
-  },
-
   /**
    * Observer function: Enables previous steps link if install task failed in installer wizard.
+   * @method togglePreviousSteps
    */
   togglePreviousSteps: function () {
     if (App.testMode) {
@@ -128,6 +128,7 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /*
    * Computed property to determine if the Retry button should be made visible on the page.
+   * @type {bool}
    */
   showRetry: function () {
     return this.get('content.cluster.status') == 'INSTALL FAILED';
@@ -135,19 +136,22 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Observer function: Calls {hostStatusUpdates} function once with change in a host status from any registered hosts.
+   * @method hostStatusObserver
    */
   hostStatusObserver: function () {
-    Ember.run.once(this, 'updateStatus');
+    Em.run.once(this, 'updateStatus');
   }.observes('hosts.@each.status'),
 
 
   /**
    * A flag that gets set with installation failure.
+   * @type {bool}
    */
   installFailed: false,
 
   /**
    * Observer function: Updates {status} field of the controller.
+   * @method updateStatus
    */
   updateStatus: function () {
     var status = 'info';
@@ -169,9 +173,41 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Incremental flag that triggers an event in step 9 view to change the tasks related data and icons of hosts.
+   * @type {number}
    */
   logTasksChangesCounter: 0,
 
+  /**
+   * <code>taskId</code> of current open task
+   * @type {number}
+   */
+  currentOpenTaskId: 0,
+
+  /**
+   * <code>requestId</code> of current open task
+   * @type {number}
+   */
+  currentOpenTaskRequestId: 0,
+
+  /**
+   * This function is called when a click event happens on Next button of "Install, Start and Test" page
+   * @method submit
+   */
+  submit: function () {
+    App.router.send('next');
+  },
+
+  /**
+   * This function is called when a click event happens on back button of "Install, Start and Test" page
+   * @method back
+   */
+  back: function () {
+    if (!this.get('isSubmitDisabled')) {
+      App.router.send('back');
+    }
+  },
+
+
   /**
    * navigateStep is called by App.WizardStep9View's didInsertElement and "retry" from router.
    * content.cluster.status can be:
@@ -186,6 +222,7 @@ App.WizardStep9Controller = Em.Controller.extend({
    * set to false upon successful transition from step 1 to step 2
    * set to true upon successful start of services in this step
    * note: looks like this is the same thing as checking content.cluster.status == 'STARTED'
+   * @method navigateStep
    */
   navigateStep: function () {
     if (App.testMode) {
@@ -201,14 +238,17 @@ App.WizardStep9Controller = Em.Controller.extend({
       if (clusterStatus === 'INSTALL FAILED') {
         this.loadStep();
         this.loadLogData(this.get('content.cluster.requestId'));
-      } else if (clusterStatus === 'START FAILED') {
-        this.loadStep();
-        this.loadLogData(this.get('content.cluster.requestId'));
       } else {
-        // handle PENDING, INSTALLED
-        this.loadStep();
-        this.loadLogData(this.get('content.cluster.requestId'));
-        this.startPolling();
+        if (clusterStatus === 'START FAILED') {
+          this.loadStep();
+          this.loadLogData(this.get('content.cluster.requestId'));
+        }
+        else {
+          // handle PENDING, INSTALLED
+          this.loadStep();
+          this.loadLogData(this.get('content.cluster.requestId'));
+          this.startPolling();
+        }
       }
     } else {
       // handle STARTED
@@ -222,11 +262,12 @@ App.WizardStep9Controller = Em.Controller.extend({
   /**
    * This is called on initial page load, refreshes and retry event.
    * clears all in memory stale data for retry event.
+   * @method clearStep
    */
   clearStep: function () {
     this.get('hosts').clear();
     this.set('hostsWithHeartbeatLost', []);
-    this.set('startCallFailed',false);
+    this.set('startCallFailed', false);
     this.set('status', 'info');
     this.set('progress', '0');
     this.set('numPolls', 1);
@@ -234,6 +275,7 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * This is called on initial page load, refreshes and retry event.
+   * @method loadStep
    */
   loadStep: function () {
     console.log("TRACE: Loading step9: Install, Start and Test");
@@ -244,19 +286,23 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Reset status and message of all hosts when retry install
+   * @method resetHostsForRetry
    */
   resetHostsForRetry: function () {
     var hosts = this.get('content.hosts');
     for (var name in hosts) {
-      hosts[name].status = "pending";
-      hosts[name].message = 'Waiting';
-      hosts[name].isNoTasksForInstall = false;
+      if (hosts.hasOwnProperty(name)) {
+        hosts[name].status = "pending";
+        hosts[name].message = 'Waiting';
+        hosts[name].isNoTasksForInstall = false;
+      }
     }
     this.set('content.hosts', hosts);
   },
 
   /**
-   * Sets the {hosts} array for the controller
+   * Sets the <code>hosts</code> array for the controller
+   * @method loadHosts
    */
   loadHosts: function () {
     var hosts = this.get('content.hosts');
@@ -276,18 +322,20 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
-   *
-   * @param polledData: sets the {polledData} object of the controller
+   * Set new polled data
+   * @param polledData sets the <code>polledData</code> object of the controller
+   * @method replacePolledData
    */
   replacePolledData: function (polledData) {
-    this.polledData.clear();
+    this.get('polledData').clear();
     this.set('polledData', polledData);
   },
 
   /**
-   *
-   * @param task
-   * @returns {String} The appropriate message for the host as per the running task.
+   * Get the appropriate message for the host as per the running task
+   * @param {object} task
+   * @returns {String}
+   * @method displayMessage
    */
   displayMessage: function (task) {
     var role = App.format.role(task.role);
@@ -305,6 +353,7 @@ App.WizardStep9Controller = Em.Controller.extend({
           case 'FAILED':
             return Em.I18n.t('installer.step9.serviceStatus.install.failed') + role;
         }
+        break;
       case 'UNINSTALL':
         switch (task.status) {
           case 'PENDING':
@@ -318,6 +367,7 @@ App.WizardStep9Controller = Em.Controller.extend({
           case 'FAILED':
             return Em.I18n.t('installer.step9.serviceStatus.uninstall.failed') + role;
         }
+        break;
       case 'START' :
         switch (task.status) {
           case 'PENDING':
@@ -331,6 +381,7 @@ App.WizardStep9Controller = Em.Controller.extend({
           case 'FAILED':
             return role + Em.I18n.t('installer.step9.serviceStatus.start.failed');
         }
+        break;
       case 'STOP' :
         switch (task.status) {
           case 'PENDING':
@@ -344,6 +395,7 @@ App.WizardStep9Controller = Em.Controller.extend({
           case 'FAILED':
             return role + Em.I18n.t('installer.step9.serviceStatus.stop.failed');
         }
+        break;
       case 'EXECUTE' :
       case 'SERVICE_CHECK' :
         switch (task.status) {
@@ -358,6 +410,7 @@ App.WizardStep9Controller = Em.Controller.extend({
           case 'FAILED':
             return role + Em.I18n.t('installer.step9.serviceStatus.execute.failed');
         }
+        break;
       case 'ABORT' :
         switch (task.status) {
           case 'PENDING':
@@ -378,6 +431,7 @@ App.WizardStep9Controller = Em.Controller.extend({
   /**
    * Run start/check services after installation phase.
    * Does Ajax call to start all services
+   * @method launchStartServices
    */
   launchStartServices: function () {
     var data = {
@@ -425,7 +479,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Success callback function for start services task.
-   * @param jsonData: {json object} Contains Request id to poll.
+   * @param {object} jsonData Contains Request id to poll.
+   * @method launchStartServicesSuccessCallback
    */
   launchStartServicesSuccessCallback: function (jsonData) {
     var clusterStatus = {};
@@ -468,7 +523,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * This function will be called for Add host wizard only.
-   * @param jsonError: {boolean} Boolean is true when API to start services returns 200 ok and no json data
+   * @param {bool} jsonError Boolean is true when API to start services returns 200 ok and no json data
+   * @method hostHasClientsOnly
    */
   hostHasClientsOnly: function (jsonError) {
     this.get('hosts').forEach(function (host) {
@@ -489,10 +545,15 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Error callback function for start services task.
+   * @param {object} jqXHR
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @param {object} opt
+   * @method launchStartServicesErrorCallback
    */
-  launchStartServicesErrorCallback: function (jqXHR) {
+  launchStartServicesErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
     console.log("ERROR");
-    this.set('startCallFailed',true);
+    this.set('startCallFailed', true);
     var clusterStatus = {
       status: 'INSTALL FAILED',
       isStartError: false,
@@ -502,24 +563,14 @@ App.WizardStep9Controller = Em.Controller.extend({
     this.get('hosts').forEach(function (host) {
       host.set('progress', '100');
     });
-    this.set('progress','100');
-
-    var params = {
-      cluster: this.get('content.cluster.name')
-    };
-
-    if (this.get('content.controllerName') === 'addHostController') {
-      params.name = 'wizard.step9.add_host.launch_start_services';
-    } else {
-      params.name = 'wizard.step9.installer.launch_start_services';
-    }
+    this.set('progress', '100');
 
-    var opt = App.formatRequest.call(App.urls[params.name], params);
-    App.ajax.defaultErrorHandler(jqXHR,opt.url,opt.type);
+    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
   },
 
   /**
-   * marks a host's status as "success" if all tasks are in COMPLETED state
+   * Marks a host's status as "success" if all tasks are in COMPLETED state
+   * @method onSuccessPerHost
    */
   onSuccessPerHost: function (actions, contentHost) {
     if (actions.everyProperty('Tasks.status', 'COMPLETED') && this.get('content.cluster.status') === 'INSTALLED') {
@@ -533,8 +584,8 @@ App.WizardStep9Controller = Em.Controller.extend({
    * to find which host FAILED occurred on, if any
    * @param actions {Array} of tasks retrieved from polled data
    * @param contentHost {Object} A host object
+   * @method onErrorPerHost
    */
-
   onErrorPerHost: function (actions, contentHost) {
     if (!actions) return;
     if (actions.someProperty('Tasks.status', 'FAILED') || actions.someProperty('Tasks.status', 'ABORTED') || actions.someProperty('Tasks.status', 'TIMEDOUT')) {
@@ -546,9 +597,10 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
-   *
-   * @param polledData : Json data polled from API.
-   * @returns {boolean}  true if there is at least one FAILED task of master component install
+   * Check if some master component is failed
+   * @param {object} polledData data polled from API.
+   * @returns {bool}  true if there is at least one FAILED task of master component install
+   * @method isMasterFailed
    */
   isMasterFailed: function (polledData) {
     var result = false;
@@ -566,6 +618,7 @@ App.WizardStep9Controller = Em.Controller.extend({
    * Mark a host status as in_progress if the any task on the host if either in IN_PROGRESS, QUEUED or PENDONG state.
    * @param actions {Array} of tasks retrieved from polled data
    * @param contentHost {Object} A host object
+   * @method onInProgressPerHost
    */
   onInProgressPerHost: function (actions, contentHost) {
     var runningAction = actions.findProperty('Tasks.status', 'IN_PROGRESS');
@@ -582,10 +635,11 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
-   * calculate progress of tasks per host
-   * @param actions
-   * @param contentHost
+   * Calculate progress of tasks per host
+   * @param {object} actions
+   * @param {object} contentHost
    * @return {Number}
+   * @method progressPerHost
    */
   progressPerHost: function (actions, contentHost) {
     var progress = 0;
@@ -620,19 +674,22 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
-   *
-   * @param polledData : Josn data retrieved from API
-   * @returns {Boolean} : Has step completed successfully
+   * Check if step completed successfully
+   * @param {object} polledData data retrieved from API
+   * @returns {bool}
+   * @method isSuccess
    */
   isSuccess: function (polledData) {
     return polledData.everyProperty('Tasks.status', 'COMPLETED');
   },
 
   /**
-   * return true if:
+   * Check if step is failed
+   * Return true if:
    *  1. any of the master/client components failed to install
    *  OR
    *  2. at least 50% of the slave host components for the particular service component fails to install
+   *  @method isStepFailed
    */
   isStepFailed: function () {
     var failed = false;
@@ -668,7 +725,8 @@ App.WizardStep9Controller = Em.Controller.extend({
    * INSTALLED -> STARTED
    * INSTALLED -> START_FAILED
    * @param polledData json data retrieved from API
-   * @returns {Boolean} true if polling should stop; false otherwise
+   * @returns {bool} true if polling should stop; false otherwise
+   * @method finishState
    */
   finishState: function (polledData) {
     if (this.get('content.cluster.status') === 'INSTALLED') {
@@ -685,7 +743,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * @param polledData Josn data retrieved from API
-   * @returns {boolean} Has "Start All Services" request completed successfully
+   * @returns {bool} Has "Start All Services" request completed successfully
+   * @method isServicesStarted
    */
   isServicesStarted: function (polledData) {
     var clusterStatus = {};
@@ -712,7 +771,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * @param polledData Josn data retrieved from API
-   * @returns {boolean} Has "Install All Services" request completed successfully
+   * @returns {bool} Has "Install All Services" request completed successfully
+   * @method isServicesInstalled
    */
   isServicesInstalled: function (polledData) {
     var clusterStatus = {};
@@ -746,8 +806,9 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * This is done at HostRole level.
-   * @param tasksPerHost {Array}
-   * @param host {Object}
+   * @param {Ember.Enumerable} tasksPerHost
+   * @param {Object} host
+   * @method setLogTasksStatePerHost
    */
   setLogTasksStatePerHost: function (tasksPerHost, host) {
     tasksPerHost.forEach(function (_task) {
@@ -764,8 +825,9 @@ App.WizardStep9Controller = Em.Controller.extend({
   /**
    * Parses the Json data retrieved from API and sets the task on the host of {hosts} array binded to template
    * @param polledData Json data retrieved from API
-   * @returns {Boolean} True if stage transition is completed.
+   * @returns {bool} True if stage transition is completed.
    * On true, polling will be stopped.
+   * @method parseHostInfo
    */
   parseHostInfo: function (polledData) {
     console.log('TRACE: Entering host info function');
@@ -829,60 +891,31 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
-   * starts polling to the API.
+   * Starts polling to the API
+   * @method startPolling
    */
   startPolling: function () {
     this.set('isSubmitDisabled', true);
     this.doPolling();
   },
 
-  /**
-   *
-   * @param requestId {Int} Request Id received on triggering install/start command successfully
-   * @returns {string} URL to poll to track the result of the triggered command
-   */
-  getUrl: function (requestId) {
-    var clusterName = this.get('content.cluster.name');
-    var requestId = requestId || this.get('content.cluster.requestId');
-    var url = App.apiPrefix + '/clusters/' + clusterName + '/requests/' + requestId + '?fields=tasks/Tasks/command,tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true';
-    console.log("URL for step9 is: " + url);
-    return url;
-  },
-
   /**
    * This function calls API just once to fetch log data of all tasks.
-   * @param requestId {Int} Request Id received on triggering install/start command successfully
+   * @method loadLogData
    */
-  loadLogData: function (requestId) {
-    var url = this.getUrl(requestId);
+  loadLogData: function () {
     var requestsId = this.get('wizardController').getDBProperty('cluster').oldRequestsId;
-    if (App.testMode) {
-      this.POLL_INTERVAL = 1;
-    }
-
     requestsId.forEach(function (requestId) {
-      url = this.getUrl(requestId);
       if (App.testMode) {
-        this.POLL_INTERVAL = 1;
-        url = this.get('mockDataPrefix') + '/poll_' + this.numPolls + '.json';
+        this.set('POLL_INTERVAL', 1);
       }
-      this.getLogsByRequest(url, false);
+      this.getLogsByRequest(false, requestId);
     }, this);
   },
-  /**
-   * {Number}
-   * <code>taskId</code> of current open task
-   */
-  currentOpenTaskId: 0,
-
-  /**
-   * {Number}
-   * <code>requestId</code> of current open task
-   */
-  currentOpenTaskRequestId: 0,
 
   /**
    * Load form server <code>stderr, stdout</code> of current open task
+   * @method loadCurrentTaskLog
    */
   loadCurrentTaskLog: function () {
     var taskId = this.get('currentOpenTaskId');
@@ -908,7 +941,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * success callback function for getting log data of the opened task
-   * @param data json object
+   * @param {object} data
+   * @method loadCurrentTaskLogSuccessCallback
    */
   loadCurrentTaskLogSuccessCallback: function (data) {
     var taskId = this.get('currentOpenTaskId');
@@ -924,6 +958,7 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Error callback function for getting log data of the opened task
+   * @method loadCurrentTaskLogErrorCallback
    */
   loadCurrentTaskLogErrorCallback: function () {
     this.set('currentOpenTaskId', 0);
@@ -931,45 +966,22 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Function polls the API to retrieve data for the request.
-   * @param url {string} url to poll
-   * @param polling  {Boolean} whether to continue polling for status or not
+   * @param {bool} polling whether to continue polling for status or not
+   * @param {number} requestId
+   * @method getLogsByRequest
    */
-  getLogsByRequest: function (url, polling) {
-    var self = this;
-    $.ajax({
-      type: 'GET',
-      url: url,
-      async: true,
-      timeout: App.timeout,
-      dataType: 'text',
-      success: function (data) {
-        var parsedData = jQuery.parseJSON(data);
-        console.log("TRACE: In success function for the GET logs data");
-        console.log("TRACE: Step9 -> The value is: ", parsedData);
-        var result = self.parseHostInfo(parsedData);
-        if (!polling) {
-          if (self.get('content.cluster.status') === 'INSTALL FAILED') {
-            self.isAllComponentsInstalled();
-          }
-          return;
-        }
-        if (result !== true) {
-          window.setTimeout(function () {
-            if (self.get('currentOpenTaskId')) {
-              self.loadCurrentTaskLog();
-            }
-            self.doPolling();
-          }, self.POLL_INTERVAL);
-        }
-      },
-
-      error: function (request, ajaxOptions, error) {
-        console.log("TRACE: STep9 -> In error function for the GET logs data");
-        console.log("TRACE: STep9 -> value of the url is: " + url);
-        console.log("TRACE: STep9 -> error code status is: " + request.status);
+  getLogsByRequest: function (polling, requestId) {
+    App.ajax.send({
+      name: 'wizard.step9.load_log',
+      sender: this,
+      data: {
+        polling: polling,
+        cluster: this.get('content.cluster.name'),
+        requestId: requestId,
+        numPolls: this.get('numPolls')
       },
-
-      statusCode: require('data/statusCodes')
+      success: 'getLogsByRequestSuccessCallback',
+      error: 'getLogsByRequestErrorCallback'
     }).retry({times: App.maxRetries, timeout: App.timeout}).then(null,
       function () {
         App.showReloadPopup();
@@ -978,22 +990,63 @@ App.WizardStep9Controller = Em.Controller.extend({
     );
   },
 
+  /**
+   * Success callback for get log by request
+   * @param {object} data
+   * @param {object} opt
+   * @param {object} params
+   * @method getLogsByRequestSuccessCallback
+   */
+  getLogsByRequestSuccessCallback: function (data, opt, params) {
+    var self = this;
+    var parsedData = jQuery.parseJSON(data);
+    console.log("TRACE: In success function for the GET logs data");
+    console.log("TRACE: Step9 -> The value is: ", parsedData);
+    var result = this.parseHostInfo(parsedData);
+    if (!params.polling) {
+      if (this.get('content.cluster.status') === 'INSTALL FAILED') {
+        this.isAllComponentsInstalled();
+      }
+      return;
+    }
+    if (result !== true) {
+      window.setTimeout(function () {
+        if (self.get('currentOpenTaskId')) {
+          self.loadCurrentTaskLog();
+        }
+        self.doPolling();
+      }, this.POLL_INTERVAL);
+    }
+  },
+
+  /**
+   * Error-callback for get log by request
+   * @param {object} request
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @method getLogsByRequestErrorCallback
+   */
+  getLogsByRequestErrorCallback: function (request, ajaxOptions, error) {
+    console.log("TRACE: STep9 -> In error function for the GET logs data");
+    console.log("TRACE: STep9 -> value of the url is: " + url);
+    console.log("TRACE: STep9 -> error code status is: " + request.status);
+  },
+
   /**
    * Delegates the function call to {getLogsByRequest} with appropriate params
+   * @method doPolling
    */
   doPolling: function () {
-    var url = this.getUrl();
-
+    var requestId = this.get('content.cluster.requestId');
     if (App.testMode) {
-      this.numPolls++;
-      url = this.get('mockDataPrefix') + '/poll_' + this.get('numPolls') + '.json';
-
+      this.incrementProperty('numPolls');
     }
-    this.getLogsByRequest(url, true);
+    this.getLogsByRequest(true, requestId);
   },
 
   /**
    * Check that all components are in INSTALLED state before issuing start command
+   * @method isAllComponentsInstalled
    */
   isAllComponentsInstalled: function () {
     if (this.get('content.controllerName') !== 'installerController') {
@@ -1013,7 +1066,8 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Success callback function for API checking host state and host_components state.
-   * @param jsonData {Object}
+   * @param {Object} jsonData
+   * @method isAllComponentsInstalledSuccessCallback
    */
   isAllComponentsInstalledSuccessCallback: function (jsonData) {
     var clusterStatus = {
@@ -1052,6 +1106,7 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * Error callback function for API checking host state and host_components state
+   * @method isAllComponentsInstalledErrorCallback
    */
   isAllComponentsInstalledErrorCallback: function () {
     console.log("ERROR");
@@ -1071,11 +1126,13 @@ App.WizardStep9Controller = Em.Controller.extend({
   },
 
   /**
+   * Get formatted string of components to display on the UI
    * @param componentArr {Array}  Array of components
-   * @returns {String} Formatted string of components to display on the UI.
+   * @returns {String}
+   * @method getComponentMessage
    */
   getComponentMessage: function (componentArr) {
-    var label;
+    var label = '';
     componentArr.forEach(function (_component) {
       if (_component === componentArr[0]) {
         label = _component;
@@ -1093,23 +1150,26 @@ App.WizardStep9Controller = Em.Controller.extend({
 
   /**
    * save cluster status in the parentController and localdb
-   * @param clusterStatus {Object}
+   * @param {object} clusterStatus
+   * @method saveClusterStatus
    */
-  saveClusterStatus: function(clusterStatus) {
+  saveClusterStatus: function (clusterStatus) {
     if (!App.testMode) {
       App.router.get(this.get('content.controllerName')).saveClusterStatus(clusterStatus);
     } else {
-      this.set('content.cluster',clusterStatus);
+      this.set('content.cluster', clusterStatus);
     }
   },
 
   /**
    * save cluster status in the parentController and localdb
-   * @param context
+   * @param {object} context
+   * @method saveInstalledHosts
    */
-  saveInstalledHosts: function(context) {
+  saveInstalledHosts: function (context) {
     if (!App.testMode) {
       App.router.get(this.get('content.controllerName')).saveInstalledHosts(context)
     }
   }
+
 });

+ 2 - 1
ambari-web/app/initialize.js

@@ -33,7 +33,8 @@ require('templates');
 require('views');
 require('router');
 
-require('utils/ajax');
+require('utils/ajax/ajax');
+require('utils/ajax/ajax_queue');
 require('utils/updater');
 
 require('mappers');

+ 157 - 111
ambari-web/app/models/cluster_states.js

@@ -16,9 +16,20 @@
  * limitations under the License.
  */
 var App = require('app');
+require('mixins/common/userPref');
 
-App.clusterStatus = Ember.Object.create({
+App.clusterStatus = Em.Object.create(App.UserPref, {
+
+  /**
+   * Cluster name
+   * @type {string}
+   */
   clusterName: '',
+
+  /**
+   * List valid cluster states
+   * @type {string[]}
+   */
   validStates: [
     'DEFAULT',
     'CLUSTER_NOT_CREATED_1',
@@ -43,93 +54,139 @@ App.clusterStatus = Ember.Object.create({
     'DISABLE_SECURITY',
     'HIGH_AVAILABILITY_DEPLOY',
     'ROLLBACK_HIGH_AVAILABILITY'],
+
+  /**
+   * Default cluster state
+   * @type {string}
+   */
   clusterState: 'CLUSTER_NOT_CREATED_1',
+
+  /**
+   * Current used wizard <code>controller.name</code>
+   * @type {string|null}
+   */
   wizardControllerName: null,
+
+  /**
+   * Local DB
+   * @type {object|null}
+   */
   localdb: null,
+
+  /**
+   * Persist key
+   * @type {string}
+   */
   key: 'CLUSTER_CURRENT_STATUS',
-  isInstalled: function(){
+
+  /**
+   * Is cluster installed
+   * @type {bool}
+   */
+  isInstalled: function () {
     var notInstalledStates = ['CLUSTER_NOT_CREATED_1', 'CLUSTER_DEPLOY_PREP_2', 'CLUSTER_INSTALLING_3', 'SERVICE_STARTING_3'];
     return !notInstalledStates.contains(this.get('clusterState'));
   }.property('clusterState'),
+
+  /**
+   * General info about cluster
+   * @type {{clusterName: string, clusterState: string, wizardControllerName: string, localdb: object}}
+   */
+  value: function () {
+    return {
+      clusterName: this.get('clusterName'),
+      clusterState: this.get('clusterState'),
+      wizardControllerName: this.get('wizardControllerName'),
+      localdb: this.get('localdb')
+    };
+  }.property('clusterName', 'clusterState', 'localdb', 'wizardControllerName'),
+
   /**
    * get cluster data from server and update cluster status
-   * @param {Boolean} isAsync set this to true if the call is to be made asynchronously.  if unspecified, false is assumed
-   * @param {Boolean} overrideLocaldb
+   * @param {bool} isAsync set this to true if the call is to be made asynchronously.  if unspecified, false is assumed
+   * @param {bool} overrideLocaldb
    * @return promise object for the get call
+   * @method updateFromServer
    */
-  updateFromServer: function(isAsync, overrideLocaldb) {
+  updateFromServer: function (isAsync, overrideLocaldb) {
     // if isAsync is undefined, set it to false
-    isAsync = isAsync || false;
+    this.set('makeRequestAsync', isAsync || false);
     // if overrideLocaldb is undefined, set it to true
-    if(typeof overrideLocaldb == "undefined"){
-      overrideLocaldb =  true;
-    }
-    var user = App.db.getUser();
-    var login = App.db.getLoginName();
-    var url = App.apiPrefix + '/persist/' + this.get('key');
-    return jQuery.ajax(
-      {
-        url: url,
-        context: this,
-        async: isAsync,
-        success: function (response) {
-          if (response) {
-            var newValue = jQuery.parseJSON(response);
-            if (newValue.clusterState) {
-              this.set('clusterState', newValue.clusterState);
-            }
-            if (newValue.clusterName) {
-              this.set('clusterName', newValue.clusterName);
-            }
-            if (newValue.wizardControllerName) {
-              this.set('wizardControllerName', newValue.wizardControllerName);
-            }
-            if (newValue.localdb) {
-              this.set('localdb', newValue.localdb);
-              // restore HAWizard data if process was started
-              var isHAWizardStarted = App.get('isAdmin') && !App.isEmptyObject(newValue.localdb.HighAvailabilityWizard);
-              if (overrideLocaldb || isHAWizardStarted) {
-                App.db.data = newValue.localdb;
-                App.db.setLocalStorage();
-                App.db.setUser(user);
-                App.db.setLoginName(login);
-              }
-            }
-          } else {
-            // default status already set
-          }
-          // this is to ensure that the local storage namespaces are initialized with all expected namespaces.
-          // after upgrading ambari, loading local storage data from the "persist" data saved via an older version of
-          // Ambari can result in missing namespaces that are defined in the new version of Ambari.
-          App.db.mergeStorage();
-        },
-        error: function (xhr) {
-          if (xhr.status == 404) {
-            // default status already set
-            console.log('Persist API did NOT find the key CLUSTER_CURRENT_STATUS');
-            return;
-          }
-          App.ModalPopup.show({
-            header: Em.I18n.t('common.error'),
-            secondary: false,
-            bodyClass: Ember.View.extend({
-              template: Ember.Handlebars.compile('<p>{{t common.update.error}}</p>')
-            })
-          });
-        },
-        statusCode: require('data/statusCodes')
+    this.set('additionalData', {
+      user: App.db.getUser(),
+      login: App.db.getLoginName(),
+      overrideLocaldb: overrideLocaldb || true
+    });
+    return this.getUserPref(this.get('key'));
+  },
+
+  /**
+   * Success callback for get-persist request
+   * @param {object} response
+   * @param {object} opt
+   * @param {object} params
+   * @method getUserPrefSuccessCallback
+   */
+  getUserPrefSuccessCallback: function (response, opt, params) {
+    if (response) {
+      if (response.clusterState) {
+        this.set('clusterState', response.clusterState);
+      }
+      if (response.clusterName) {
+        this.set('clusterName', response.clusterName);
+      }
+      if (response.wizardControllerName) {
+        this.set('wizardControllerName', response.wizardControllerName);
       }
-    );
+      if (response.localdb) {
+        this.set('localdb', response.localdb);
+        // restore HAWizard data if process was started
+        var isHAWizardStarted = App.get('isAdmin') && !App.isEmptyObject(response.localdb.HighAvailabilityWizard);
+        if (params.overrideLocaldb || isHAWizardStarted) {
+          App.db.data = response.localdb;
+          App.db.setLocalStorage();
+          App.db.setUser(params.user);
+          App.db.setLoginName(params.login);
+        }
+      }
+    }
+    // this is to ensure that the local storage namespaces are initialized with all expected namespaces.
+    // after upgrading ambari, loading local storage data from the "persist" data saved via an older version of
+    // Ambari can result in missing namespaces that are defined in the new version of Ambari.
+    App.db.mergeStorage();
   },
+
+  /**
+   * Error callback for get-persist request
+   * @param {object} request
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @method getUserPrefErrorCallback
+   */
+  getUserPrefErrorCallback: function (request, ajaxOptions, error) {
+    if (request.status == 404) {
+      // default status already set
+      console.log('Persist API did NOT find the key CLUSTER_CURRENT_STATUS');
+      return;
+    }
+    App.ModalPopup.show({
+      header: Em.I18n.t('common.error'),
+      secondary: false,
+      bodyClass: Em.View.extend({
+        template: Em.Handlebars.compile('<p>{{t common.update.error}}</p>')
+      })
+    });
+  },
+
   /**
    * update cluster status and post it on server
-   * @param newValue
-   * @param opt - used for additional ajax request options, by default ajax used synchronous mode
-   *
+   * @param {object} newValue
+   * @param {object} opt - used for additional ajax request options, by default ajax used synchronous mode
+   * @method setClusterStatus
    * @return {*}
    */
-  setClusterStatus: function(newValue, opt){
-    if(App.testMode) return false;
+  setClusterStatus: function (newValue, opt) {
+    if (App.testMode) return false;
     var user = App.db.getUser();
     var login = App.db.getLoginName();
     var val = {clusterName: this.get('clusterName')};
@@ -137,7 +194,7 @@ App.clusterStatus = Ember.Object.create({
       //setter
       if (newValue.clusterName) {
         this.set('clusterName', newValue.clusterName);
-        val.clusterName =  newValue.clusterName;
+        val.clusterName = newValue.clusterName;
       }
 
       if (newValue.clusterState) {
@@ -164,38 +221,39 @@ App.clusterStatus = Ember.Object.create({
         App.db.setLoginName(login);
       }
 
-      var keyValuePair = {};
-
-      keyValuePair[this.get('key')] = JSON.stringify(val);
-
-      var ajaxOptions = {
-        name: 'cluster.state',
-        sender: this,
-        data: {
-          keyValuePair: keyValuePair
-        },
-        beforeSend: 'clusterStatusBeforeSend',
-        error: 'clusterStatusErrorCallBack'
-      };
-
       if (opt) {
-        ajaxOptions.async = !!opt.async;
-        ajaxOptions.sender = opt.sender || this;
-        ajaxOptions.success = opt.success;
-        ajaxOptions.beforeSend = opt.beforeSend;
-        ajaxOptions.error = opt.error;
+        var keyValuePair = {};
+        keyValuePair[this.get('key')] = JSON.stringify(val);
+        App.ajax.send({
+          name: 'settings.post.user_pref',
+          sender: opt.sender || this,
+          data: {
+            async: !!opt.async,
+            keyValuePair: keyValuePair
+          },
+          success: opt.success || Em.K,
+          beforeSend: opt.beforeSend || Em.K,
+          error: opt.error || Em.K
+        });
+      }
+      else {
+        this.set('makeRequestAsync', false);
+        this.postUserPref(this.get('key'), val);
       }
-
-      App.ajax.send(ajaxOptions);
       return newValue;
     }
   },
-  clusterStatusBeforeSend: function (keyValuePair) {
-    console.log('BeforeSend: persistKeyValues', keyValuePair);
-  },
-  clusterStatusErrorCallBack: function(request, ajaxOptions, error, opt) {
+
+  /**
+   * Error callback for post-persist request
+   * @param {object} request
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @method postUserPrefErrorCallback
+   */
+  postUserPrefErrorCallback: function (request, ajaxOptions, error) {
     console.log("ERROR");
-    var msg, doc;
+    var msg = '', doc;
     try {
       msg = 'Error ' + (request.status) + ' ';
       doc = $.parseXML(request.responseText);
@@ -208,22 +266,10 @@ App.clusterStatus = Ember.Object.create({
       header: Em.I18n.t('common.error'),
       secondary: false,
       response: msg,
-      bodyClass: Ember.View.extend({
-        template: Ember.Handlebars.compile('<p>{{t common.persist.error}} {{response}}</p>')
+      bodyClass: Em.View.extend({
+        template: Em.Handlebars.compile('<p>{{t common.persist.error}} {{response}}</p>')
       })
     });
-  },
-
-  /**
-   * general info about cluster
-   */
-  value: function () {
-      return {
-        clusterName: this.get('clusterName'),
-        clusterState: this.get('clusterState'),
-        wizardControllerName: this.get('wizardControllerName'),
-        localdb: this.get('localdb')
-      };
-  }.property('clusterName', 'clusterState', 'localdb', 'wizardControllerName')
+  }
 
 });

+ 267 - 16
ambari-web/app/utils/ajax.js → ambari-web/app/utils/ajax/ajax.js

@@ -397,6 +397,119 @@ var urls = {
       }
     }
   },
+
+  'host.host_component.delete': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}',
+    'mock': '',
+    'format': function() {
+      return {
+        type: 'DELETE',
+        async: false
+      }
+    }
+  },
+
+  'host.host_components.delete': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}',
+    'mock': '',
+    'format': function() {
+      return {
+        type: 'DELETE',
+        async: false
+      }
+    }
+  },
+
+  'host.host_component.stop': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_components.stop': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        async: false,
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_component.start': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_components.start': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        async: false,
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_component.install': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_component.update': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_component.add_new_component': {
+    'real': '/clusters/{clusterName}/hosts?Hosts/host_name={hostName}',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        data: data.data
+      }
+    }
+  },
+
+  'host.host_component.install_new_component': {
+    'real': '/clusters/{clusterName}/host_components?HostRoles/host_name={hostName}\&HostRoles/component_name={componentName}\&HostRoles/state=INIT',
+    'mock': '/data/wizard/deploy/poll_1.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
   'host.host_component.slave_desired_admin_state': {
     'real': '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}/?fields=HostRoles/desired_admin_state',
     'mock': ''
@@ -849,17 +962,6 @@ var urls = {
       };
     }
   },
-  'cluster.state': {
-    'type': 'POST',
-    'real': '/persist/',
-    'mock': '',
-    'format': function(data) {
-      return {
-        async: false,
-        data: JSON.stringify(data.keyValuePair)
-      };
-    }
-  },
   'cluster.update_upgrade_version': {
     'real': '/stacks2/HDP/versions?fields=stackServices/StackServices,Versions',
     'mock': '/data/wizard/stack/stacks.json',
@@ -1171,6 +1273,39 @@ var urls = {
       };
     }
   },
+
+  'admin.user.create': {
+    'real': '/users/{user}',
+    'mock': '/data/users/users.json',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        data: JSON.stringify(data.data)
+      }
+    }
+  },
+
+  'admin.user.delete': {
+    'real': '/users/{user}',
+    'mock': '/data/users/users.json',
+    'format': function() {
+      return {
+        type: 'DELETE'
+      }
+    }
+  },
+
+  'admin.user.edit': {
+    'real': '/users/{user}',
+    'mock':'/data/users/users.json',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        data: data.data
+      }
+    }
+  },
+
   'admin.stack_upgrade.do_poll': {
     'real': '/clusters/{cluster}/requests/{requestId}?fields=tasks/*',
     'mock': '/data/wizard/{mock}'
@@ -1284,6 +1419,19 @@ var urls = {
       };
     }
   },
+
+  'wizard.step9.load_log': {
+    'real': '/clusters/{cluster}/requests/{requestId}?fields=tasks/Tasks/command,tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true',
+    'mock': '/data/wizard/deploy/5_hosts/poll_{numPolls}.json',
+    'format': function () {
+      return {
+        type: 'GET',
+        async: true,
+        dataType: 'text'
+      };
+    }
+  },
+
   'wizard.step8.delete_cluster': {
     'real': '/clusters/{name}',
     'mock': '',
@@ -1303,6 +1451,111 @@ var urls = {
       };
     }
   },
+
+  'wizard.step8.create_cluster': {
+    'real':'/clusters/{cluster}',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.create_selected_services': {
+    'real':'/clusters/{cluster}/services',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.create_components': {
+    'real':'/clusters/{cluster}/services?ServiceInfo/service_name={serviceName}',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.register_host_to_cluster': {
+    'real':'/clusters/{cluster}/hosts',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.register_host_to_component': {
+    'real':'/clusters/{cluster}/hosts',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.apply_configuration_to_cluster': {
+    'real':'/clusters/{cluster}',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.apply_configuration_groups': {
+    'real':'/clusters/{cluster}/config_groups',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'POST',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
+  'wizard.step8.set_local_repos': {
+    'real':'{stack2VersionURL}/operatingSystems/{osType}/repositories/{name}',
+    'mock':'',
+    'format': function(data) {
+      return {
+        type: 'PUT',
+        async: true,
+        dataType: 'text',
+        data: data.data
+      }
+    }
+  },
+
   'wizard.step3.host_info': {
     'real': '/hosts?fields=Hosts/total_mem,Hosts/cpu_count,Hosts/disk_info,Hosts/last_agent_env,Hosts/host_name,Hosts/os_type,Hosts/os_arch,Hosts/ip',
     'mock': '/data/wizard/bootstrap/two_hosts_information.json',
@@ -1566,9 +1819,9 @@ var urls = {
         contentType: 'text/xml',
         dataType: 'xml',
         data: data.entity,
-          headers: {
-        'AmbariProxy-Content-Type': 'text/xml'
-      }
+        headers: {
+          'AmbariProxy-Content-Type': 'text/xml'
+        }
       }
     }
   },
@@ -1996,5 +2249,3 @@ if ($.mocho) {
 }
 
 App.ajax = ajax.create({});
-App.formatRequest = formatRequest;
-App.urls = urls;

+ 187 - 0
ambari-web/app/utils/ajax/ajax_queue.js

@@ -0,0 +1,187 @@
+/**
+ * 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');
+
+/**
+ * Simple util for executing ajax-requests queue
+ *
+ * @uses App.ajax
+ * @type {Em.Object}
+ *
+ * Don't add requests directly to <code>queue</code>!
+ * Bad example:
+ * <code>
+ *  var q = App.ajax.Queue.create();
+ *  q.get('queue').pushObject({...}); // Don't do this!!!!
+ * </code>
+ *
+ * Don't call <code>runNextRequest</code> directly!
+ * Bad example:
+ * <code>
+ *  var q = App.ajax.Queue.create();
+ *  q.runNextRequest(); // Don't do this!!!!
+ * </code>
+ *
+ * Usage example 1:
+ * <code>
+ *  var q = App.ajax.Queue.create();
+ *  q.addRequest({
+ *    name: 'some_request',
+ *    sender: this,
+ *    success: 'someFunc'
+ *  }).addRequest({
+ *    name: 'some_request2',
+ *    sender: this,
+ *    success: 'someFunc2'
+ *  }).start();
+ * </code>
+ *
+ * Usage example 2:
+ * <code>
+ *  var q = App.ajax.Queue.create();
+ *  q.addRequest({
+ *    name: 'some_request',
+ *    sender: this,
+ *    success: 'someFunc'
+ *  });
+ *  q.addRequest({
+ *    name: 'some_request2',
+ *    sender: this,
+ *    success: 'someFunc2'
+ *  });
+ *  q.start();
+ * </code>
+ *
+ * Usage example 3:
+ * <code>
+ *  var q = App.ajax.Queue.create();
+ *  q.addRequests(Em.A([{
+ *    name: 'some_request',
+ *    sender: this,
+ *    success: 'someFunc'
+ *  },
+ *  {
+ *    name: 'some_request2',
+ *    sender: this,
+ *    success: 'someFunc2'
+ *  }]));
+ *  q.start();
+ * </code>
+ */
+App.ajaxQueue = Em.Object.extend({
+
+  /**
+   * List of requests
+   * @type {Object[]}
+   */
+  queue: Em.A([]),
+
+  /**
+   * About query executing if some request failed
+   * @type {bool}
+   */
+  abortOnError: true,
+
+  /**
+   * Function called after queue is complete
+   * @type {callback}
+   */
+  finishedCallback: Em.K,
+
+  /**
+   * Add request to the <code>queue</code>
+   * @param {Object} request object that uses in <code>App.ajax.send</code>
+   * @return {App.ajaxQueue}
+   * @method addRequest
+   */
+  addRequest: function(request) {
+    Em.assert('Each ajax-request should has non-blank `name`', !Em.isBlank(Em.get(request, 'name')));
+    Em.assert('Each ajax-request should has object `sender`', Em.typeOf(Em.get(request, 'sender')) !== 'object');
+    this.get('queue').pushObject(request);
+    return this;
+  },
+
+  /**
+   * Add requests to the <code>queue</code>
+   * @param {Object[]} requests list of objects that uses in <code>App.ajax.send</code>
+   * @return {App.ajaxQueue}
+   * @method addRequests
+   */
+  addRequests: function(requests) {
+    requests.map(function(request) {
+      this.addRequest(request);
+    }, this);
+    return this;
+  },
+
+  /**
+   * Enter point to start requests executing
+   * @method start
+   */
+  start: function() {
+    this.runNextRequest();
+  },
+
+  /**
+   * Execute first request from the <code>queue</code>
+   * @method runNextRequest
+   */
+  runNextRequest: function() {
+    var self = this;
+    var queue = this.get('queue');
+    if (queue.length === 0) {
+      this.finishedCallback();
+      return;
+    }
+    var r = App.ajax.send(queue.shift());
+    this.propertyDidChange('queue');
+    if (r) {
+      r.complete(function(xhr) {
+        if(xhr.status>=200 && xhr.status <= 299) {
+          self.runNextRequest();
+        }
+        else {
+          if (self.get('abortOnError')) {
+            self.clear();
+          }
+          else {
+            self.runNextRequest();
+          }
+        }
+      });
+    }
+    else {
+      if (this.get('abortOnError')) {
+        this.clear();
+      }
+      else {
+        this.runNextRequest();
+      }
+    }
+  },
+
+  /**
+   * Remove all requests from <code>queue</code>
+   * @method clear
+   */
+  clear: function() {
+    this.get('queue').clear();
+  }
+
+});

+ 85 - 44
ambari-web/app/views/main/admin/user/create.js

@@ -19,69 +19,110 @@
 var App = require('app');
 
 App.MainAdminUserCreateView = Em.View.extend({
+
   templateName: require('templates/main/admin/user/create'),
+
+  /**
+   * Form for new user
+   * @type {App.CreateUserForm}
+   */
+  userForm: App.CreateUserForm.create({}),
+
+  /**
+   * @type {number|bool}
+   */
   userId: false,
+
+  /**
+   * @type {bool}
+   */
   isPasswordDirty: false,
 
-  create: function(event){
-    var parent_controller=this.get("controller").controllers.mainAdminUserController;
+  /**
+   * Create new user
+   * @method create
+   */
+  create: function () {
     var form = this.get("userForm");
-    if(form.isValid()) {
+    if (form.isValid()) {
       form.getField("userName").set('value', form.getField("userName").get('value').toLowerCase());
-      if(form.getField("admin").get('value') === "" || form.getField("admin").get('value') == true) {
-        form.getField("roles").set("value","admin,user");
-        form.getField("admin").set("value","true");
-      } else{
-        form.getField("roles").set("value","user");
+      if (form.getField("admin").get('value') === "" || form.getField("admin").get('value') == true) {
+        form.getField("roles").set("value", "admin,user");
+        form.getField("admin").set("value", "true");
+      }
+      else {
+        form.getField("roles").set("value", "user");
       }
-      parent_controller.sendCommandToServer('/users/' + form.getField("userName").get('value'), "POST" , {
-        Users: {
-          password: form.getField("password").get('value'),
-          roles: form.getField("roles").get('value')
-        }
-      }, function (success) {
 
-        if (!success) {
-          App.ModalPopup.show({
-            header: Em.I18n.t('admin.users.addButton'),
-            body: Em.I18n.t('admin.users.createError'),
-            primary: Em.I18n.t('ok'),
-            secondary: null,
-            onPrimary: function() {
-              this.hide();
+      App.ajax.send({
+        name: 'admin.user.create',
+        sender: this,
+        data: {
+          user: form.getField("userName").get('value'),
+          form: form,
+          data: {
+            Users: {
+              password: form.getField("password").get('value'),
+              roles: form.getField("roles").get('value')
             }
-          });
-          return;
-        }
-        App.ModalPopup.show({
-          header: Em.I18n.t('admin.users.addButton'),
-          body: Em.I18n.t('admin.users.createSuccess'),
-          primary: Em.I18n.t('ok'),
-          secondary: null,
-          onPrimary: function() {
-            this.hide();
           }
-        });
-        var persists = App.router.get('applicationController').persistKey(form.getField("userName").get('value'));
-        App.router.get('applicationController').postUserPref(persists,true);
-
-        form.save();
-
-        App.router.transitionTo("allUsers");
-      })
+        },
+        success: 'createUserSuccessCallback',
+        error: 'createUserErrorCallback'
+      });
     }
   },
 
-  userForm: App.CreateUserForm.create({}),
+  /**
+   * Success-callback for create user request
+   * @param {object} data
+   * @param {object} opts
+   * @param {object} params
+   * @method createUserSuccessCallback
+   */
+  createUserSuccessCallback: function (data, opts, params) {
+    App.ModalPopup.show({
+      header: Em.I18n.t('admin.users.addButton'),
+      body: Em.I18n.t('admin.users.createSuccess'),
+      secondary: null
+    });
+    var persists = App.router.get('applicationController').persistKey(params.form.getField("userName").get('value'));
+    App.router.get('applicationController').postUserPref(persists, true);
+    params.form.save();
+    App.router.transitionTo("allUsers");
+  },
+
+  /**
+   * Error callback for create used request
+   * @method createUserErrorCallback
+   */
+  createUserErrorCallback: function () {
+    App.ModalPopup.show({
+      header: Em.I18n.t('admin.users.addButton'),
+      body: Em.I18n.t('admin.users.createError'),
+      secondary: null
+    });
+  },
 
-  keyPress: function(event) {
+  /**
+   * Submit form by Enter-click
+   * @param {object} event
+   * @returns {bool}
+   * @method keyPress
+   */
+  keyPress: function (event) {
     if (event.keyCode === 13) {
       this.create();
       return false;
     }
+    return true;
   },
 
-  passwordValidation: function() {
+  /**
+   * Validate password value
+   * @method passwordValidation
+   */
+  passwordValidation: function () {
     var passwordValue = this.get('userForm').getField('password').get('value');
     if (passwordValue && !this.get('isPasswordDirty')) {
       this.set('isPasswordDirty', true);
@@ -92,7 +133,7 @@ App.MainAdminUserCreateView = Em.View.extend({
     }
   }.observes('userForm.fields.@each.value'),
 
-  didInsertElement: function(){
+  didInsertElement: function () {
     this.get('userForm').propertyDidChange('object');
   }
 });

+ 81 - 38
ambari-web/app/views/main/admin/user/edit.js

@@ -19,59 +19,102 @@
 var App = require('app');
 
 App.MainAdminUserEditView = Em.View.extend({
+
   templateName: require('templates/main/admin/user/edit'),
+
+  /**
+   * @type {bool}
+   */
   userId: false,
-  edit: function(event){
-    var form = this.get("userForm");
-    if(form.isValid()) {
-      var Users={};
-      if(form.getField("admin").get('value') === "" || form.getField("admin").get('value') == true) {
-        form.getField("roles").set("value","admin,user");
-        form.getField("admin").set("value", true);
-      } else{
-        form.getField("roles").set("value","user");
-      }
 
-      Users.roles = form.getField("roles").get('value');
+  /**
+   * Form to edit existing user
+   * @type {App.EditUserForm}
+   */
+  userForm: App.EditUserForm.create({}),
 
-      if(form.getField("new_password").get('value') != "" && form.getField("old_password").get('value') != "") {
-        Users.password = form.getField("new_password").get('value');
-        Users.old_password = form.getField("old_password").get('value');
-      }
+  /**
+   * Edit existing user
+   * @method edit
+   */
+  edit: function () {
+    var form = this.get("userForm");
+    if (!form.isValid()) return;
 
-      this.get("controller").sendCommandToServer('/users/' + form.getField("userName").get('value'), "PUT" , {
-       Users:Users
-      }, function (success, message) {
-        if (!success) {
-          App.ModalPopup.show({
-            header: Em.I18n.t('admin.users.editButton'),
-            body: message,
-            primary: Em.I18n.t('ok'),
-            secondary: null,
-            onPrimary: function() {
-              this.hide();
-            }
-          });
-          return;
-        }
+    var Users = {};
+    if (form.getField("admin").get('value') === "" || form.getField("admin").get('value') == true) {
+      form.getField("roles").set("value", "admin,user");
+      form.getField("admin").set("value", true);
+    }
+    else {
+      form.getField("roles").set("value", "user");
+    }
 
-        form.save();
+    Users.roles = form.getField("roles").get('value');
 
-        App.router.transitionTo("allUsers");
-      })
+    if (form.getField("new_password").get('value') != "" && form.getField("old_password").get('value') != "") {
+      Users.password = form.getField("new_password").get('value');
+      Users.old_password = form.getField("old_password").get('value');
     }
+
+    App.ajax.send({
+      name: 'admin.user.edit',
+      sender: this,
+      data: {
+        form: form,
+        user: form.getField("userName").get('value'),
+        data: JSON.stringify({
+          Users: Users
+        })
+      },
+      success: 'editUserSuccessCallback',
+      error: 'editUserErrorCallback'
+    });
+  },
+
+  /**
+   * Success callback for edit user request
+   * @param {object} data
+   * @param {object} opt
+   * @param {object} params
+   * @method editUserSuccessCallback
+   */
+  editUserSuccessCallback: function (data, opt, params) {
+    params.form.save();
+    App.router.transitionTo("allUsers");
   },
 
-  keyPress: function(event) {
+  /**
+   * Error callback for edit user request
+   * @param {object} request
+   * @method editUserErrorCallback
+   */
+  editUserErrorCallback: function (request) {
+    var message = $.parseJSON(request.responseText).message;
+    message = message.substr(message.lastIndexOf(':') + 1);
+    App.ModalPopup.show({
+      header: Em.I18n.t('admin.users.editButton'),
+      body: message,
+      secondary: null
+    });
+    console.log(message);
+  },
+
+  /**
+   * Submit form by Enter-click
+   * @param {object} event
+   * @returns {bool}
+   * @method keyPress
+   */
+  keyPress: function (event) {
     if (event.keyCode === 13) {
       this.edit();
       return false;
     }
+    return true;
   },
 
-  userForm: App.EditUserForm.create({}),
-
-  didInsertElement: function() {
+  didInsertElement: function () {
     var form = this.get('userForm');
     if (form.getField("isLdap").get("value")) {
       form.getField("old_password").set("disabled", true);
@@ -84,4 +127,4 @@ App.MainAdminUserEditView = Em.View.extend({
     }
     form.propertyDidChange('object');
   }
-});
+});

+ 11 - 25
ambari-web/app/views/main/host/details/host_component_view.js

@@ -84,33 +84,22 @@ App.HostComponentView = Em.View.extend({
     return 'health-status-' + App.HostComponentStatus.getKeyName(this.get('workStatus'));
 
   }.property('workStatus'),
+
   /**
    * CSS-icon-class for host component status
    * @type {String}
    */
   statusIconClass: function () {
-    switch (this.get('statusClass')) {
-      case 'health-status-started':
-      case 'health-status-starting':
-        return App.healthIconClassGreen;
-        break;
-      case 'health-status-installed':
-      case 'health-status-stopping':
-        return App.healthIconClassRed;
-        break;
-      case 'health-status-unknown':
-        return App.healthIconClassYellow;
-        break;
-      case 'health-status-DEAD-ORANGE':
-        return App.healthIconClassOrange;
-        break;
-      default:
-        return "";
-        break;
-    }
+    return Em.getWithDefault({
+      'health-status-started': App.healthIconClassGreen,
+      'health-status-starting': App.healthIconClassGreen,
+      'health-status-installed': App.healthIconClassRed,
+      'health-status-stopping': App.healthIconClassRed,
+      'health-status-unknown': App.healthIconClassYellow,
+      'health-status-DEAD-ORANGE': App.healthIconClassOrange
+    }, this.get('statusClass'), '');
   }.property('statusClass'),
 
-
   /**
    * CSS-class for disabling drop-down menu with list of host component actions
    * Disabled if host's <code>healthClass</code> is health-status-DEAD-YELLOW (lost heartbeat)
@@ -174,11 +163,8 @@ App.HostComponentView = Em.View.extend({
    */
   noActionAvailable: function () {
     var workStatus = this.get('workStatus');
-    if ([App.HostComponentStatus.starting, App.HostComponentStatus.stopping, App.HostComponentStatus.unknown, App.HostComponentStatus.disabled].contains(workStatus)) {
-      return "hidden";
-    }else{
-      return "";
-    }
+    return [App.HostComponentStatus.starting, App.HostComponentStatus.stopping,
+      App.HostComponentStatus.unknown, App.HostComponentStatus.disabled].contains(workStatus) ? "hidden" : '';
   }.property('workStatus'),
 
   /**

+ 1 - 1
ambari-web/app/views/main/host/summary.js

@@ -270,7 +270,7 @@ App.MainHostSummaryView = Em.View.extend({
     }
 
     return components;
-  }.property('content.hostComponents.length', 'installableClientComponents'),
+  }.property('content.hostComponents.length', 'installableClientComponents', 'App.components.addableToHost.@each'),
 
   /**
    * Formatted with <code>$.timeago</code> value of host's last heartbeat

+ 44 - 30
ambari-web/app/views/wizard/step8_view.js

@@ -24,28 +24,28 @@ App.WizardStep8View = Em.View.extend({
   templateName: require('templates/wizard/step8'),
 
   didInsertElement: function () {
-    var controller = this.get('controller');
-    controller.loadStep();
+    this.get('controller').loadStep();
   },
 
-  spinner : null,
-
+  /**
+   * Print review-report
+   * @method printReview
+   */
   printReview: function() {
     var o = $("#step8-info");
     o.jqprint();
   },
 
-  ajaxQueueLength: function() {
-    return this.get('controller.ajaxQueueLength');
-  }.property('controller.ajaxQueueLength'),
-
-  ajaxQueueLeft: function() {
-    return this.get('controller.ajaxQueueLeft');
-  }.property('controller.ajaxQueueLeft'),
-
-  // reference to modalPopup to make sure only one instance is created
+  /**
+   * Reference to modalPopup to make sure only one instance is created
+   * @type {App.ModalPopup|null}
+   */
   modalPopup: null,
 
+  /**
+   * Should ajax-queue progress bar be displayed
+   * @method showLoadingIndicator
+   */
   showLoadingIndicator: function() {
     if (!this.get('controller.isSubmitDisabled') || App.testMode) {
       if (this.get('modalPopup')) {
@@ -59,39 +59,53 @@ App.WizardStep8View = Em.View.extend({
       return;
     }
     this.set('modalPopup', App.ModalPopup.show({
+
       header: '',
 
       showFooter: false,
 
       showCloseButton: false,
 
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/wizard/step8_log_popup'),
+      bodyClass: Em.View.extend({
 
-        message: function() {
-          return Em.I18n.t('installer.step8.deployPopup.message').format(this.get('ajaxQueueComplete'), this.get('ajaxQueueLength'));
-        }.property('ajaxQueueComplete', 'ajaxQueueLength'),
+        templateName: require('templates/wizard/step8_log_popup'),
 
         controllerBinding: 'App.router.wizardStep8Controller',
 
-        ajaxQueueLength: function() {
-          return this.get('controller.ajaxQueueLength');
-        }.property(),
-
-        ajaxQueueComplete: function() {
-          return this.get('ajaxQueueLength') - this.get('controller.ajaxQueueLeft');
-        }.property('controller.ajaxQueueLeft', 'ajaxQueueLength'),
-
-        barWidth: function () {
-          return 'width: ' + (this.get('ajaxQueueComplete') / this.get('ajaxQueueLength') * 100) + '%;';
-        }.property('ajaxQueueComplete', 'ajaxQueueLength'),
-
+        /**
+         * Css-property for progress-bar
+         * @type {string}
+         */
+        barWidth: '',
+
+        /**
+         * Popup-message
+         * @type {string}
+         */
+        message: '',
+
+        /**
+         * Set progress bar width and popup message when ajax-queue requests are proccessed
+         * @method ajaxQueueChangeObs
+         */
+        ajaxQueueChangeObs: function() {
+          var length = this.get('controller.ajaxQueueLength');
+          var left = this.get('controller.ajaxRequestsQueue.queue.length');
+          this.set('barWidth', 'width: ' + ((length - left) / length * 100) + '%;');
+          this.set('message', Em.I18n.t('installer.step8.deployPopup.message').format((length - left), length));
+        }.observes('controller.ajaxQueueLength', 'controller.ajaxRequestsQueue.queue.length'),
+
+        /**
+         * Hide popup when ajax-queue is finished
+         * @method autoHide
+         */
         autoHide: function() {
           if (this.get('controller.servicesInstalled')) {
             this.get('parentView').hide();
           }
         }.observes('controller.servicesInstalled')
       })
+
     }));
   }.observes('controller.isSubmitDisabled')
 });

+ 2 - 2
ambari-web/test/controllers/global/background_operations_test.js

@@ -21,7 +21,7 @@ var App = require('app');
 
 require('config');
 require('utils/updater');
-require('utils/ajax');
+require('utils/ajax/ajax');
 
 require('models/host_component');
 
@@ -162,7 +162,7 @@ describe('App.BackgroundOperationsController', function () {
       };
       controller.callBackForMostRecent(data);
       expect(controller.get("allOperationsCount")).to.equal(0);
-      expect(controller.get("services")).to.be.empty;
+      expect(controller.get("services.length")).to.equal(0);
     });
     it('One non-running request', function () {
       var data = {

+ 2 - 2
ambari-web/test/controllers/global/cluster_controller_test.js

@@ -23,7 +23,7 @@ require('models/host_component');
 require('utils/http_client');
 require('models/service');
 require('models/host');
-require('utils/ajax');
+require('utils/ajax/ajax');
 
 describe('App.clusterController', function () {
   var controller = App.ClusterController.create();
@@ -449,4 +449,4 @@ describe('App.clusterController', function () {
     });
   });
 
-});
+});

+ 1 - 0
ambari-web/test/installer/step8_test.js

@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+require('utils/ajax/ajax_queue');
 require('controllers/wizard/step8_controller');
 
 var installerStep8Controller;

+ 1 - 13
ambari-web/test/installer/step9_test.js

@@ -724,18 +724,6 @@ describe('App.InstallerStep9Controller', function () {
     });
   });
 
-  describe('#getUrl', function () {
-    var clusterName = 'tdk';
-    var cluster = App.WizardStep9Controller.create({content: {cluster: {name: clusterName, requestId: null}}});
-    it('check requestId priority', function () {
-      cluster.set('content.cluster.requestId', 123);
-      var url = cluster.getUrl(321);
-      expect(url).to.equal(App.apiPrefix + '/clusters/' + clusterName + '/requests/' + '321' + '?fields=tasks/Tasks/command,tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true');
-      url = cluster.getUrl();
-      expect(url).to.equal(App.apiPrefix + '/clusters/' + clusterName + '/requests/' + '123' + '?fields=tasks/Tasks/command,tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true');
-    });
-  });
-
   describe('#finishState', function () {
     var statuses = Em.A(['INSTALL FAILED', 'START FAILED', 'STARTED']);
     it('Installer is finished', function () {
@@ -1180,7 +1168,7 @@ describe('App.InstallerStep9Controller', function () {
     var controller = App.WizardStep9Controller.create({hosts: hosts, content: {controllerName: 'installerController', cluster: {status: 'PENDING',name: 'c1'}},togglePreviousSteps: function(){}});
 
     //Action
-    controller.launchStartServicesErrorCallback({status:500, statusTesxt: 'Server Error'});
+    controller.launchStartServicesErrorCallback({status:500, statusTesxt: 'Server Error'}, {}, '', {});
     it('Cluster Status should be INSTALL FAILED', function () {
       expect(controller.get('content.cluster.status')).to.equal('INSTALL FAILED');
     });

+ 111 - 0
ambari-web/test/utils/ajax/ajax_queue_test.js

@@ -0,0 +1,111 @@
+/**
+ * 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 ajaxQueue;
+
+describe('App.ajaxQueue', function () {
+
+  beforeEach(function() {
+    ajaxQueue = App.ajaxQueue.create();
+    sinon.spy(ajaxQueue, 'runNextRequest');
+    sinon.spy(ajaxQueue, 'finishedCallback');
+    sinon.spy(App.ajax, 'send');
+  });
+
+  afterEach(function() {
+    ajaxQueue.clear();
+    ajaxQueue.runNextRequest.restore();
+    ajaxQueue.finishedCallback.restore();
+    App.ajax.send.restore();
+  });
+
+  describe('#clear', function() {
+    it('should clear queue', function() {
+      ajaxQueue.addRequest({name:'some', sender: Em.Object.create()});
+      ajaxQueue.clear();
+      expect(ajaxQueue.get('queue.length')).to.equal(0);
+    });
+  });
+
+  describe('#addRequest', function() {
+    it('should add request', function() {
+      ajaxQueue.addRequest({name:'some', sender: Em.Object.create()});
+      expect(ajaxQueue.get('queue.length')).to.equal(1);
+    });
+    it('should throw `name` error', function() {
+      expect(function() {ajaxQueue.addRequest({name:'', sender: Em.Object.create()})}).to.throw(Error);
+    });
+    it('should throw `sender` error', function() {
+      expect(function() {ajaxQueue.addRequest({name:'some', sender: {}})}).to.throw(Error);
+    });
+  });
+
+  describe('#addRequests', function() {
+    it('should add requests', function() {
+      ajaxQueue.addRequests(Em.A([
+        {name:'some', sender: Em.Object.create()},
+        {name:'some2', sender: Em.Object.create()}
+      ]));
+      expect(ajaxQueue.get('queue.length')).to.equal(2);
+    });
+
+    it('should throw `name` error', function() {
+      expect(function() {ajaxQueue.addRequests(Em.A([
+        {name:'some', sender: Em.Object.create()},
+        {name:'', sender: Em.Object.create()}
+      ]));}).to.throw(Error);
+    });
+
+    it('should throw `sender` error', function() {
+      expect(function() {ajaxQueue.addRequests(Em.A([
+        {name:'some', sender: Em.Object.create()},
+        {name:'some2', sender: {}}
+      ]));}).to.throw(Error);
+    });
+
+  });
+
+  describe('#start', function() {
+    it('should call runNextRequest', function() {
+      ajaxQueue.start();
+      expect(ajaxQueue.runNextRequest.called).to.equal(true);
+    });
+  });
+
+  describe('#runNextRequest', function() {
+    it('for empty queue App.ajax.send shouldn\'t be called', function() {
+      ajaxQueue.clear();
+      ajaxQueue.runNextRequest();
+      expect(App.ajax.send.called).to.equal(false);
+    });
+    it('when queue is empty finishedCallback should be called', function() {
+      ajaxQueue.clear();
+      ajaxQueue.runNextRequest();
+      expect(ajaxQueue.finishedCallback.called).to.equal(true);
+    });
+    it('if abortOnError is false queue shouldn\'t be interrupted', function() {
+      ajaxQueue.clear();
+      ajaxQueue.set('abortOnError', false);
+      ajaxQueue.addRequest({name:'some_fake', sender: Em.Object.create()}).addRequest({name: 'some_fake2', sender: Em.Object.create()}).start();
+      expect(ajaxQueue.runNextRequest.callCount).to.equal(3); // One for empty-queue
+    });
+  });
+
+});

+ 4 - 4
ambari-web/test/utils/ajax_test.js → ambari-web/test/utils/ajax/ajax_test.js

@@ -17,7 +17,7 @@
  */
 
 var App = require('app');
-require('utils/ajax');
+require('utils/ajax/ajax');
 
 describe('App.ajax', function() {
 
@@ -35,17 +35,17 @@ describe('App.ajax', function() {
 
     it('Without sender', function() {
       expect(App.ajax.send({})).to.equal(null);
-      expect($.ajax.called).to.be.false;
+      expect($.ajax.called).to.equal(false);
     });
 
     it('Invalid config.name', function() {
       expect(App.ajax.send({name:'fake_name', sender: this})).to.equal(null);
-      expect($.ajax.called).to.be.false;
+      expect($.ajax.called).to.equal(false);
     });
 
     it('With proper data', function() {
       App.ajax.send({name: 'router.logoff', sender: this});
-      expect($.ajax.calledOnce).to.be.true;
+      expect($.ajax.calledOnce).to.equal(true);
     });
 
   });

+ 1 - 1
ambari-web/test/utils/date_test.js

@@ -67,7 +67,7 @@ describe('date', function () {
     });
     it('Today timestamp', function() {
       var now = new Date();
-      var then = new Date(now.getFullYear(),now.getMonth(),now.getDate(),0,0,0);
+      var then = new Date(now.getFullYear(),now.getUTCMonth(),now.getUTCDate(),0,0,0);
       expect(date.dateFormatShort(then.getTime() + 10*3600*1000)).to.equal('Today 10:00:00');
     });
     describe('Incorrect timestamps', function() {

+ 19 - 0
ambari-web/test/views/main/host/details/host_component_view_test.js

@@ -381,4 +381,23 @@ describe('App.HostComponentView', function() {
 
   });
 
+  describe('#statusIconClass', function() {
+    var tests = Em.A([
+      {s: 'health-status-started', e: App.healthIconClassGreen},
+      {s: 'health-status-starting', e: App.healthIconClassGreen},
+      {s: 'health-status-installed', e: App.healthIconClassRed},
+      {s: 'health-status-stopping', e: App.healthIconClassRed},
+      {s: 'health-status-unknown', e: App.healthIconClassYellow},
+      {s: 'health-status-DEAD-ORANGE', e: App.healthIconClassOrange},
+      {s: 'other', e: ''}
+    ]);
+
+    tests.forEach(function(test) {
+      it(test.s, function() {
+        hostComponentView.reopen({statusClass: test.s});
+        expect(hostComponentView.get('statusIconClass')).to.equal(test.e);
+      })
+    });
+  });
+
 });

Some files were not shown because too many files changed in this diff