Updated Branches:
  refs/heads/trunk 59b24e512 -> aa8de8964

AMBARI-3253. Provide UI to delete host from Ambari. (srimanth)


Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/aa8de896
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/aa8de896
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/aa8de896

Branch: refs/heads/trunk
Commit: aa8de8964814515780671f4966ee4855c56ef1e8
Parents: 59b24e5
Author: Srimanth Gunturi <[email protected]>
Authored: Mon Sep 16 16:10:56 2013 -0700
Committer: Srimanth Gunturi <[email protected]>
Committed: Mon Sep 16 16:13:30 2013 -0700

----------------------------------------------------------------------
 ambari-web/app/config.js                        |   3 +-
 ambari-web/app/controllers/main/host/details.js | 457 +++++++++++++++----
 ambari-web/app/mappers/hosts_mapper.js          |  18 +
 ambari-web/app/messages.js                      |  23 +-
 ambari-web/app/models/host_component.js         |  24 +
 ambari-web/app/templates/main/host/details.hbs  |  38 +-
 ambari-web/app/utils/ajax.js                    |  14 +-
 ambari-web/app/views/main/host/details.js       |   5 +-
 ambari-web/app/views/main/host/summary.js       |  65 ++-
 9 files changed, 525 insertions(+), 122 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/config.js b/ambari-web/app/config.js
