ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From atk...@apache.org
Subject [ambari] branch trunk updated: AMBARI-22851 Host Details page style fixes
Date Fri, 26 Jan 2018 15:27:11 GMT
This is an automated email from the ASF dual-hosted git repository.

atkach pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 26f178c  AMBARI-22851 Host Details page style fixes
26f178c is described below

commit 26f178ca2b50ccd80e8d97808578e39b4be0e0f2
Author: Andrii Tkach <atkach@apache.org>
AuthorDate: Fri Jan 26 13:47:55 2018 +0200

    AMBARI-22851 Host Details page style fixes
---
 ambari-web/app/messages.js                         |   3 +
 ambari-web/app/models/host_component.js            |   3 +
 ambari-web/app/styles/application.less             |   9 +-
 ambari-web/app/styles/config_versions_control.less |   5 -
 ambari-web/app/styles/hosts.less                   |  34 +-
 .../templates/common/configs/service_config.hbs    |   2 +-
 .../common/configs/service_config_wizard.hbs       |   2 +-
 ambari-web/app/templates/main/host/details.hbs     |  10 +-
 .../templates/main/host/details/host_component.hbs | 289 +++++++++------
 ambari-web/app/templates/main/host/summary.hbs     | 121 ++-----
 .../views/main/host/details/host_component_view.js |  92 +++--
 ambari-web/app/views/main/host/summary.js          | 170 ++-------
 .../main/host/details/host_component_view_test.js  | 136 +++----
 ambari-web/test/views/main/host/summary_test.js    | 396 +--------------------
 14 files changed, 381 insertions(+), 891 deletions(-)

diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 1f87f60..391ac89 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -117,6 +117,8 @@ Em.I18n.translations = {
   'common.description':'Description',
   'common.default':'Default',
   'common.client':'Client',
+  'common.master':'Master',
+  'common.slave':'Slave',
   'common.zookeeper':'ZooKeeper',
   'common.hbase':'HBase',
   'common.regionServer':'RegionServer',
@@ -2722,6 +2724,7 @@ Em.I18n.translations = {
   'hosts.host.summary.addComponent':'Add Component',
   'hosts.host.summary.currentVersion':'Current Version',
 
+  'hosts.host.details.upgradeFailed':'Component upgrade failed',
   'hosts.host.details.hostActions':'Host Actions',
   'hosts.host.details.needToRestart':'Host needs {0} {1} restarted',
   'hosts.host.details.needToRestart.button':'Restart',
diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js
index 19eb085..8157b75 100644
--- a/ambari-web/app/models/host_component.js
+++ b/ambari-web/app/models/host_component.js
@@ -158,6 +158,9 @@ App.HostComponent = DS.Model.extend({
   },
 
   componentTextStatus: function () {
+    if (this.get('isClient') && this.get("workStatus") === 'INSTALLED') {
+      return Em.I18n.t('common.installed');
+    }
     return App.HostComponentStatus.getTextStatus(this.get("workStatus"));
   }.property('workStatus', 'isDecommissioning')
 });
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index e89e3fa..20ff5b1 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -317,6 +317,10 @@ footer {
   display: inline-block;
 }
 
+.display-inline {
+  display: inline;
+}
+
 ::-webkit-scrollbar {
   -webkit-appearance: none;
 }
@@ -1330,8 +1334,9 @@ ul.filter {
   padding: 10px;
 }
 
-.button-caret-margin {
-  margin-top: 8px;
+.pull-caret-right {
+  float: right;
+  margin-top: 5px;
 }
 
 /*start charts rack*/
diff --git a/ambari-web/app/styles/config_versions_control.less b/ambari-web/app/styles/config_versions_control.less
index 0b7f131..86584ad 100644
--- a/ambari-web/app/styles/config_versions_control.less
+++ b/ambari-web/app/styles/config_versions_control.less
@@ -111,11 +111,6 @@
       text-align: left;
       padding: 10px;
     }
-    .caret {
-      position: absolute;
-      top: 15px;
-      right: 10px;
-    }
   }
   .filter-combobox {
     display: inline-block;
diff --git a/ambari-web/app/styles/hosts.less b/ambari-web/app/styles/hosts.less
index 29e4333..0909a3d 100644
--- a/ambari-web/app/styles/hosts.less
+++ b/ambari-web/app/styles/hosts.less
@@ -395,18 +395,11 @@
 
   .host-components {
     padding: 10px;
-    border-radius: 4px;
-    background: #FFF;
-    .component-name-block-wrapper {
-      display: table;
-      height: 34px;
-      .component-name-block {
-        white-space: normal;
-        word-wrap: break-word;
-        display: table-cell;
-        vertical-align: middle;
-      }
-
+    .component-name-block {
+      white-space: normal;
+      word-wrap: break-word;
+      display: table-cell;
+      vertical-align: middle;
     }
     .dropdown-menu {
       .disabled {
@@ -418,14 +411,15 @@
         pointer-events: auto;
       }
     }
-    .status-icons {
-      line-height: 34px;
-    }
-    .client-list {
-      ul {
-        list-style: none;
-      }
-    }
+  }
+
+  .status-icons span {
+    padding-right: 2px;
+  }
+
+  .action-icon {
+    padding: 2px 5px 0 5px;
+    font-size: 16px;
   }
 
   .logs-tab-content {
diff --git a/ambari-web/app/templates/common/configs/service_config.hbs b/ambari-web/app/templates/common/configs/service_config.hbs
index fe97062..e6a15ea 100644
--- a/ambari-web/app/templates/common/configs/service_config.hbs
+++ b/ambari-web/app/templates/common/configs/service_config.hbs
@@ -70,8 +70,8 @@
           <button {{bindAttr disabled="controller.isHostsConfigsPage"}}
             class="btn btn-default dropdown-toggle"
             data-toggle="dropdown">
+            <span class="caret pull-caret-right"></span>
             {{selectedConfigGroup.displayNameHosts}}
-            <span class="caret"></span>
           </button>
           <ul class="dropdown-menu config-groups-dropdown-menu">
             {{#isAuthorized "SERVICE.MANAGE_CONFIG_GROUPS"}}
diff --git a/ambari-web/app/templates/common/configs/service_config_wizard.hbs b/ambari-web/app/templates/common/configs/service_config_wizard.hbs
index 3abd5eb..fdf0059 100644
--- a/ambari-web/app/templates/common/configs/service_config_wizard.hbs
+++ b/ambari-web/app/templates/common/configs/service_config_wizard.hbs
@@ -21,8 +21,8 @@
     {{t common.group}}&nbsp;
 	  <div class="btn-group config-groups-dropdown">
 		  <button {{bindAttr disabled="controller.isHostsConfigsPage"}} class="btn btn-default dropdown-toggle first" data-toggle="dropdown">
+        <span class="caret pull-caret-right"></span>
         {{selectedConfigGroup.displayNameHosts}}
-        <span class="caret"></span>
       </button>
 		  <ul class="dropdown-menu config-groups-dropdown-menu">
         <!-- available config group menu links -->
diff --git a/ambari-web/app/templates/main/host/details.hbs b/ambari-web/app/templates/main/host/details.hbs
index 15b0f4a..6616f6d 100644
--- a/ambari-web/app/templates/main/host/details.hbs
+++ b/ambari-web/app/templates/main/host/details.hbs
@@ -19,15 +19,15 @@
 {{#if view.isLoaded}}
   <div id="host-details">
     <div class="host-details-header">
-      <div class="status-info">
-        <span rel="HealthTooltip" {{bindAttr class="view.content.healthClass view.content.healthIconClass"}} data-placement="bottom" {{bindAttr data-original-title="view.content.healthToolTip" }}></span><span
-              class='host-title'>{{unbound view.content.hostName}}</span>
+      <div class="status-info mbm">
+        <h2 class='table-title display-inline'>
+          <a class="disabled">{{t common.host}}</a><span>:&nbsp;{{unbound view.content.hostName}}</span>
+        </h2>
+        <span rel="HealthTooltip" {{bindAttr class="view.content.healthClass view.content.healthIconClass"}} data-placement="bottom" {{bindAttr data-original-title="view.content.healthToolTip" }}></span>
         {{#unless view.isActive}}
           <span class="host-maintenance-notice pull-right"><span class="icon-medkit"></span> {{t hosts.host.passive.mode}}</span>
         {{/unless}}
       </div>
-      <div><a href="javascript:void(null)" id="host-details-back-btn" data-toggle="modal" {{action back}}><i
-              class="glyphicon glyphicon-arrow-left"></i>&nbsp;{{t common.back}}</a></div>
     </div>
     <div class="content">
       {{view App.MainHostMenuView hostBinding="view.content"}}
diff --git a/ambari-web/app/templates/main/host/details/host_component.hbs b/ambari-web/app/templates/main/host/details/host_component.hbs
index 28d2acf..e14ae6e 100644
--- a/ambari-web/app/templates/main/host/details/host_component.hbs
+++ b/ambari-web/app/templates/main/host/details/host_component.hbs
@@ -16,152 +16,205 @@
 * limitations under the License.
 }}
 
-    <div class="col-md-1 col-lg-1 align-left status-icons component-label host-component-block">
-      <span class="health-icon-block">
-        {{#if view.isUpgradeFailed}}
-          {{#isAuthorized "HOST.ADD_DELETE_COMPONENTS"}}
-            <a href="#" {{action "upgradeComponent" view.content target="controller"}} >
-              <i title="Component upgrade failed" class="components-health glyphicon glyphicon-arrow-up"></i>
-            </a>
-          {{/isAuthorized}}
-        {{else}}
-          <span rel='componentHealthTooltip' {{bindAttr class="view.statusClass view.statusIconClass :components-health" data-original-title="view.componentTextStatus"}}></span>
-        {{/if}}
-      </span>
-    </div>
-    <div class="col-md-4 col-lg-5 component-name-block-wrapper">
-      <span class="component-name-block">
-        {{#if component.displayNameAdvanced}}
-          <span {{bindAttr title="component.displayNameAdvanced"}}>{{component.getDisplayNameAdvanced}}&nbsp;/</span>
-        {{else}}
-          <span {{bindAttr title="component.displayName"}}>{{component.getDisplayName}}&nbsp;/</span>
-        {{/if}}
-        <span class="hidden-lg"><br /></span> <a href="#" {{action routeToService component.service}} {{bindAttr title="component.service.displayName"}}>{{component.serviceDisplayName}}</a>
-      </span>
-    </div>
-<div class="col-md-1 col-lg-1 align-center status-icons">
+<td class="status-icons">
+  <span class="health-icon-block">
+    {{#if view.isUpgradeFailed}}
+      {{#isAuthorized "HOST.ADD_DELETE_COMPONENTS"}}
+        <a href="#" rel='componentHealthTooltip'
+          {{bindAttr data-original-title="view.upgradeFailedMessage"}}
+          {{action "upgradeComponent" view.content target="controller"}}>
+          <i class="glyphicon glyphicon-arrow-up"></i>
+        </a>
+      {{/isAuthorized}}
+    {{else}}
+      <i rel='componentHealthTooltip'
+        {{bindAttr class="view.statusClass view.statusIconClass :components-health"
+                   data-original-title="view.componentTextStatus"}}></i>
+    {{/if}}
+  </span>
   <span class="refresh-icon-block">
     {{#if component.staleConfigs}}
-      <span class="text-warning glyphicon glyphicon-refresh"></span>
+      <i class="text-warning glyphicon glyphicon-refresh"></i>
     {{/if}}
   </span>
-</div>
-<div class="col-md-1 col-lg-1 align-center status-icons">
   <span class="medkit-icon-block">
     {{#unless component.isActive}}
-      <span class="icon-medkit"></span>
+      <i class="icon-medkit"></i>
     {{/unless}}
   </span>
-</div>
-<div class="col-md-5 col-lg-4">
+</td>
+
+<td class="component-name-block">
+  <span>
+    {{#if component.displayNameAdvanced}}
+      <span {{bindAttr title="component.displayNameAdvanced"}}>{{component.getDisplayNameAdvanced}}&nbsp;/</span>
+    {{else}}
+      <span {{bindAttr title="component.displayName"}}>{{component.getDisplayName}}&nbsp;/</span>
+    {{/if}}
+    <span class="hidden-lg"><br/></span> <a href="#" {{action routeToService component.service}} {{bindAttr
+    title="component.service.displayName"}}>{{component.serviceDisplayName}}</a>
+  </span>
+</td>
+
+<td class="component-type-block">
+  <span>{{view.typeDisplay}}</span>
+</td>
+
+<td class="align-center">
   {{#havePermissions "SERVICE.DECOMMISSION_RECOMMISSION"}}
     <div class="dropdown">
-      <button {{ bindAttr class="view.disabled :btn :btn-default :btn-block :dropdown-toggle" disabled="App.router.wizardWatcherController.isNonWizardUser"}} data-toggle="dropdown">
-        {{view.componentTextStatus}}
-        <span class="caret pull-right button-caret-margin"></span>
-      </button>
-      {{#unless App.router.wizardWatcherController.isNonWizardUser}}
-        <ul class="dropdown-menu">
-          {{#if view.isComponentDecommissionAvailable}}
-            {{view view.decommissionView}}
-          {{/if}}
-          {{#if view.isComponentRecommissionAvailable}}
-            {{view view.decommissionView}}
-          {{/if}}
-          {{#if view.isRestartableComponent}}
-            <li {{bindAttr class="view.isRestartComponentDisabled:hidden"}}>
-              <a href="javascript:void(null)" data-toggle="modal" {{action "restartComponent" view.content target="controller"}}>
-                {{t common.restart}}
+      <a {{bindAttr class=":dropdown-toggle view.isDisabled:disabled"}} data-toggle="dropdown" href="javascript:void(null)">
+        <i class="glyphicon glyphicon-option-horizontal action-icon"></i>
+      </a>
+      {{#unless isDisabled}}
+
+        {{!-- Actions of Clients --}}
+        {{#if view.content.isClient}}
+          <ul class="dropdown-menu">
+            <li>
+              <a href="javascript:void(null)" data-toggle="modal"
+                {{action "refreshComponentConfigs" view.content target="controller"}}>
+                {{t hosts.host.details.refreshConfigs}}
+              </a>
+            </li>
+            <li>
+              <a href="javascript:void(null)" {{bindAttr class="view.isInit::disabled" }}
+                 data-toggle="modal" {{action installClient target="view"}}>
+                {{t common.install}}
               </a>
             </li>
-          {{/if}}
-          {{#unless view.isInstalling}}
-            {{#isAuthorized "SERVICE.START_STOP"}}
-              {{#if view.isStart}}
-                <li {{bindAttr class="view.isComponentDecommissioning:hidden view.noActionAvailable"}}>
-                  <a href="javascript:void(null)" data-toggle="modal" {{action "stopComponent" view.content target="controller"}}>
-                    {{t common.stop}}
+            {{#if view.isInstallFailed}}
+              <li>
+                <a href="javascript:void(null)" {{action installClient target="view"}}>
+                  {{t common.reinstall}}
+                </a>
+              </li>
+            {{/if}}
+            {{#each command in view.clientCustomCommands}}
+              <li>
+                <a href="javascript:void(null)"
+                  {{action "executeCustomCommand" command target="controller" href=true}}>
+                  {{command.label}}
+                </a>
+              </li>
+            {{/each}}
+          </ul>
+        {{else}}
+
+          {{!-- Actions of Masters and Slaves --}}
+          <ul class="dropdown-menu">
+            {{#if view.isComponentDecommissionAvailable}}
+              {{view view.decommissionView}}
+            {{/if}}
+            {{#if view.isComponentRecommissionAvailable}}
+              {{view view.decommissionView}}
+            {{/if}}
+            {{#if view.isRestartableComponent}}
+              <li {{bindAttr class="view.isRestartComponentDisabled:hidden"}}>
+                <a href="javascript:void(null)" data-toggle="modal" {{action "restartComponent" view.content
+                                                                             target="controller"}}>
+                  {{t common.restart}}
+                </a>
+              </li>
+            {{/if}}
+            {{#unless view.isInstalling}}
+              {{#isAuthorized "SERVICE.START_STOP"}}
+                {{#if view.isStart}}
+                  <li {{bindAttr class="view.isComponentDecommissioning:hidden view.noActionAvailable"}}>
+                    <a href="javascript:void(null)" data-toggle="modal" {{action "stopComponent" view.content
+                                                                                 target="controller"}}>
+                      {{t common.stop}}
+                    </a>
+                  </li>
+                {{/if}}
+                {{#unless view.isStart}}
+                  {{#unless view.isInit}}
+                    <li {{bindAttr
+                      class="view.isUpgradeFailed:hidden view.isInstallFailed:hidden view.isDecommissioning:hidden view.noActionAvailable"}}>
+                      <a href="javascript:void(null)" data-toggle="modal" {{action "startComponent" view.content
+                                                                                   target="controller"}}>
+                        {{t common.start}}
+                      </a>
+                    </li>
+                  {{/unless}}
+                {{/unless}}
+              {{/isAuthorized}}
+              {{#if view.isUpgradeFailed}}
+                <li {{bindAttr class="view.noActionAvailable"}}>
+                  <a href="javascript:void(null)" data-toggle="modal" {{action "upgradeComponent" view.content
+                                                                               target="controller"}}>
+                    {{t common.reUpgrade}}
                   </a>
                 </li>
               {{/if}}
-              {{#unless view.isStart}}
-                {{#unless view.isInit}}
-                  <li {{bindAttr class="view.isUpgradeFailed:hidden view.isInstallFailed:hidden view.isDecommissioning:hidden view.noActionAvailable"}}>
-                    <a href="javascript:void(null)" data-toggle="modal" {{action "startComponent" view.content target="controller"}}>
-                      {{t common.start}}
+              {{#if view.isInstallFailed}}
+                <li {{bindAttr class="view.noActionAvailable"}}>
+                  <a href="javascript:void(null)" data-toggle="modal" {{action "installComponent" view.content
+                                                                               target="controller"}}>
+                    {{t common.reinstall}}
+                  </a>
+                </li>
+              {{/if}}
+              {{#if view.isReassignable}}
+                {{#isAuthorized "SERVICE.MOVE"}}
+                  <li {{bindAttr class="view.noActionAvailable view.isMoveComponentDisabled:disabled"}}>
+                    <a href="javascript:void(null)" data-toggle="modal" {{action "moveComponent" view.content
+                                                                                 target="controller"}}>
+                      {{t common.move}}
                     </a>
                   </li>
-                {{/unless}}
-              {{/unless}}
-            {{/isAuthorized}}
-            {{#if view.isUpgradeFailed}}
-              <li {{bindAttr class="view.noActionAvailable"}}>
-                <a href="javascript:void(null)" data-toggle="modal" {{action "upgradeComponent" view.content target="controller"}}>
-                  {{t common.reUpgrade}}
-                </a>
-              </li>
-            {{/if}}
-            {{#if view.isInstallFailed}}
-              <li {{bindAttr class="view.noActionAvailable"}}>
-                <a href="javascript:void(null)" data-toggle="modal" {{action "installComponent" view.content target="controller"}}>
+                {{/isAuthorized}}
+              {{/if}}
+              {{#isAuthorized "HOST.TOGGLE_MAINTENANCE"}}
+                <li {{bindAttr class="view.noActionAvailable view.content.isImpliedState:disabled :allow-tooltip"}}
+                  {{bindAttr data-original-title="view.maintenanceTooltip"}} rel="passiveTooltip">
+                  <a href="javascript:void(null)"
+                     data-toggle="modal" {{action "toggleMaintenanceMode" view.content target="controller"}}>
+                    {{#if view.isActive}}
+                      {{t passiveState.turnOn}}
+                    {{else}}
+                      {{t passiveState.turnOff}}
+                    {{/if}}
+                  </a>
+                </li>
+              {{/isAuthorized}}
+            {{/unless}}
+            {{#if view.isInit}}
+              <li>
+                <a href="javascript:void(null)" data-toggle="modal" {{action "installComponent" view.content
+                                                                             target="controller"}}>
                   {{t common.reinstall}}
                 </a>
               </li>
             {{/if}}
-            {{#if view.isReassignable}}
-              {{#isAuthorized "SERVICE.MOVE"}}
-                <li {{bindAttr class="view.noActionAvailable view.isMoveComponentDisabled:disabled"}}>
-                  <a href="javascript:void(null)" data-toggle="modal" {{action "moveComponent" view.content target="controller"}}>
-                    {{t common.move}}
+            {{#if view.isDeletableComponent}}
+              {{#isAuthorized "HOST.ADD_DELETE_COMPONENTS"}}
+                <li {{bindAttr class="view.isDeleteComponentDisabled:disabled"}}>
+                  <a href="javascript:void(null)" data-toggle="modal" {{action "deleteComponent" view.content
+                                                                               target="controller"}}>
+                    {{t common.delete}}
                   </a>
                 </li>
               {{/isAuthorized}}
             {{/if}}
-            {{#isAuthorized "HOST.TOGGLE_MAINTENANCE"}}
-              <li {{bindAttr class="view.noActionAvailable view.content.isImpliedState:disabled :allow-tooltip"}}
-                {{bindAttr data-original-title="view.maintenanceTooltip"}} rel="passiveTooltip" >
-              <a href="javascript:void(null)"
-                 data-toggle="modal" {{action "toggleMaintenanceMode" view.content target="controller"}}>
-                {{#if view.isActive}}
-                  {{t passiveState.turnOn}}
-                {{else}}
-                  {{t passiveState.turnOff}}
-                {{/if}}
-              </a>
-            </li>
-            {{/isAuthorized}}
-          {{/unless}}
-          {{#if view.isInit}}
-            <li>
-              <a href="javascript:void(null)" data-toggle="modal" {{action "installComponent" view.content target="controller"}}>
-                {{t common.reinstall}}
-              </a>
-            </li>
-          {{/if}}
-          {{#if view.isDeletableComponent}}
-            {{#isAuthorized "HOST.ADD_DELETE_COMPONENTS"}}
-              <li {{bindAttr class="view.isDeleteComponentDisabled:disabled"}}>
-                  <a href="javascript:void(null)" data-toggle="modal" {{action "deleteComponent" view.content target="controller"}}>
-                    {{t common.delete}}
-                  </a>
-              </li>
-            {{/isAuthorized}}
-          {{/if}}
-          {{#if view.isRefreshConfigsAllowed}}
+            {{#if view.isRefreshConfigsAllowed}}
               <li>
-                  <a href="javascript:void(null)" data-toggle="modal" {{action "refreshComponentConfigs" view.content target="controller"}}>
-                    {{t hosts.host.details.refreshConfigs}}
-                  </a>
+                <a href="javascript:void(null)" data-toggle="modal" {{action "refreshComponentConfigs" view.content
+                                                                             target="controller"}}>
+                  {{t hosts.host.details.refreshConfigs}}
+                </a>
               </li>
-          {{/if}}
+            {{/if}}
 
-        {{#each command in view.customCommands}}
-          <li {{bindAttr class="command.disabled:disabled"}}>
-            <a href="javascript:void(null)" {{action "executeCustomCommand" command target="controller" href=true}}>{{command.label}}</a>
-          </li>
-        {{/each}}
-        </ul>
+            {{#each command in view.customCommands}}
+              <li {{bindAttr class="command.disabled:disabled"}}>
+                <a href="javascript:void(null)" {{action "executeCustomCommand" command target="controller"
+                                                         href=true}}>{{command.label}}</a>
+              </li>
+            {{/each}}
+          </ul>
+        {{/if}}
       {{/unless}}
     </div>
   {{/havePermissions}}
-</div>
+</td>
diff --git a/ambari-web/app/templates/main/host/summary.hbs b/ambari-web/app/templates/main/host/summary.hbs
index 3ceb92f..f04f134 100644
--- a/ambari-web/app/templates/main/host/summary.hbs
+++ b/ambari-web/app/templates/main/host/summary.hbs
@@ -16,6 +16,17 @@
 * limitations under the License.
 }}
 
+{{#havePermissions "SERVICE.MODIFY_CONFIGS, SERVICE.START_STOP, HOST.ADD_DELETE_COMPONENTS, HOST.TOGGLE_MAINTENANCE"}}
+  {{#if view.content.componentsWithStaleConfigsCount}}
+    <div class="alert alert-warning clearfix restart-required">
+      <i class="glyphicon glyphicon-refresh"></i> {{view.needToRestartMessage}}
+      <button {{bindAttr class=":btn :restart-components :pull-right :btn-warning" disabled="App.router.wizardWatcherController.isNonWizardUser"}} {{action restartAllStaleConfigComponents target="controller"}}>
+        {{t hosts.host.details.needToRestart.button}}
+      </button>
+    </div>
+  {{/if}}
+{{/havePermissions}}
+
 <div class="row host-block">
   <div class="col-md-6">
       {{!components}}
@@ -48,105 +59,21 @@
         </div>
       </div>
       <div class="host-components panel-body">
+        <table class="table">
+          <thead>
+            <th>{{t common.status}}</th>
+            <th>{{t common.name}}</th>
+            <th>{{t common.type}}</th>
+            <th class="align-center">{{t common.action}}</th>
+          </thead>
+          <tbody>
           {{#if view.sortedComponents.length}}
-
-              {{#havePermissions "SERVICE.MODIFY_CONFIGS, SERVICE.START_STOP, HOST.ADD_DELETE_COMPONENTS, HOST.TOGGLE_MAINTENANCE"}}
-                  {{#if view.content.componentsWithStaleConfigsCount}}
-                    <div class="alert alert-warning clearfix restart-required">
-                      <i class="glyphicon glyphicon-refresh"></i> {{view.needToRestartMessage}}
-                      <button {{bindAttr class=":btn :restart-components :pull-right :btn-warning" disabled="App.router.wizardWatcherController.isNonWizardUser"}} {{action restartAllStaleConfigComponents target="controller"}}>
-                          {{t hosts.host.details.needToRestart.button}}
-                      </button>
-                    </div>
-                  {{/if}}
-              {{/havePermissions}}
-
-              {{#each component in view.sortedComponents}}
-                {{view component.viewClass classNames="row row-no-pad" contentBinding="component"}}
-              {{/each}}
+            {{#each component in view.sortedComponents}}
+              {{view component.viewClass contentBinding="component"}}
+            {{/each}}
           {{/if}}
-          {{!clients and add component button}}
-        <div class="clients row row-no-pad">
-          <div class="col-md-7 col-lg-8 row client-list component-name-block-wrapper">
-              {{#if view.clients.length}}
-                <div class="col-md-3 align-right">{{t common.clients}}&nbsp;/</div>
-                <div class="col-md-9 component-name-block-wrapper">
-                  <ul>
-                    {{#each component in view.clients}}
-                      <li>
-                        <span {{bindAttr class="component.staleConfigs:component-restart-required-name"}}>{{component.displayName}}</span>
-                        {{#if component.isInstallFailed}}
-                          &nbsp;<span class="health-status-installed icon-warning-sign"></span>
-                        {{/if}}
-                        {{#if component.staleConfigs}}
-                          &nbsp;<span class="text-warning glyphicon glyphicon-refresh"></span>
-                        {{/if}}
-                        {{#if component.service.isInPassive}}
-                          &nbsp;<span class="icon-medkit"></span>
-                        {{/if}}
-                      </li>
-                    {{/each}}
-                  </ul>
-                </div>
-              {{/if}}
-          </div>
-          <div class="col-md-5 col-lg-4 pull-right">
-              {{#havePermissions "SERVICE.MODIFY_CONFIGS, SERVICE.START_STOP, HOST.ADD_DELETE_COMPONENTS, HOST.TOGGLE_MAINTENANCE"}}
-                  {{#if view.clients.length}}
-                    <div class="dropdown">
-                      <button id="add_component"
-                              data-toggle="dropdown" {{bindAttr disabled="App.router.wizardWatcherController.isNonWizardUser" class=":btn :btn-default :btn-block :dropdown-toggle controller.content.isNotHeartBeating:disabled"}}>
-                          {{t common.installed}}
-                        <span class="caret pull-right button-caret-margin"></span>
-                      </button>
-                      {{#unless App.router.wizardWatcherController.isNonWizardUser}}
-                        <ul class="dropdown-menu">
-                          <li>
-                            <a href="javascript:void(null)"
-                               data-toggle="modal" {{action refreshConfigs view.clients target="controller"}}>
-                                {{t hosts.host.details.refreshConfigs}}
-                            </a>
-                          </li>
-                          <li>
-                            <a href="javascript:void(null)" {{bindAttr class="view.areClientsNotInstalled::disabled" }}
-                               data-toggle="modal" {{action installClients target="view"}}>
-                                {{t host.host.details.installClients}}
-                            </a>
-                          </li>
-                            {{#if view.anyClientFailedToInstall}}
-                              <li>
-                                <a href="javascript:void(null)" {{action reinstallClients target="view"}}>
-                                    {{t host.host.details.reinstallClients}}
-                                </a>
-                              </li>
-                            {{/if}}
-                            {{#each option in view.clientsWithCustomCommands}}
-                              <li class="dropdown-submenu submenu-left">
-                                <a href="javascript:void(null)">
-                                  <i class="glyphicon glyphicon-play-circle"></i>
-                                    {{option.label}}
-                                </a>
-
-                                <div class="dropdown-menu-wrap">
-                                  <ul class="dropdown-menu">
-                                      {{#each command in option.commands}}
-                                        <li>
-                                          <a href="javascript:void(null)" {{action "executeCustomCommand" command target="controller" href=true}}>
-                                            {{command.label}}
-                                          </a>
-                                        </li>
-                                      {{/each}}
-                                  </ul>
-                                </div>
-                              </li>
-                            {{/each}}
-                        </ul>
-                      {{/unless}}
-                    </div>
-                  {{/if}}
-              {{/havePermissions}}
-          </div>
-        </div>
+          </tbody>
+        </table>
       </div>
     </div>
     <div class="host-configuration">
diff --git a/ambari-web/app/views/main/host/details/host_component_view.js b/ambari-web/app/views/main/host/details/host_component_view.js
index 92cf0c6..45b2b0a 100644
--- a/ambari-web/app/views/main/host/details/host_component_view.js
+++ b/ambari-web/app/views/main/host/details/host_component_view.js
@@ -22,34 +22,48 @@ var uiEffects = require('utils/ui_effects');
 App.HostComponentView = Em.View.extend({
 
   templateName: require('templates/main/host/details/host_component'),
+  tagName: 'tr',
 
   /**
    * @type {App.HostComponent}
    */
   content: null,
+
+  /**
+   * @type {Array}
+   * @const
+   */
   excludedMasterCommands: ['DECOMMISSION', 'RECOMMISSION'],
+
   /**
-   * @type {App.HostComponent}
+   * @type {string}
+   * @const
    */
-  hostComponent: function () {
-    var hostComponent = null;
-    var serviceComponent = this.get('content');
-    var host = App.router.get('mainHostDetailsController.content');
-    if (host) {
-      hostComponent = host.get('hostComponents').findProperty('componentName', serviceComponent.get('componentName'));
-    }
-    return hostComponent;
-  }.property('content', 'App.router.mainHostDetailsController.content'),
+  upgradeFailedMessage: Em.I18n.t('hosts.host.details.upgradeFailed'),
+
   /**
    * @type {String}
    */
-  workStatus: Em.computed.firstNotBlank('hostComponent.workStatus', 'content.workStatus'),
+  workStatus: Em.computed.alias('content.workStatus'),
 
   /**
    * Return host component text status
    * @type {String}
    */
-  componentTextStatus: Em.computed.firstNotBlank('hostComponent.componentTextStatus', 'content.componentTextStatus'),
+  componentTextStatus: Em.computed.alias('content.componentTextStatus'),
+
+  /**
+   * @type {string}
+   */
+  typeDisplay: function() {
+    if (this.get('content.isMaster')) {
+      return Em.I18n.t('common.master')
+    } else if (this.get('content.isSlave')) {
+      return Em.I18n.t('common.slave')
+    } else if (this.get('content.isClient')) {
+      return Em.I18n.t('common.client')
+    }
+  }.property('content'),
 
   /**
    * CSS-class for host component status
@@ -66,9 +80,13 @@ App.HostComponentView = Em.View.extend({
       return 'health-status-color-blue glyphicon glyphicon-cog';
     }
 
+    // Client can't be started, it has only INSTALLED as a working state, so show working/green icon
+    if (this.get('workStatus') === App.HostComponentStatus.stopped && this.get('content.isClient')) {
+      return 'health-status-started';
+    }
+
     //For all other cases
     return 'health-status-' + App.HostComponentStatus.getKeyName(this.get('workStatus'));
-
   }.property('workStatus'),
 
   /**
@@ -90,11 +108,17 @@ App.HostComponentView = Em.View.extend({
    * 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)
    * Disabled if component's action list is empty
-   * @type {String}
+   * @type {boolean}
    */
-  disabled: function () {
-    return ( (this.get('parentView.content.healthClass') === "health-status-DEAD-YELLOW") || (this.get('noActionAvailable') === 'hidden' && this.get('isRestartComponentDisabled'))) ? 'disabled' : '';
-  }.property('parentView.content.healthClass', 'noActionAvailable', 'isRestartComponentDisabled'),
+  isDisabled: function () {
+    return (this.get('parentView.content.healthClass') === "health-status-DEAD-YELLOW") ||
+      (this.get('noActionAvailable') === 'hidden' && this.get('isRestartComponentDisabled')) ||
+      App.router.get('wizardWatcherController.isNonWizardUser');
+  }.property(
+    'parentView.content.healthClass',
+    'noActionAvailable',
+    'isRestartComponentDisabled',
+    'App.router.wizardWatcherController.isNonWizardUser'),
 
   /**
    * For Upgrade failed state
@@ -154,7 +178,7 @@ App.HostComponentView = Em.View.extend({
 
   /**
    *  Tooltip message for switch maintenance mode option
-   *  @type {Strting}
+   *  @type {String}
    */
   maintenanceTooltip: function () {
     switch (this.get('content.passiveState')) {
@@ -188,9 +212,9 @@ App.HostComponentView = Em.View.extend({
    * @type {bool}
    */
   isDeleteComponentDisabled: function () {
-    var stackComponentCount = App.StackServiceComponent.find(this.get('hostComponent.componentName')).get('minToInstall');
-    var installedCount = App.HostComponent.getCount(this.get('hostComponent.componentName'), 'totalCount');
-    if(this.get('hostComponent.componentName') == 'MYSQL_SERVER' && this.get('hostComponent.serviceDisplayName') == 'Hive') {
+    var stackComponentCount = App.StackServiceComponent.find(this.get('content.componentName')).get('minToInstall');
+    var installedCount = App.HostComponent.getCount(this.get('content.componentName'), 'totalCount');
+    if(this.get('content.componentName') === 'MYSQL_SERVER' && this.get('content.serviceDisplayName') === 'Hive') {
       var db_type=App.db.getConfigs().findProperty('type','hive-env').properties['hive_database'];
       var status=[App.HostComponentStatus.stopped, App.HostComponentStatus.unknown, App.HostComponentStatus.install_failed, App.HostComponentStatus.upgrade_failed, App.HostComponentStatus.init].contains(this.get('workStatus'));
       if(db_type.indexOf('Existing') > -1 && status)
@@ -198,7 +222,7 @@ App.HostComponentView = Em.View.extend({
       else
     	return true;
     }
-    if (this.get('hostComponent.componentName') == 'JOURNALNODE') {
+    if (this.get('content.componentName') === 'JOURNALNODE') {
       var count_JN = App.HostComponent.find().filterProperty('componentName', 'JOURNALNODE').get('length');
       return count_JN <= 3; // TODO get 3 from stack
     }
@@ -365,7 +389,7 @@ App.HostComponentView = Em.View.extend({
 
     if (component.get('cardinality') !== '1') {
       if (!this.get('isStart')) {
-        if (App.HostComponent.getCount(this.get('hostComponent.componentName'), 'totalCount') > 1) {
+        if (App.HostComponent.getCount(this.get('content.componentName'), 'totalCount') > 1) {
           if (this.runningComponentCounter()) {
             return false;
           }
@@ -376,6 +400,28 @@ App.HostComponentView = Em.View.extend({
     }
 
     return true;
+  },
+
+  /**
+   * @type {Array}
+   */
+  clientCustomCommands: function() {
+    const componentName = this.get('content.componentName');
+
+    if (componentName === 'KERBEROS_CLIENT') {
+      return [];
+    }
+
+    return App.StackServiceComponent.find(componentName).get('customCommands').map(function (command) {
+      return {
+        label: Em.I18n.t('services.service.actions.run.executeCustomCommand.menu').format(command),
+        command: command
+      }
+    });
+  }.property('controller'),
+
+  installClient: function() {
+    this.get('controller').installClients([this.get('content')]);
   }
 
 });
diff --git a/ambari-web/app/views/main/host/summary.js b/ambari-web/app/views/main/host/summary.js
index 89332de..82f42da 100644
--- a/ambari-web/app/views/main/host/summary.js
+++ b/ambari-web/app/views/main/host/summary.js
@@ -120,96 +120,43 @@ App.MainHostSummaryView = Em.View.extend(App.TimeRangeMixin, {
 
   /**
    * Update <code>sortedComponents</code>
-   * Master components first, then slaves
+   * Master components first, then slaves and clients
    */
   sortedComponentsFormatter: function() {
-    var updatebleProperties = Em.A(['workStatus', 'passiveState', 'staleConfigs', 'haStatus']);
-    var self = this;
-    var hostComponentViewMap = this.get('hostComponentViewMap');
+    const updatebleProperties = Em.A(['workStatus', 'passiveState', 'staleConfigs', 'haStatus']);
+    const hostComponentViewMap = this.get('hostComponentViewMap');
+    const masters = [], slaves = [], clients = [];
     // Remove deleted components
-    this.get('sortedComponents').forEach(function(sortedComponent, index) {
-      if (!self.get('content.hostComponents').findProperty('id', sortedComponent.get('id'))) {
-        self.get('sortedComponents').removeAt(index, 1);
+    this.get('sortedComponents').forEach((sortedComponent, index) => {
+      if (!this.get('content.hostComponents').findProperty('id', sortedComponent.get('id'))) {
+        this.get('sortedComponents').removeAt(index, 1);
       }
     });
 
     this.get('content.hostComponents').forEach(function (component) {
-      if (component.get('isMaster') || component.get('isSlave')) {
-        var obj = this.get('sortedComponents').findProperty('id', component.get('id'));
-        if (obj) {
-          // Update existing component
-          updatebleProperties.forEach(function(property) {
-            obj.set(property, component.get(property));
-          });
-        }
-        else {
-          // Add new component
-          component.set('viewClass', hostComponentViewMap[component.get('componentName')] ? hostComponentViewMap[component.get('componentName')] : App.HostComponentView);
-          if (component.get('isMaster')) {
-            // Masters should be before slaves
-            var lastMasterIndex = 0, atLeastOneMasterExists = false;
-            this.get('sortedComponents').forEach(function(sortedComponent, index) {
-              if (sortedComponent.get('isMaster')) {
-                lastMasterIndex = index;
-                atLeastOneMasterExists = true;
-              }
-            });
-            this.get('sortedComponents').insertAt(atLeastOneMasterExists ? lastMasterIndex + 1 : 0, component);
-          }
-          else {
-            // it is slave 100%
-            this.get('sortedComponents').pushObject(component);
-          }
+      const obj = this.get('sortedComponents').findProperty('id', component.get('id'));
+      if (obj) {
+        // Update existing component
+        updatebleProperties.forEach(function (property) {
+          obj.set(property, component.get(property));
+        });
+      } else {
+        component.set('viewClass', hostComponentViewMap[component.get('componentName')] ? hostComponentViewMap[component.get('componentName')] : App.HostComponentView);
+        if (component.get('isMaster')) {
+          masters.push(component);
+        } else if (component.get('isSlave')) {
+          slaves.push(component);
+        } else if (component.get('isClient')) {
+          component.set('isLast', true);
+          component.set('isInstallFailed', ['INSTALL_FAILED', 'INIT'].contains(component.get('workStatus')));
+          clients.pushObject(component);
         }
       }
     }, this);
+    this.set('sortedComponents', masters.concat(slaves, clients));
   },
 
   /**
-   * List of installed clients
-   * @type {App.HostComponent[]}
-   */
-  clients: function () {
-    var clients = [];
-    this.get('content.hostComponents').forEach(function (component) {
-      if (!component.get('componentName')) {
-        //temporary fix because of different data in hostComponents and serviceComponents
-        return;
-      }
-      if (!component.get('isSlave') && !component.get('isMaster')) {
-        if (clients.length) {
-          clients[clients.length - 1].set('isLast', false);
-        }
-        component.set('isLast', true);
-        component.set('isInstallFailed', ['INSTALL_FAILED', 'INIT'].contains(component.get('workStatus')));
-        clients.push(component);
-      }
-    }, this);
-    return clients;
-  }.property('content.hostComponents.length', 'content.hostComponents.@each.workStatus'),
-
-  anyClientFailedToInstall: Em.computed.someBy('clients', 'isInstallFailed', true),
-
-  /**
-   * Check if some clients not installed or started
-   *
-   * @type {bool}
-   **/
-  areClientsNotInstalled: Em.computed.or('anyClientFailedToInstall', 'installableClientComponents.length'),
-
-  /**
-   * Check if some clients have stale configs
-   * @type {bool}
-   */
-  areClientWithStaleConfigs: Em.computed.someBy('clients', 'staleConfigs', true),
-
-  /**
-   * List of install failed clients
-   * @type {App.HostComponent[]}
-   */
-  installFailedClients: Em.computed.filterBy('clients', 'workStatus', 'INSTALL_FAILED'),
-
-  /**
    * Template for addable component
    * @type {Em.Object}
    */
@@ -233,26 +180,6 @@ App.MainHostSummaryView = Em.View.extend(App.TimeRangeMixin, {
   addComponentDisabled: Em.computed.or('!isAddComponent', '!addableComponents.length'),
 
   /**
-   * List of client's that may be installed to the current host
-   * @type {String[]}
-   */
-  installableClientComponents: function() {
-    var clientComponents = App.StackServiceComponent.find().filterProperty('isClient');
-    var installedServices = this.get('installedServices');
-    var installedClients = this.get('clients').mapProperty('componentName');
-    return clientComponents.filter(function(component) {
-      // service for current client is installed but client isn't installed on current host
-      return installedServices.contains(component.get('serviceName')) && !installedClients.contains(component.get('componentName'));
-    });
-  }.property('content.hostComponents.length', 'installedServices.length'),
-
-  notInstalledClientComponents: function () {
-    return this.get('clients').filter(function(component) {
-      return ['INIT', 'INSTALL_FAILED'].contains(component.get('workStatus'));
-    }).concat(this.get('installableClientComponents'));
-  }.property('installableClientComponents.length', 'clients.length'),
-
-  /**
    * List of components that may be added to the current host
    * @type {Em.Object[]}
    */
@@ -299,52 +226,5 @@ App.MainHostSummaryView = Em.View.extend(App.TimeRangeMixin, {
   timeSinceHeartBeat: function () {
     var d = this.get('content.rawLastHeartBeatTime');
     return d ? $.timeago(d) : '';
-  }.property('content.rawLastHeartBeatTime'),
-
-  /**
-   * Get clients with custom commands
-   */
-  clientsWithCustomCommands: function() {
-    var clients = this.get('clients').rejectProperty('componentName', 'KERBEROS_CLIENT');
-    var options = [];
-    var clientWithCommands;
-    clients.forEach(function(client) {
-      var componentName = client.get('componentName');
-      var customCommands = App.StackServiceComponent.find(componentName).get('customCommands');
-
-      if (customCommands.length) {
-        clientWithCommands = {
-          label: client.get('displayName'),
-          commands: []
-        };
-        customCommands.forEach(function(command) {
-          clientWithCommands.commands.push({
-            label: Em.I18n.t('services.service.actions.run.executeCustomCommand.menu').format(command),
-            service: client.get('service.serviceName'),
-            hosts: client.get('hostName'),
-            component: componentName,
-            command: command
-          });
-        });
-
-        options.push(clientWithCommands);
-      }
-    });
-
-    return options;
-  }.property('controller'),
-
-  /**
-   * Call installClients method from controller for not installed client components
-   */
-  installClients: function () {
-    this.get('controller').installClients(this.get('notInstalledClientComponents'));
-  },
-
-  /**
-   * Call installClients method from controller for not install failed client components
-   */
-  reinstallClients: function () {
-    this.get('controller').installClients(this.get('installFailedClients'));
-  }
+  }.property('content.rawLastHeartBeatTime')
 });
diff --git a/ambari-web/test/views/main/host/details/host_component_view_test.js b/ambari-web/test/views/main/host/details/host_component_view_test.js
index f78c71e..1ac6fe0 100644
--- a/ambari-web/test/views/main/host/details/host_component_view_test.js
+++ b/ambari-web/test/views/main/host/details/host_component_view_test.js
@@ -30,7 +30,9 @@ function getView() {
     content: Em.Object.create({
       componentName: 'component'
     }),
-    hostComponent: Em.Object.create()
+    controller: Em.Object.create({
+      installClients: sinon.spy()
+    })
   });
 }
 
@@ -42,32 +44,32 @@ describe('App.HostComponentView', function() {
 
   App.TestAliases.testAsComputedNotEqual(getView(), 'isRestartComponentDisabled', 'workStatus', App.HostComponentStatus.started);
 
-  describe('#disabled', function() {
+  describe('#isDisabled', function() {
 
     var tests = Em.A([
       {
         parentView: {content: {healthClass: 'health-status-DEAD-YELLOW'}},
         noActionAvailable: '',
         isRestartComponentDisabled: true,
-        e: 'disabled'
+        e: true
       },
       {
         parentView: {content: {healthClass: 'another-class'}},
         noActionAvailable: '',
         isRestartComponentDisabled: true,
-        e: ''
+        e: false
       },
       {
         parentView: {content: {healthClass: 'another-class'}},
         noActionAvailable: 'hidden',
         isRestartComponentDisabled: true,
-        e: 'disabled'
+        e: true
       },
       {
         parentView: {content: {healthClass: 'another-class'}},
         noActionAvailable: 'hidden',
         isRestartComponentDisabled: false,
-        e: ''
+        e: false
       }
     ]);
 
@@ -80,7 +82,7 @@ describe('App.HostComponentView', function() {
           noActionAvailable: test.noActionAvailable,
           isRestartComponentDisabled: test.isRestartComponentDisabled
         });
-        expect(hostComponentView.get('disabled')).to.equal(test.e);
+        expect(hostComponentView.get('isDisabled')).to.equal(test.e);
       });
     });
 
@@ -127,21 +129,21 @@ describe('App.HostComponentView', function() {
 
     it('delete is disabled because min cardinality 1', function() {
       this.mock.returns(Em.Object.create({minToInstall: 1}));
-      hostComponentView.get('hostComponent').set('componentName', 'C1');
+      hostComponentView.get('content').set('componentName', 'C1');
       hostComponentView.propertyDidChange('isDeleteComponentDisabled');
       expect(hostComponentView.get('isDeleteComponentDisabled')).to.be.true;
     });
 
     it('delete is disabled because min cardinality 0 and status INSTALLED', function() {
       this.mock.returns(Em.Object.create({minToInstall: 0}));
-      hostComponentView.get('hostComponent').set('workStatus', 'INIT');
+      hostComponentView.get('content').set('workStatus', 'INIT');
       hostComponentView.propertyDidChange('isDeleteComponentDisabled');
       expect(hostComponentView.get('isDeleteComponentDisabled')).to.be.false;
     });
 
     it('delete is enabled because min cardinality 0 and status STARTED', function() {
       this.mock.returns(Em.Object.create({minToInstall: 0}));
-      hostComponentView.get('hostComponent').set('workStatus', 'STARTED');
+      hostComponentView.get('content').set('workStatus', 'STARTED');
       hostComponentView.propertyDidChange('isDeleteComponentDisabled');
       expect(hostComponentView.get('isDeleteComponentDisabled')).to.be.true;
     });
@@ -149,79 +151,13 @@ describe('App.HostComponentView', function() {
     it('delete is enabled because mysql server is stopped and hive is using external database', function() {
       App.db.setConfigs(configs);
       this.mock.returns(Em.Object.create({minToInstall: 0}));
-      hostComponentView.get('hostComponent').set('componentName', 'MYSQL_SERVER');
-      hostComponentView.get('hostComponent').set('workStatus', 'STOPPED');
+      hostComponentView.get('content').set('componentName', 'MYSQL_SERVER');
+      hostComponentView.get('content').set('workStatus', 'STOPPED');
       hostComponentView.propertyDidChange('isDeleteComponentDisabled');
       expect(hostComponentView.get('isDeleteComponentDisabled')).to.be.true;
     });
   });
 
-  describe('#componentTextStatus', function() {
-
-    var tests = Em.A([
-      {
-        componentTextStatus: 'status',
-        hostComponent: null,
-        e: 'status',
-        m: 'get content status'
-      },
-      {
-        componentTextStatus: 'status',
-        hostComponent: Em.Object.create({componentTextStatus: 'new_status'}),
-        e: 'new_status',
-        m: 'get hostComponent status'
-      }
-    ]);
-
-    tests.forEach(function(test) {
-      it(test.m, function() {
-        hostComponentView = App.HostComponentView.create({
-          startBlinking: function(){},
-          doBlinking: function(){},
-          getDesiredAdminState: function(){return $.ajax({});},
-          hostComponent: test.hostComponent,
-          content: Em.Object.create()
-        });
-        hostComponentView.get('content').set('componentTextStatus', test.componentTextStatus);
-        expect(hostComponentView.get('componentTextStatus')).to.equal(test.e);
-      });
-    });
-
-  });
-
-  describe('#workStatus', function() {
-
-    var tests = Em.A([
-      {
-        workStatus: 'status',
-        hostComponent: null,
-        e: 'status',
-        m: 'get content workStatus'
-      },
-      {
-        workStatus: 'status',
-        hostComponent: Em.Object.create({workStatus: 'new_status'}),
-        e: 'new_status',
-        m: 'get hostComponent workStatus'
-      }
-    ]);
-
-    tests.forEach(function(test) {
-      it(test.m, function() {
-        hostComponentView = App.HostComponentView.create({
-          startBlinking: function(){},
-          doBlinking: function(){},
-          getDesiredAdminState: function(){return $.ajax({});},
-          hostComponent: test.hostComponent,
-          content: Em.Object.create()
-        });
-        hostComponentView.get('content').set('workStatus', test.workStatus);
-        expect(hostComponentView.get('workStatus')).to.equal(test.e);
-      });
-    });
-
-  });
-
   describe('#statusClass', function() {
 
     var tests = Em.A([
@@ -249,19 +185,25 @@ describe('App.HostComponentView', function() {
         workStatus: 'STARTED',
         passiveState: 'OFF',
         e: 'health-status-started'
+      },
+      {
+        workStatus: App.HostComponentStatus.stopped,
+        passiveState: 'OFF',
+        isClient: true,
+        e: 'health-status-started'
       }
     ]);
 
     tests.forEach(function(test) {
       it(test.workStatus + ' ' + test.passiveState, function() {
         hostComponentView = App.HostComponentView.create({
-          startBlinking: function(){},
-          doBlinking: function(){},
+          startBlinking: Em.K,
+          doBlinking: Em.K,
           getDesiredAdminState: function(){return $.ajax({});},
-          content: Em.Object.create(),
-          hostComponent: Em.Object.create()
+          content: Em.Object.create()
         });
-        hostComponentView.get('hostComponent').set('workStatus',test.workStatus);
+        hostComponentView.get('content').set('isClient', test.isClient);
+        hostComponentView.get('content').set('workStatus', test.workStatus);
         hostComponentView.get('content').set('passiveState', test.passiveState);
         expect(hostComponentView.get('statusClass')).to.equal(test.e);
       });
@@ -482,4 +424,32 @@ describe('App.HostComponentView', function() {
     });
   });
 
+  describe('#clientCustomCommands', function() {
+    beforeEach(function() {
+      sinon.stub(App.StackServiceComponent, 'find').returns(Em.Object.create({
+        customCommands: ['COMMAND1']
+      }));
+    });
+    afterEach(function() {
+      App.StackServiceComponent.find.restore();
+    });
+
+    it('should return list of commands', function() {
+      hostComponentView.set('content');
+      hostComponentView.propertyDidChange('clientCustomCommands');
+      expect(hostComponentView.get('clientCustomCommands')).to.be.eql([{
+        command: 'COMMAND1',
+        label: Em.I18n.t('services.service.actions.run.executeCustomCommand.menu').format('COMMAND1')
+      }]);
+    });
+  });
+
+  describe('#installClient', function() {
+
+    it('installClients should be called', function() {
+      hostComponentView.installClient();
+      expect(hostComponentView.get('controller').installClients.calledOnce).to.be.true;
+    });
+  });
+
 });
diff --git a/ambari-web/test/views/main/host/summary_test.js b/ambari-web/test/views/main/host/summary_test.js
index f1c9d06..07d3ee8 100644
--- a/ambari-web/test/views/main/host/summary_test.js
+++ b/ambari-web/test/views/main/host/summary_test.js
@@ -112,14 +112,14 @@ describe('App.MainHostSummaryView', function() {
       {
         content: Em.Object.create({
           hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'B'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'A'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'C'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'})
+            Em.Object.create({isClient: true, componentName: 'B'}),
+            Em.Object.create({isMaster: true, componentName: 'A'}),
+            Em.Object.create({isSlave: true, componentName: 'C'}),
+            Em.Object.create({isClient: true, componentName: 'D'})
           ])
         }),
         m: 'List of clients',
-        e: []
+        e: ['A', 'C', 'B', 'D']
       }
     ]);
 
@@ -137,151 +137,6 @@ describe('App.MainHostSummaryView', function() {
 
   });
 
-  describe('#clients', function() {
-
-    var tests = Em.A([
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'})
-          ])
-        }),
-        m: 'List of masters, slaves and clients',
-        e: ['D']
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'})
-          ])
-        }),
-        m: 'List of masters and slaves',
-        e: []
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'B'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}),
-            Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'})
-          ])
-        }),
-        m: 'List of masters',
-        e: []
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}),
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'A'}),
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'C'}),
-            Em.Object.create({isMaster: false, isSlave: true, componentName: 'D'})
-          ])
-        }),
-        m: 'List of slaves',
-        e: []
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([])
-        }),
-        m: 'Empty list',
-        e: []
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'B'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'A'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'C'}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'})
-          ])
-        }),
-        m: 'List of clients',
-        e: ['B', 'A', 'C', 'D']
-      }
-    ]);
-
-    tests.forEach(function(test) {
-      it(test.m, function() {
-        mainHostSummaryView.set('content', test.content);
-        expect(mainHostSummaryView.get('clients').mapProperty('componentName')).to.eql(test.e);
-      });
-    });
-
-    it('should set isInstallFailed for clients with INIT and INSTALL_FAILED workStatus', function() {
-      mainHostSummaryView.set('content', Em.Object.create({
-        hostComponents: [
-          Em.Object.create({isMaster: false, isSlave: false, componentName: 'B', workStatus: 'INIT'}),
-          Em.Object.create({isMaster: false, isSlave: false, componentName: 'A', workStatus: 'INSTALLED'}),
-          Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', workStatus: 'INSTALL_FAILED'}),
-          Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', workStatus: 'INSTALLING'})
-        ]
-      }));
-      expect(mainHostSummaryView.get('clients').filterProperty('isInstallFailed', true).mapProperty('componentName')).to.eql(['B', 'C']);
-      expect(mainHostSummaryView.get('clients').filterProperty('isInstallFailed', false).mapProperty('componentName')).to.eql(['A', 'D']);
-    });
-
-  });
-
-  describe('#areClientWithStaleConfigs', function() {
-
-    var tests = Em.A([
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: true}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: false})
-          ])
-        }),
-        m: 'Some clients with stale configs',
-        e: true
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: false}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: false})
-          ])
-        }),
-        m: 'No clients with stale configs',
-        e: false
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: true}),
-            Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: true})
-          ])
-        }),
-        m: 'All clients with stale configs',
-        e: true
-      },
-      {
-        content: Em.Object.create({
-          hostComponents: Em.A([])
-        }),
-        m: 'Empty list',
-        e: false
-      }
-    ]);
-
-    tests.forEach(function(test) {
-      it(test.m, function() {
-        mainHostSummaryView.set('content', test.content);
-        expect(mainHostSummaryView.get('areClientWithStaleConfigs')).to.equal(test.e);
-      });
-    });
-
-  });
-
   describe('#isAddComponent', function() {
 
     var tests = Em.A([
@@ -368,104 +223,6 @@ describe('App.MainHostSummaryView', function() {
     });
   });
 
-  describe('#areClientsNotInstalled', function () {
-
-    var cases = [
-      {
-        clients: [
-          {
-            isInstallFailed: true
-          }
-        ],
-        installableClientComponents: [],
-        areClientsNotInstalled: true,
-        title: 'some clients failed to install, no clients to add'
-      },
-      {
-        clients: [
-          {
-            isInstallFailed: false
-          }
-        ],
-        installableClientComponents: [{}],
-        areClientsNotInstalled: true,
-        title: 'no clients failed to install, some clients to add'
-      },
-      {
-        clients: [
-          {
-            isInstallFailed: true
-          }
-        ],
-        installableClientComponents: [{}],
-        areClientsNotInstalled: true,
-        title: 'some clients failed to install, some clients to add'
-      },
-      {
-        clients: [
-          {
-            isInstallFailed: false
-          }
-        ],
-        installableClientComponents: [],
-        areClientsNotInstalled: false,
-        title: 'no clients failed to install, no clients to add'
-      }
-    ];
-
-    cases.forEach(function (item) {
-      it(item.title, function () {
-        mainHostSummaryView.reopen({
-          clients: item.clients,
-          installableClientComponents: item.installableClientComponents
-        });
-        expect(mainHostSummaryView.get('areClientsNotInstalled')).to.equal(item.areClientsNotInstalled);
-      });
-    });
-
-  });
-
-  describe('#notInstalledClientComponents', function () {
-
-    it('should concat not added clients and the ones that failed to install', function () {
-      mainHostSummaryView.reopen({
-        clients: [
-          Em.Object.create({
-            componentName: 'c0',
-            workStatus: 'INIT'
-          }),
-          Em.Object.create({
-            componentName: 'c1',
-            workStatus: 'INSTALL_FAILED'
-          }),
-          Em.Object.create({
-            componentName: 'c2',
-            workStatus: 'INSTALLED'
-          })
-        ],
-        installableClientComponents: [
-          Em.Object.create({
-            componentName: 'c3'
-          })
-        ]
-      });
-      expect(mainHostSummaryView.get('notInstalledClientComponents')).to.eql([
-        Em.Object.create({
-          componentName: 'c0',
-          workStatus: 'INIT'
-        }),
-        Em.Object.create({
-          componentName: 'c1',
-          workStatus: 'INSTALL_FAILED'
-        }),
-        Em.Object.create({
-          componentName: 'c3'
-        })
-      ]);
-    });
-
-  });
-
   describe("#needToRestartMessage", function() {
 
     it("one component", function() {
@@ -577,43 +334,6 @@ describe('App.MainHostSummaryView', function() {
 
   });
 
-  describe("#installableClientComponents", function() {
-
-    beforeEach(function() {
-      sinon.stub(App.StackServiceComponent, 'find').returns([
-        Em.Object.create({
-          isClient: true,
-          serviceName: 'S1',
-          componentName: 'C1'
-        }),
-        Em.Object.create({
-          isClient: true,
-          serviceName: 'S1',
-          componentName: 'C2'
-        }),
-        Em.Object.create({
-          isClient: true,
-          serviceName: 'S2',
-          componentName: 'C1'
-        })
-      ]);
-    });
-    afterEach(function() {
-      App.StackServiceComponent.find.restore();
-    });
-
-    it("should return installable client components", function() {
-      mainHostSummaryView.reopen({
-        installedServices: ['S1'],
-        clients: [
-          Em.Object.create({componentName: 'C2'})
-        ]
-      });
-      mainHostSummaryView.propertyDidChange('installableClientComponents');
-      expect(mainHostSummaryView.get('installableClientComponents').mapProperty('componentName')).to.eql(['C1']);
-    });
-  });
-
   describe("#hasCardinalityConflict()", function () {
 
     beforeEach(function() {
@@ -657,44 +377,6 @@ describe('App.MainHostSummaryView', function() {
     });
   });
 
-  describe("#installClients()", function () {
-
-    beforeEach(function () {
-      var controller = {installClients: Em.K};
-      sinon.spy(controller, 'installClients');
-      mainHostSummaryView.set('controller', controller);
-      mainHostSummaryView.reopen({'notInstalledClientComponents': [1,2,3]});
-    });
-
-    afterEach(function () {
-      mainHostSummaryView.get('controller.installClients').restore();
-    });
-
-    it("should call installClients method from controller", function () {
-      mainHostSummaryView.installClients();
-      expect(mainHostSummaryView.get('controller.installClients').calledWith([1,2,3])).to.be.true;
-    });
-  });
-
-  describe("#reinstallClients()", function () {
-
-    beforeEach(function () {
-      var controller = {installClients: Em.K};
-      sinon.spy(controller, 'installClients');
-      mainHostSummaryView.set('controller', controller);
-      mainHostSummaryView.reopen({'installFailedClients': [1,2,3]});
-    });
-
-    afterEach(function () {
-      mainHostSummaryView.get('controller.installClients').restore();
-    });
-
-    it("should call installClients method from controller", function () {
-      mainHostSummaryView.reinstallClients();
-      expect(mainHostSummaryView.get('controller.installClients').calledWith([1,2,3])).to.be.true;
-    });
-  });
-
   describe("#timeSinceHeartBeat", function () {
 
     beforeEach(function() {
@@ -717,72 +399,4 @@ describe('App.MainHostSummaryView', function() {
       expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.equal('1');
     });
   });
-
-  describe("#clientsWithCustomCommands", function () {
-
-    beforeEach(function() {
-      this.mockComponents = sinon.stub(App.StackServiceComponent, 'find');
-    });
-
-    afterEach(function() {
-      this.mockComponents.restore();
-    });
-
-    var testCases = [
-      {
-        component: Em.Object.create(),
-        clients: [],
-        expected: []
-      },
-      {
-        component: Em.Object.create(),
-        clients: [
-          Em.Object.create({componentName: 'KERBEROS_CLIENT'})
-        ],
-        expected: []
-      },
-      {
-        component: Em.Object.create({customCommands: []}),
-        clients: [
-          Em.Object.create({componentName: 'C1'})
-        ],
-        expected: []
-      },
-      {
-        component: Em.Object.create({customCommands: ['cmd1']}),
-        clients: [
-          Em.Object.create({
-            hostName: 'host1',
-            displayName: 'dn1',
-            componentName: 'C1',
-            service: Em.Object.create({serviceName: 'S1'})
-          })
-        ],
-        expected: [{
-          label: 'dn1',
-          commands: [
-            {
-              label: Em.I18n.t('services.service.actions.run.executeCustomCommand.menu').format('cmd1'),
-              service: "S1",
-              hosts: 'host1',
-              component: 'C1',
-              command: 'cmd1'
-            }
-          ]
-        }]
-      }
-    ];
-
-    testCases.forEach(function(test) {
-      it("component = " + JSON.stringify(test.component) +
-         " clients = " + JSON.stringify(test.clients), function() {
-        this.mockComponents.returns(test.component);
-        mainHostSummaryView.reopen({
-          clients: test.clients
-        });
-        mainHostSummaryView.propertyDidChange('clientsWithCustomCommands');
-        expect(mainHostSummaryView.get('clientsWithCustomCommands')).to.be.eql(test.expected);
-      });
-    });
-  });
 });

-- 
To stop receiving notification emails like this one, please contact
atkach@apache.org.

Mime
View raw message