index f8f1464..96a4cdd 100644
--- a/ambari-web/app/config.js
+++ b/ambari-web/app/config.js
@@ -58,7 +58,8 @@ App.supports = {
   hue: false,
   ldapGroupMapping: false,
   localRepositories: false,
-  highAvailability: true
+  highAvailability: true,
+  deleteHost: false
 };
 
 if (App.enableExperimental) {

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/controllers/main/host/details.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/host/details.js 
b/ambari-web/app/controllers/main/host/details.js
index 15e8c54..9cc852c 100644
--- a/ambari-web/app/controllers/main/host/details.js
+++ b/ambari-web/app/controllers/main/host/details.js
@@ -78,8 +78,8 @@ App.MainHostDetailsController = Em.Controller.extend({
 
       error: function (request, ajaxOptions, error) {
         //do something
-        callback(null);
-        console.log('error on change component host status')
+        console.log('error on change component host status');
+        App.ajax.defaultErrorHandler(request, url, method);
       },
 
       statusCode: require('data/statusCodes')
@@ -94,38 +94,130 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     App.showConfirmationPopup(function() {
       var component = event.context;
-
-      self.sendCommandToServer('/hosts/' + self.get('content.hostName') + 
'/host_components/' + component.get('componentName').toUpperCase(),{
-        RequestInfo : {
-          "context" : Em.I18n.t('requestInfo.startHostComponent') + " " + 
component.get('displayName')
-        },
-        Body:{
-          HostRoles:{
-            state: 'STARTED'
-          }
+      var context = Em.I18n.t('requestInfo.startHostComponent') + " " + 
component.get('displayName');
+      self.sendStartComponentCommand(component, context);
+    });
+  },
+  
+  /**
+   * PUTs a command to server to start a component. If no 
+   * specific component is provided, all components are started.
+   * @param component  When <code>null</code> all startable components are 
started. 
+   * @param context  Context under which this command is beign sent. 
+   */
+  sendStartComponentCommand: function(component, context) {
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + 
component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    var dataToSend = {
+      RequestInfo : {
+        "context" : context
+      },
+      Body:{
+        HostRoles:{
+          state: 'STARTED'
         }
-      }, 'PUT',
-        function(requestId){
-
-        if(!requestId){
-          return;
+      }
+    };
+    if (component === null) {
+      var allComponents = this.get('content.hostComponents');
+      var startable = [];
+      allComponents.forEach(function (c) {
+        if (c.get('isMaster') || c.get('isSlave')) {
+          startable.push(c.get('componentName'));
         }
+      });
+      dataToSend.RequestInfo.query = "HostRoles/component_name.in(" + 
startable.join(',') + ")";
+    }
+    this.sendCommandToServer(url, dataToSend, 'PUT',
+      function(requestId){
+
+      if(!requestId){
+        return;
+      }
 
-        console.log('Send request for STARTING successfully');
+      console.log('Send request for STARTING successfully');
 
-        if (App.testMode) {
+      if (App.testMode) {
+        if(component === null){
+          var allComponents = this.get('content.hostComponents');
+          allComponents.forEach(function(component){
+            component.set('workStatus', App.HostComponentStatus.stopping);
+            setTimeout(function(){
+              component.set('workStatus', App.HostComponentStatus.stopped);
+            },App.testModeDelayForActions);
+          });
+        } else {
           component.set('workStatus', App.HostComponentStatus.starting);
           setTimeout(function(){
             component.set('workStatus', App.HostComponentStatus.started);
           },App.testModeDelayForActions);
-        } else {
-          App.router.get('clusterController').loadUpdatedStatusDelayed(500);
         }
+      } else {
+        App.router.get('clusterController').loadUpdatedStatusDelayed(500);
+      }
+      App.router.get('backgroundOperationsController').showPopup();
+    });
+  },
 
-        App.router.get('backgroundOperationsController').showPopup();
-
-      });
+  /**
+   * Deletes the given host component, or all host components.
+   * 
+   * @param component  When <code>null</code> all host components are deleted.
+   * @return  <code>null</code> when components get deleted.
+   *          <code>{xhr: XhrObj, url: "http://";, method: "DELETE"}</code> 
+   *          when components failed to get deleted. 
+   */
+  _doDeleteHostComponent: function(component) {
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + 
component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    url = App.apiPrefix + '/clusters/' + App.router.getClusterName() + url;
+    var deleted = null;
+    $.ajax({
+      type: 'DELETE',
+      url: url,
+      timeout: App.timeout,
+      async: false,
+      success: function (data) {
+        deleted = null;
+        // If ZooKeeper Server component was removed, 
+        // restart ZooKeeper service.
+        /*
+         * Commenting it out as user can restart service
+         * whenever they want. We mention in message.
+        if (component.get('componentName') === 'ZOOKEEPER_SERVER') {
+          App.ajax.send({
+            'name': 'service.item.start_stop',
+            'sender': this,
+            'data': {
+              'requestInfo': 'Stop ZooKeeper',
+              'serviceName': 'ZOOKEEPER',
+              'state': 'INSTALLED'
+            },
+            'callback': function() {
+              App.ajax.send({
+                'name': 'service.item.start_stop',
+                'sender': this,
+                'data': {
+                  'requestInfo': 'Start ZooKeeper',
+                  'serviceName': 'ZOOKEEPER',
+                  'state': 'STARTED'
+                }
+              });
+            }
+          });
+        }*/
+      },
+      error: function (xhr, textStatus, errorThrown) {
+        console.log('Error deleting host component');
+        console.log(textStatus);
+        console.log(errorThrown);
+        deleted = {xhr: xhr, url: url, method: 'DELETE'};
+      },
+      statusCode: require('data/statusCodes')
     });
+    return deleted;
   },
 
   /**
@@ -176,36 +268,68 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     App.showConfirmationPopup(function() {
       var component = event.context;
-      self.sendCommandToServer('/hosts/' + self.get('content.hostName') + 
'/host_components/' + component.get('componentName').toUpperCase(),{
-        RequestInfo : {
-          "context" : Em.I18n.t('requestInfo.stopHostComponent')+ " " + 
component.get('displayName')
-        },
-        Body:{
-          HostRoles:{
-            state: 'INSTALLED'
-          }
+      var context = Em.I18n.t('requestInfo.stopHostComponent')+ " " + 
component.get('displayName');
+      self.sendStopComponentCommand(component, context);
+    });
+  },
+  
+  /**
+   * PUTs a command to server to stop a component. If no 
+   * specific component is provided, all components are stopped.
+   * @param component  When <code>null</code> all components are stopped. 
+   * @param context  Context under which this command is beign sent. 
+   */
+  sendStopComponentCommand: function(component, context){
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + 
component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    var dataToSend = {
+      RequestInfo : {
+        "context" : context
+      },
+      Body:{
+        HostRoles:{
+          state: 'INSTALLED'
         }
-      }, 'PUT',
-        function(requestId){
-        if(!requestId){
-          return;
+      }
+    };
+    if (component === null) {
+      var allComponents = this.get('content.hostComponents');
+      var startable = [];
+      allComponents.forEach(function (c) {
+        if (c.get('isMaster') || c.get('isSlave')) {
+          startable.push(c.get('componentName'));
         }
+      });
+      dataToSend.RequestInfo.query = "HostRoles/component_name.in(" + 
startable.join(',') + ")";
+    }
+    this.sendCommandToServer( url, dataToSend, 'PUT',
+      function(requestId){
+      if(!requestId){
+        return;
+      }
 
-        console.log('Send request for STOPPING successfully');
+      console.log('Send request for STOPPING successfully');
 
-        if (App.testMode) {
+      if (App.testMode) {
+        if(component === null){
+          var allComponents = this.get('content.hostComponents');
+          allComponents.forEach(function(component){
+            component.set('workStatus', App.HostComponentStatus.stopping);
+            setTimeout(function(){
+              component.set('workStatus', App.HostComponentStatus.stopped);
+            },App.testModeDelayForActions);
+          });
+        } else {
           component.set('workStatus', App.HostComponentStatus.stopping);
           setTimeout(function(){
             component.set('workStatus', App.HostComponentStatus.stopped);
           },App.testModeDelayForActions);
-        } else {
-          App.router.get('clusterController').loadUpdatedStatusDelayed(500);
         }
-
-        App.router.get('backgroundOperationsController').showPopup();
-
-      });
-
+      } else {
+        App.router.get('clusterController').loadUpdatedStatusDelayed(500);
+      }
+      App.router.get('backgroundOperationsController').showPopup();
     });
   },
 
@@ -217,6 +341,7 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     var component = event.context;
     var componentName = 
component.get('componentName').toUpperCase().toString();
+    var subComponentNames = component.get('subComponentNames');
     var displayName = component.get('displayName');
 
     var securityEnabled = 
App.router.get('mainAdminSecurityController').getUpdatedSecurityStatus();
@@ -227,19 +352,40 @@ App.MainHostDetailsController = Em.Controller.extend({
       }, 
Em.I18n.t('hosts.host.addComponent.securityNote').format(componentName,self.get('content.hostName')));
     }
     else {
+      var dn = displayName;
+      if (subComponentNames !== null && subComponentNames.length > 0) {
+        var dns = [];
+        subComponentNames.forEach(function(scn){
+          dns.push(App.format.role(scn));
+        });
+        dn += " ("+dns.join(", ")+")";
+      }
+      var dialogContent = 
+        [Em.I18n.t('hosts.host.addComponent.msg').format(dn) + "<br><br>",
+        '{{t hosts.host.addComponent.note}}'];
       App.ModalPopup.show({
         primary: Em.I18n.t('yes'),
         secondary: Em.I18n.t('no'),
         header: Em.I18n.t('popup.confirmation.commonHeader'),
         bodyClass: Ember.View.extend({
-          template: Ember.Handlebars.compile([
-            '{{t hosts.delete.popup.body}}<br><br>',
-            '{{t hosts.host.addComponent.note}}'
-          ].join(''))
+          template: Ember.Handlebars.compile(dialogContent.join(''))
         }),
         onPrimary: function () {
           this.hide();
-          self.primary(component);
+          if (component.get('componentName') === 'CLIENTS') {
+            // Clients component has many sub-components which 
+            // need to be installed.
+            var scs = component.get('subComponentNames');
+            scs.forEach(function (sc) {
+              var c = Em.Object.create({
+                displayName: App.format.role(sc),
+                componentName: sc
+              });
+              self.primary(c);
+            });
+          } else {
+            self.primary(component);
+          }
         }
       });
     }
@@ -483,43 +629,116 @@ App.MainHostDetailsController = Em.Controller.extend({
       App.router.get('backgroundOperationsController').showPopup();
     });
   },
+  
+  doAction: function(option) {
+    switch (option.context.action) {
+      case "deleteHost":
+        this.validateAndDeleteHost();
+        break;
+      case "startAllComponents":
+        this.doStartAllComponents();
+        break;
+      case "stopAllComponents":
+        this.doStopAllComponents();
+        break;
+      default:
+        break;
+    }
+  },
+  
+  doStartAllComponents: function() {
+    var self = this;
+    var components = this.get('content.hostComponents');
+    var componentsLength = components == null ? 0 : components.get('length');
+    if (componentsLength > 0) {
+      App.showConfirmationPopup(function() {
+        self.sendStartComponentCommand(null, 
+            Em.I18n.t('hosts.host.maintainance.startAllComponents.context'));
+      });
+    }
+  },
+  
+  doStopAllComponents: function() {
+    var self = this;
+    var components = this.get('content.hostComponents');
+    var componentsLength = components == null ? 0 : components.get('length');
+    if (componentsLength > 0) {
+      App.showConfirmationPopup(function() {
+        self.sendStopComponentCommand(null, 
+            Em.I18n.t('hosts.host.maintainance.stopAllComponents.context'));
+      });
+    }
+  },
 
   /**
    * Deletion of hosts not supported for this version
-   *
-   * validateDeletion: function () { var slaveComponents = [ 'DataNode',
-   * 'TaskTracker', 'RegionServer' ]; var masterComponents = []; var
-   * workingComponents = [];
-   *
-   * var components = this.get('content.components');
-   * components.forEach(function (cInstance) { var cName =
-   * cInstance.get('componentName'); if (slaveComponents.contains(cName)) { if
-   * (cInstance.get('workStatus') === App.HostComponentStatus.stopped &&
-   * !cInstance.get('decommissioned')) { workingComponents.push(cName); } } 
else {
-   * masterComponents.push(cName); } }); // debugger; if
-   * (workingComponents.length || masterComponents.length) {
-   * this.raiseWarning(workingComponents, masterComponents); } else {
-   * this.deleteButtonPopup(); } },
    */
-
-  raiseWarning: function (workingComponents, masterComponents) {
+   validateAndDeleteHost: function () {
+     if (!App.supports.deleteHost) {
+       return;
+     }
+     var stoppedStates = [App.HostComponentStatus.stopped, 
+                          App.HostComponentStatus.install_failed, 
+                          App.HostComponentStatus.upgrade_failed];
+     var masterComponents = [];
+     var runningComponents = [];
+     var unknownComponents = [];
+     var nonDeletableComponents = [];
+     var components = this.get('content.hostComponents');
+     if (components!=null && components.get('length')>0){
+       components.forEach(function (cInstance) { 
+         var workStatus = cInstance.get('workStatus');
+         if (cInstance.get('isMaster') && !cInstance.get('isDeletable')) {
+           masterComponents.push(cInstance.get('displayName'));
+         }
+         if (stoppedStates.indexOf(workStatus) < 0) {
+           runningComponents.push(cInstance.get('displayName'));
+         }
+         if (!cInstance.get('isDeletable')) {
+           nonDeletableComponents.push(cInstance.get('displayName'));
+         }
+         if (workStatus === App.HostComponentStatus.unknown) {
+           unknownComponents.push(cInstance.get('displayName'));
+         }
+       });
+     }
+     if (masterComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += 
Em.I18n.t('hosts.cant.do.popup.masterList.body').format(masterComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += masterComponents.join(", ");
+       bodyHtml += "</i>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     } else if (nonDeletableComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += 
Em.I18n.t('hosts.cant.do.popup.nonDeletableList.body').format(nonDeletableComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += nonDeletableComponents.join(", ");
+       bodyHtml += "</i>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     } else if(runningComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += 
Em.I18n.t('hosts.cant.do.popup.runningList.body').format(runningComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += runningComponents.join(", ");
+       bodyHtml += "</i><br><br><p>";
+       bodyHtml += Em.I18n.t('hosts.cant.do.popup.runningList.body.end');
+       bodyHtml += "</p>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     }
+     this._doDeleteHost(unknownComponents);
+  },
+  
+  raiseDeleteComponentsError: function (bodyHtml) {
     var self = this;
-    var masterString = '';
-    var workingString = '';
-    if(masterComponents && masterComponents.length) {
-      var masterList = masterComponents.join(', ');
-      var ml_text = Em.I18n.t('hosts.cant.do.popup.masterList.body');
-      masterString = ml_text.format(masterList);
-    }
-    if(workingComponents && workingComponents.length) {
-      var workingList = workingComponents.join(', ');
-      var wl_text = Em.I18n.t('hosts.cant.do.popup.workingList.body');
-      workingString = wl_text.format(workingList);
-    }
     App.ModalPopup.show({
-      header: Em.I18n.t('hosts.cant.do.popup.header'),
+      header: Em.I18n.t('hosts.cant.do.popup.title'),
       html: true,
-      body: masterString + workingString,
+      encodeBody: false,
+      body: bodyHtml,
       primary: Em.I18n.t('ok'),
       secondary: null,
       onPrimary: function() {
@@ -531,19 +750,73 @@ App.MainHostDetailsController = Em.Controller.extend({
   /**
    * show confirmation popup to delete host
    */
-  deleteButtonPopup: function() {
+  _doDeleteHost: function(unknownComponents) {
     var self = this;
-    App.showConfirmationPopup(function(){
-      self.removeHost();
-    });
-  },
-
-  /**
-   * remove host and open hosts page
-   */
-  removeHost: function () {
-    App.router.get('mainHostController').checkRemoved(this.get('content.id'));
-    App.router.transitionTo('hosts');
+    var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+    bodyHtml += 
Em.I18n.t('hosts.delete.popup.body').format("<i>"+this.get('content.publicHostName')+"</i>");
+    bodyHtml += "</p>";
+    if (unknownComponents!=null && unknownComponents.length > 0) {
+      bodyHtml += "<div class=\"alert\">";
+      bodyHtml += Em.I18n.t('hosts.delete.popup.unknownComponents') + "<br>";
+      bodyHtml += "<i>"
+      bodyHtml += unknownComponents.join(", ");
+      bodyHtml += "</i></div>";
+    }
+    bodyHtml += "<p>";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg1');
+    bodyHtml += "</p><p>";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg2');
+    bodyHtml += "</p><p>";
+    bodyHtml += "<span class=\"label 
label-important\">"+Em.I18n.t('common.important')+"</span>  ";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg3');
+    bodyHtml += "</p>";
+    App.ModalPopup.show({
+      header: Em.I18n.t('hosts.delete.popup.title'),
+      html: true,
+      encodeBody: false,
+      body: bodyHtml,
+      primary: Em.I18n.t('ok'),
+      secondary: Em.I18n.t('common.cancel'),
+      onPrimary: function() {
+        var dialogSelf = this;
+        var allComponents = self.get('content.hostComponents');
+        var deleteError = null;
+        allComponents.forEach(function(component){
+          if (!deleteError) {
+            deleteError = self._doDeleteHostComponent(component);
+          }
+        });
+        if (!deleteError) {
+          var url = App.apiPrefix + '/clusters/' + App.router.getClusterName() 
+ '/hosts/' + self.get('content.hostName');
+          $.ajax({
+            type: 'DELETE',
+            url: url,
+            timeout: App.timeout,
+            async: false,
+            success: function (data) {
+              dialogSelf.hide();
+              App.router.get('updateController').updateAll();
+              App.router.transitionTo('hosts.index');
+            },
+            error: function (xhr, textStatus, errorThrown) {
+              console.log('Error deleting host component');
+              console.log(textStatus);
+              console.log(errorThrown);
+              dialogSelf.hide();
+              xhr.responseText = "{\"message\": \"" + xhr.statusText + "\"}";
+              App.ajax.defaultErrorHandler(xhr, url, 'DELETE', xhr.status);
+            },
+            statusCode: require('data/statusCodes')
+          });
+        } else {
+          dialogSelf.hide();
+          deleteError.xhr.responseText = "{\"message\": \"" + 
deleteError.xhr.statusText + "\"}";
+          App.ajax.defaultErrorHandler(deleteError.xhr, deleteError.url, 
deleteError.method, deleteError.xhr.status);
+        }
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    })
   }
-
 })
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/mappers/hosts_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/hosts_mapper.js 
b/ambari-web/app/mappers/hosts_mapper.js
index bf1ed48..706281f 100644
--- a/ambari-web/app/mappers/hosts_mapper.js
+++ b/ambari-web/app/mappers/hosts_mapper.js
@@ -51,6 +51,24 @@ App.hostsMapper = App.QuickDataMapper.create({
   map: function (json) {
     if (json.items) {
       var result = this.parse(json.items);
+      var clientHosts = App.Host.find();
+      if (clientHosts != null && clientHosts.get('length') !== result.length) {
+        var serverHostIds = {};
+        result.forEach(function (host) {
+          serverHostIds[host.id] = host.id;
+        });
+        var hostsToDelete = [];
+        clientHosts.forEach(function (host) {
+          if (host !== null && !serverHostIds[host.get('hostName')]) {
+            // Delete old ones as new ones will be
+            // loaded by loadMany().
+            hostsToDelete.push(host);
+          }
+        });
+        hostsToDelete.forEach(function (host) {
+          host.deleteRecord();
+        });
+      }
       App.store.loadMany(this.get('model'), result);
     }
   },

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 44da0bf..600e053 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -147,6 +147,7 @@ Em.I18n.translations = {
   'common.persist.error' : 'Error in persisting web client state at ambari 
server:',
   'common.update.error' : 'Error in retrieving web client state from ambari 
server',
   'common.tags': 'Tags',
+  'common.important': 'Important',
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
   'requestInfo.startServices':'Start Services',
@@ -515,7 +516,7 @@ Em.I18n.translations = {
 
   'installer.step10.header':'Summary',
   'installer.step10.body':'Here is the summary of the install process.',
-  'installer.step10.nagiosRestartRequired':'<b>Important!</b> Restarting 
Nagios service is required for the alerts and notifications to work properly.  
After clicking on the Complete button to dismiss this wizard, go to Services -> 
Nagios to restart the Nagios service.',
+  'installer.step10.nagiosRestartRequired':'<b>Important!</b> Restarting 
Nagios service is required for alerts and notifications to work properly.  
After clicking on the Complete button to dismiss this wizard, go to Services -> 
Nagios to restart the Nagios service.',
   'installer.step10.hostsSummary':'The cluster consists of {0} hosts',
   'installer.step10.servicesSummary':'Installed and started services 
successfully on {0} new ',
   'installer.step10.warnings':' warnings',
@@ -1172,10 +1173,13 @@ Em.I18n.translations = {
   'hosts.host.summary.hostMetrics':'Host Metrics',
 
   'hosts.host.details.deleteHost':'Delete Host',
+  'hosts.host.details.startAllComponents':'Start All Components',
+  'hosts.host.details.stopAllComponents':'Stop All Components',
 
   'host.host.componentFilter.master':'Master Components',
   'host.host.componentFilter.slave':'Slave Components',
   'host.host.componentFilter.client':'Client Components',
+  'hosts.host.addComponent.msg':'Are you sure you want to add {0}?',
   'hosts.host.addComponent.note':'Note: After this component is installed, go 
to Services -> Nagios to restart the Nagios service.  This is required for the 
alerts and notifications to work properly.',
   'hosts.host.addComponent.securityNote':'You are running your cluster in 
secure mode. You must set up the keytab for {0} on {1} before you proceed. 
Otherwise, the component will not be able to start properly.',
   'hosts.host.datanode.decommission':'Decommission DataNode',
@@ -1193,14 +1197,23 @@ Em.I18n.translations = {
   'hosts.host.healthStatusCategory.orange': "Slave Down",
   'hosts.host.healthStatusCategory.yellow': "No Heartbeat",
   'hosts.host.alerts.label': 'Alerts',
+  'hosts.host.maintainance.stopAllComponents.context': 'Stop All Host 
Components',
+  'hosts.host.maintainance.startAllComponents.context': 'Start All Host 
Components',
   'hosts.host.alerts.st':'&nbsp;!&nbsp;',
   'hosts.decommission.popup.body':'Are you sure?',
   'hosts.decommission.popup.header':'Confirmation',
-  'hosts.delete.popup.body':'Are you sure?',
+  'hosts.delete.popup.body':'Are you sure you want to delete host {0}?',
+  'hosts.delete.popup.body.msg1':'This will remove the host from Ambari\'s 
management. Ambari will ignore any communications from this host.',
+  'hosts.delete.popup.body.msg2':'Installed bits of service components will 
not be removed from the system. Individual service components should not be 
restarted later to join the cluster. This will introduce inconsistencies in 
monitoring data.',
+  'hosts.delete.popup.body.msg3':'Nagios service should be restarted for 
alerts and notifications to work properly. ZooKeeper service should be 
restarted if any ZooKeeper components are removed. Go to the <i>Services</i> 
page to restart services.',
   'hosts.delete.popup.header':'Confirmation',
-  'hosts.cant.do.popup.header':'Operation not allowed',
-  'hosts.cant.do.popup.masterList.body':'You cannot delete this host because 
it is hosting following master services: {0}.',
-  'hosts.cant.do.popup.workingList.body':'You cannot delete this host because 
following slave services are not fully stopped or decommissioned: {0}.',
+  'hosts.delete.popup.title':'Delete Host',
+  'hosts.delete.popup.unknownComponents':'Components with unknown status:',
+  'hosts.cant.do.popup.title':'Delete Host Error',
+  'hosts.cant.do.popup.masterList.body':'Host with {0} master components 
cannot be deleted',
+  'hosts.cant.do.popup.nonDeletableList.body':'Deletion of the following {0} 
components is not supported. ',
+  'hosts.cant.do.popup.runningList.body':'Host cannot be deleted with the 
following {0} components running. ',
+  'hosts.cant.do.popup.runningList.body.end':'Stop the components before 
reattempting to delete host. Some components might need special actions 
performed before deletion from cluster. For example, DataNode has to be 
decommissioned before being deleted.',
   'hosts.add.header':'Add Host Wizard',
   'hosts.assignRack':'Assign Rack',
 

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/models/host_component.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/host_component.js 
b/ambari-web/app/models/host_component.js
index ea33df9..7c99a3d 100644
--- a/ambari-web/app/models/host_component.js
+++ b/ambari-web/app/models/host_component.js
@@ -77,6 +77,30 @@ App.HostComponent = DS.Model.extend({
     }
   }.property('componentName'),
   /**
+   * Only certain components can be deleted.
+   * They include some from master components, 
+   * some from slave components, and rest from 
+   * client components.
+   */
+  isDeletable: function() {
+    var canDelete = false;
+    switch (this.get('componentName')) {
+      case 'DATANODE':
+      case 'TASKTRACKER':
+      case 'ZOOKEEPER_SERVER':
+      case 'HBASE_REGIONSERVER':
+      case 'GANGLIA_MONITOR':
+      case 'NODEMANAGER':
+        canDelete = true;
+        break;
+      default:
+    }
+    if (!canDelete) {
+      canDelete = this.get('isClient');
+    }
+    return canDelete;
+  }.property('componentName', 'isClient'),
+  /**
    * A host-component is decommissioning when it is in HDFS service's list of
    * decomNodes.
    */

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/templates/main/host/details.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/details.hbs 
b/ambari-web/app/templates/main/host/details.hbs
index b594486..51e1200 100644
--- a/ambari-web/app/templates/main/host/details.hbs
+++ b/ambari-web/app/templates/main/host/details.hbs
@@ -24,26 +24,28 @@
     <span class="label label-success alerts-count" {{action "showAlertsPopup" 
content target="App.router.mainHostController"}}>{{t 
hosts.host.alert.noAlerts}}</span>
   {{/if}}
   <div><a href="javascript:void(null)" data-toggle="modal" {{action back}}><i 
class="icon-arrow-left"></i>&nbsp;{{t common.back}}</a></div>
-<!--   {{#if App.isAdmin}} -->
-<!--   <div class="host-maintenance"> -->
-<!--     <div class="host-maintenance-btn btn-group display-inline-block"> -->
-<!--       <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> -->
-<!--         {{t services.service.actions.maintenance}} -->
-<!--         <span class="caret"></span> -->
-<!--       </a> -->
-<!--       <ul class="dropdown-menu"> -->
-<!--       dropdown menu links -->
-<!--         {{#each option in view.maintenance}} -->
-<!--         <li> -->
-<!--         <a {{action validateDeletion target="controller"}} 
href="#">{{option.label}}</a> -->
-<!--         </li> -->
-<!--         {{/each}} -->
-<!--       </ul> -->
-<!--     </div> -->
-<!--   </div> -->
-<!--   {{/if}} -->
   <div class="content">
     {{view App.MainHostMenuView}}
+    {{#if App.isAdmin}}
+      {{#if App.supports.deleteHost}}
+                               <div class="service-button">
+                                   <div class="btn-group display-inline-block">
+                                     <a class="btn dropdown-toggle" 
data-toggle="dropdown" href="#">
+                                       {{t 
services.service.actions.maintenance}}
+                                       <span class="caret"></span>
+                                     </a>
+                                     <ul class="dropdown-menu">
+                                       <!-- dropdown menu links -->
+                                       {{#each option in view.maintenance}}
+                                       <li {{bindAttr 
class="controller.isStopDisabled:disabled"}}>
+                                         <a {{action "doAction" option 
target="controller" href=true}}>{{option.label}}</a>
+                                       </li>
+                                       {{/each}}
+                                     </ul>
+                                   </div>
+                               </div>
+                       {{/if}}
+         {{/if}}
     {{outlet}}
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/utils/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js
index ad90016..19dd80a 100644
--- a/ambari-web/app/utils/ajax.js
+++ b/ambari-web/app/utils/ajax.js
@@ -812,6 +812,11 @@ var urls = {
       };
     }
   },
+  'admin.delete_host': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}',
+    'mock': '',
+    'type': 'DELETE'
+  },
   'admin.security.all_configurations': {
     'real': '/clusters/{clusterName}/configurations?{urlParams}',
     'mock':'',
@@ -1220,8 +1225,9 @@ App.ajax = {
    * @jqXHR {jqXHR Object}
    * @url {string}
    * @method {String} Http method
+   * @showStatus {number} HTTP response code which should be shown. Default is 
500.
    */
-  defaultErrorHandler: function(jqXHR,url,method) {
+  defaultErrorHandler: function(jqXHR,url,method,showStatus) {
     method = method || 'GET';
     var self = this;
     var api = " received on " + method + " method for API: " + url;
@@ -1231,12 +1237,14 @@ App.ajax = {
       var message = json.message;
     } catch (err) {
     }
-
+    if (showStatus === null) {
+      showStatus = 500;
+    }
     if (message === undefined) {
       showMessage = false;
     }
     var statusCode = jqXHR.status + " status code";
-    if (jqXHR.status === 500 && !this.modalPopup) {
+    if (jqXHR.status === showStatus && !this.modalPopup) {
       this.modalPopup = App.ModalPopup.show({
         header: jqXHR.statusText,
         secondary: false,

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/views/main/host/details.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/details.js 
b/ambari-web/app/views/main/host/details.js
index 677eab6..4738adc 100644
--- a/ambari-web/app/views/main/host/details.js
+++ b/ambari-web/app/views/main/host/details.js
@@ -27,7 +27,10 @@ App.MainHostDetailsView = Em.View.extend({
   }.property('App.router.mainHostDetailsController.content'),
 
   maintenance: function(){
-    var options = [{action: 'deleteHost', 'label': 
this.t('hosts.host.details.deleteHost')}];
+    var options = [
+         {action: 'startAllComponents', 'label': 
this.t('hosts.host.details.startAllComponents')},
+         {action: 'stopAllComponents', 'label': 
this.t('hosts.host.details.stopAllComponents')},
+         {action: 'deleteHost', 'label': 
this.t('hosts.host.details.deleteHost')}];
     return options;
   }.property('controller.content'),
   didInsertElement: function() {

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/views/main/host/summary.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/summary.js 
b/ambari-web/app/views/main/host/summary.js
index 8c53f6b..0260e7c 100644
--- a/ambari-web/app/views/main/host/summary.js
+++ b/ambari-web/app/views/main/host/summary.js
@@ -123,13 +123,69 @@ App.MainHostSummaryView = Em.View.extend({
 
   addableComponentObject: Em.Object.extend({
     componentName: '',
+    subComponentNames: null,
     displayName: function () {
+      if (this.get('componentName') === 'CLIENTS') {
+        return this.t('common.clients');
+      }
       return App.format.role(this.get('componentName'));
     }.property('componentName')
   }),
   isAddComponent: function () {
     return this.get('content.healthClass') !== 'health-status-DEAD-YELLOW';
   }.property('content.healthClass'),
+  
+  installableClientComponents: function() {
+    var installableClients = [];
+    if (!App.supports.deleteHost) {
+      return installableClients;
+    }
+    App.Service.find().forEach(function(svc){
+      switch(svc.get('serviceName')){
+        case 'PIG':
+          installableClients.push('PIG');
+          break;
+        case 'SQOOP':
+          installableClients.push('SQOOP');
+          break;
+        case 'HCAT':
+          installableClients.push('HCAT');
+          break;
+        case 'HDFS':
+          installableClients.push('HDFS_CLIENT');
+          break;
+        case 'OOZIE':
+          installableClients.push('OOZIE_CLIENT');
+          break;
+        case 'ZOOKEEPER':
+          installableClients.push('ZOOKEEPER_CLIENT');
+          break;
+        case 'HIVE':
+          installableClients.push('HIVE_CLIENT');
+          break;
+        case 'HBASE':
+          installableClients.push('HBASE_CLIENT');
+          break;
+        case 'YARN':
+          installableClients.push('YARN_CLIENT');
+          break;
+        case 'MAPREDUCE':
+          installableClients.push('MAPREDUCE_CLIENT');
+          break;
+        case 'MAPREDUCE2':
+          installableClients.push('MAPREDUCE2_CLIENT');
+          break;
+      }
+    });
+    this.get('content.hostComponents').forEach(function (component) {
+      var index = installableClients.indexOf(component.get('componentName'));
+      if (index > -1) {
+        installableClients.splice(index, 1);
+      }
+    }, this);
+    return installableClients;
+  }.property('content', 'content.hostComponents.length', 'App.Service', 
'App.supports.deleteHost'),
+  
   addableComponents: function () {
     var components = [];
     var services = App.Service.find();
@@ -138,7 +194,9 @@ App.MainHostSummaryView = Em.View.extend({
     var regionServerExists = false;
     var zookeeperServerExists = false;
     var nodeManagerExists = false;
-
+    
+    var installableClients = this.get('installableClientComponents');
+    
     this.get('content.hostComponents').forEach(function (component) {
       switch (component.get('componentName')) {
         case 'DATANODE':
@@ -174,8 +232,11 @@ App.MainHostSummaryView = Em.View.extend({
     if (!nodeManagerExists && services.findProperty('serviceName', 'YARN')) {
       components.pushObject(this.addableComponentObject.create({ 
'componentName': 'NODEMANAGER' }));
     }
+    if (installableClients.length > 0) {
+      components.pushObject(this.addableComponentObject.create({ 
'componentName': 'CLIENTS', subComponentNames: installableClients }));
+    }
     return components;
-  }.property('content', 'content.hostComponents.length'),
+  }.property('content', 'content.hostComponents.length', 
'installableClientComponents'),
 
   ComponentView: Em.View.extend({
     content: null,

Reply via email to