You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by at...@apache.org on 2014/11/13 12:20:31 UTC

ambari git commit: AMBARI-8304 Hosts: Add Stack versions column to Hosts table. (atkach)

Repository: ambari
Updated Branches:
  refs/heads/trunk 16b84e77f -> f327b0a23


AMBARI-8304 Hosts: Add Stack versions column to Hosts table. (atkach)


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

Branch: refs/heads/trunk
Commit: f327b0a2350b33e8999d440e15aec003b1fade94
Parents: 16b84e7
Author: Andrii Tkach <at...@hortonworks.com>
Authored: Thu Nov 13 13:20:24 2014 +0200
Committer: Andrii Tkach <at...@hortonworks.com>
Committed: Thu Nov 13 13:20:24 2014 +0200

----------------------------------------------------------------------
 .../app/assets/data/hosts/HDP2/hosts.json       |  37 +++++++
 ambari-web/app/controllers/main/host.js         |   1 +
 ambari-web/app/mappers/hosts_mapper.js          |  33 +++++-
 ambari-web/app/messages.js                      |   4 +
 ambari-web/app/models/host.js                   |   5 +
 ambari-web/app/models/host_stack_version.js     |  67 ++++++------
 ambari-web/app/styles/application.less          |  44 +++++---
 ambari-web/app/templates/main/host.hbs          |  21 +++-
 .../app/templates/main/host/stack_versions.hbs  |   6 +-
 ambari-web/app/templates/main/host/summary.hbs  |   5 +-
 .../app/templates/main/host/version_filter.hbs  |  30 ++++++
 ambari-web/app/views/common/filter_view.js      |  14 ++-
 ambari-web/app/views/main/host.js               | 108 ++++++++++++++++---
 .../app/views/main/host/stack_versions_view.js  |  12 +--
 .../test/views/common/filter_view_test.js       |  77 +++++++++++++
 15 files changed, 387 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/assets/data/hosts/HDP2/hosts.json
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/data/hosts/HDP2/hosts.json b/ambari-web/app/assets/data/hosts/HDP2/hosts.json
index 0e2e3fc..4275ee2 100644
--- a/ambari-web/app/assets/data/hosts/HDP2/hosts.json
+++ b/ambari-web/app/assets/data/hosts/HDP2/hosts.json
@@ -13,6 +13,43 @@
         "public_host_name" : "dev01.hortonworks.com",
         "total_mem" : 4055304
       },
+      "stack_versions": [
+        {
+          "StackVersion": {
+            "name": "HDP-2.2",
+            "version": "2.2.0.1-885",
+            "state": "INIT"
+          }
+        },
+        {
+          "StackVersion":  {
+            "name": "HDP-2.2",
+            "version": "2.2.1.1-885",
+            "state": "INSTALLED"
+          }
+        },
+        {
+          "StackVersion": {
+            "name": "HDP-2.2",
+            "version": "2.2.2.1-885",
+            "state": "INSTALL_FAILED"
+          }
+        },
+        {
+          "StackVersion":  {
+            "name": "HDP-2.3",
+            "version": "2.3.0.1-885",
+            "state": "INSTALLING"
+          }
+        },
+        {
+          "StackVersion": {
+            "name": "HDP-2.3",
+            "version": "2.3.1.1-885",
+            "state": "CURRENT"
+          }
+        }
+      ],
       "host_components" : [
         {
           "HostRoles" : {

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/controllers/main/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/host.js b/ambari-web/app/controllers/main/host.js
index b331c86..b2daba0 100644
--- a/ambari-web/app/controllers/main/host.js
+++ b/ambari-web/app/controllers/main/host.js
@@ -975,6 +975,7 @@ App.MainHostController = Em.ArrayController.extend({
     associations[8] = 'componentsWithStaleConfigsCount';
     associations[9] = 'componentsInPassiveStateCount';
     associations[10] = 'selected';
+    associations[11] = 'stackVersions';
     return associations;
   }.property()
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/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 4aa1154..4f2f0a1 100644
--- a/ambari-web/app/mappers/hosts_mapper.js
+++ b/ambari-web/app/mappers/hosts_mapper.js
@@ -64,6 +64,15 @@ App.hostsMapper = App.QuickDataMapper.create({
     host_name: 'host_name',
     admin_state: 'HostRoles.desired_admin_state'
   },
+  stackVersionConfig: {
+    id: 'id',
+    stack_id: 'stack_id',
+    stack_name: 'StackVersion.name',
+    version: 'StackVersion.version',
+    status: 'StackVersion.state',
+    host_name: 'host_name',
+    host_id: 'host_name'
+  },
   map: function (json, returnMapped) {
     returnMapped = !!returnMapped;
     console.time('App.hostsMapper execution time');
@@ -71,6 +80,7 @@ App.hostsMapper = App.QuickDataMapper.create({
       var hostsWithFullInfo = [];
       var hostIds = {};
       var components = [];
+      var stackVersions = [];
       var componentsIdMap = {};
       var cacheServices = App.cache['services'];
       var loadedServiceComponentsMap = App.get('componentConfigMapper').buildServiceComponentMap(cacheServices);
@@ -93,11 +103,29 @@ App.hostsMapper = App.QuickDataMapper.create({
           }
           serviceToHostComponentIdMap[serviceName].push(component.id);
         }, this);
+
+        if (App.get('supports.stackUpgrade')) {
+          item.stack_versions.forEach(function (stackVersion) {
+            stackVersion.id = stackVersion.StackVersion.name + "_" + stackVersion.StackVersion.version + "_" + item.Hosts.host_name;
+            stackVersion.host_name = item.Hosts.host_name;
+            stackVersion.stack_id = stackVersion.StackVersion.name + stackVersion.StackVersion.version;
+            stackVersions.push(this.parseIt(stackVersion, this.stackVersionConfig));
+          }, this);
+        }
+
         item.critical_alerts_count = (item.legacy_alerts) ? item.legacy_alerts.summary.CRITICAL + item.legacy_alerts.summary.WARNING : 0;
         item.cluster_id = App.get('clusterName');
         item.index = index;
 
-
+        if (App.get('supports.stackUpgrade')) {
+          this.config = $.extend(this.config, {
+            stack_versions_key: 'stack_versions',
+            stack_versions_type: 'array',
+            stack_versions: {
+              item: 'id'
+            }
+          })
+        }
         var parsedItem = this.parseIt(item, this.config);
         parsedItem.is_requested = true;
 
@@ -120,6 +148,9 @@ App.hostsMapper = App.QuickDataMapper.create({
         if (componentsIdMap[component.get('id')]) componentsIdMap[component.get('id')].display_name_advanced = component.get('displayNameAdvanced');
       });
       App.store.commit();
+      if (App.get('supports.stackUpgrade')) {
+        App.store.loadMany(App.HostStackVersion, stackVersions);
+      }
       App.store.loadMany(App.HostComponent, components);
       App.store.loadMany(App.Host, hostsWithFullInfo);
       var itemTotal = parseInt(json.itemTotal);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index f795909..409df42 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -90,6 +90,7 @@ Em.I18n.translations = {
   'common.failed':'Failed',
   'common.service': 'Service',
   'common.version':'Version',
+  'common.versions':'Versions',
   'common.description':'Description',
   'common.default':'Default',
   'common.client':'Client',
@@ -1728,10 +1729,12 @@ Em.I18n.translations = {
   'hosts.host.menu.stackVersions': 'Versions',
   'hosts.host.stackVersions.table.noVersions': 'No versions',
   'hosts.host.stackVersions.table.filteredInfo': '{0} of {1} versions showing',
+  'hosts.host.stackVersions.table.labels': '{0} ({1})',
   'hosts.host.stackVersions.status.init': 'Uninstalled',
   'hosts.host.stackVersions.status.installed': 'Installed',
   'hosts.host.stackVersions.status.install_failed': 'Install Failed',
   'hosts.host.stackVersions.status.installing': 'Installing',
+  'hosts.host.stackVersions.status.current': 'Current',
 
   'hosts.host.metrics.dataUnavailable':'Data Unavailable',
   'hosts.host.metrics.cpu':'CPU Usage',
@@ -1768,6 +1771,7 @@ Em.I18n.translations = {
   'hosts.host.summary.agentHeartbeat':'Heartbeat',
   'hosts.host.summary.hostMetrics':'Host Metrics',
   'hosts.host.summary.addComponent':'Add Component',
+  'hosts.host.summary.currentVersion':'Current Version',
 
   'hosts.host.details.hostActions':'Host Actions',
   'hosts.host.details.needToRestart':'Host needs {0} {1} restarted',

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/models/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/host.js b/ambari-web/app/models/host.js
index f59c97b..9f7aa28 100644
--- a/ambari-web/app/models/host.js
+++ b/ambari-web/app/models/host.js
@@ -47,6 +47,7 @@ App.Host = DS.Model.extend({
   criticalAlertsCount: DS.attr('number'),
   passiveState: DS.attr('string'),
   index: DS.attr('number'),
+  stackVersions: DS.hasMany('App.HostStackVersion'),
 
   /**
    * Is host checked at the main Hosts page
@@ -57,6 +58,10 @@ App.Host = DS.Model.extend({
    */
   isRequested: DS.attr('boolean'),
 
+  currentVersion: function () {
+    return this.get('stackVersions').findProperty('isCurrent').get('version');
+  }.property('stackVersions.@each.isCurrent'),
+
   /**
    * Overall CPU usage (system and user)
    * @returns {Number}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/models/host_stack_version.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/host_stack_version.js b/ambari-web/app/models/host_stack_version.js
index c170c53..250a24a 100644
--- a/ambari-web/app/models/host_stack_version.js
+++ b/ambari-web/app/models/host_stack_version.js
@@ -19,21 +19,20 @@
 var App = require('app');
 
 App.HostStackVersion = DS.Model.extend({
-  stack: DS.attr('string'),
+  stackName: DS.attr('string'),
+  stack: DS.belongsTo('App.StackVersion'),
   version: DS.attr('string'),
   /**
-   * property can have next values:
-   *  - INSTALLED
-   *  - INSTALLING
-   *  - INSTALL_FAILED
-   *  - INIT
+   * possible property value defined at App.HostStackVersion.statusDefinition
    */
   status: DS.attr('string'),
-  isCurrent: DS.attr('boolean'),
+  host: DS.belongsTo('App.Host'),
+  hostName: DS.attr('string'),
+  isCurrent: function () {
+    return this.get('status') === 'CURRENT'
+  }.property('status'),
   displayStatus: function() {
-    return this.get('status') ?
-      Em.I18n.t('hosts.host.stackVersions.status.' + this.get('status').toLowerCase()) :
-      Em.I18n.t('common.unknown');
+    return App.HostStackVersion.formatStatus(this.get('status'));
   }.property('status'),
   installEnabled: function () {
     return (this.get('status') === 'INIT' || this.get('status') === 'INSTALL_FAILED');
@@ -41,29 +40,27 @@ App.HostStackVersion = DS.Model.extend({
   installDisabled: Ember.computed.not('installEnabled')
 });
 
-App.HostStackVersion.FIXTURES = [
-  {
-    stack: 'HDP-2.2',
-    version: 'HDP-2.2.2',
-    status: 'INIT',
-    isCurrent: false
-  },
-  {
-    stack: 'HDP-2.2',
-    version: 'HDP-2.2.1',
-    status: 'INSTALLED',
-    isCurrent: true
-  },
-  {
-    stack: 'HDP-2.2',
-    version: 'HDP-2.2.3',
-    status: 'INSTALLING',
-    isCurrent: false
-  },
-  {
-    stack: 'HDP-2.3',
-    version: 'HDP-2.3.0',
-    status: 'INSTALL_FAILED',
-    isCurrent: false
-  }
+App.HostStackVersion.FIXTURES = [];
+
+/**
+ * definition of possible statuses of Stack Version
+ * @type {Array}
+ */
+App.HostStackVersion.statusDefinition = [
+  "INSTALLED",
+  "INSTALLING",
+  "INSTALL_FAILED",
+  "INIT",
+  "CURRENT"
 ];
+
+/**
+ * translate status to label
+ * @param status
+ * @return {string}
+ */
+App.HostStackVersion.formatStatus = function (status) {
+  return status ?
+    Em.I18n.t('hosts.host.stackVersions.status.' + status.toLowerCase()) :
+    Em.I18n.t('common.unknown');
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index 6e15b97..8f27d1b 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -3499,12 +3499,15 @@ table.graphs {
     margin-bottom: 0;
   }
   .filter-input-width{
-    width:68%;
+    width:65%;
   }
   .table {
     input[type="checkbox"] {
       margin: -2px 0 0 0;
     }
+    th {
+      padding-left: 6px;
+    }
     .col0,.col1,
     td:first-child,
     th:first-child,
@@ -3519,12 +3522,11 @@ table.graphs {
     .col2,
     td:first-child + td + td,
     th:first-child + th + th{
-      width: 30%;
-      padding-left: 6px;
+      width: 20%;
       padding-right: 1px;
 
       .filter-input-width{
-        width:90%;
+        width:85%;
       }
     }
     .col3, .col4,
@@ -3540,35 +3542,41 @@ table.graphs {
     .col5,
     td:first-child + td + td + td + td + td,
     th:first-child + th + th + th + th + th {
-      width: 11%!important;
+      width: 10%!important;
     }
     .col6,
     td:first-child + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th {
-      width: 15%!important;
+      width: 11%!important;
     }
     .col7,
     td:first-child + td + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th + th{
-      width: 11%!important;
+      width: 8%!important;
     }
 
     .col8,
     td:first-child + td + td + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th + th + th{
-      width: 11%!important;
+      width: 10%!important;
     }
 
     .col9,
     td:first-child + td + td + td + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th + th + th + th{
-      width: 11%!important;
+      width: 10%!important;
     }
 
     .col10,
     td:first-child + td + td + td + td + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th + th + th + th + th{
-      width: 16%!important;
+      width: 13%!important;
+    }
+
+    .col11,
+    td:first-child + td + td + td + td + td + td + td + td + td + td + td,
+    th:first-child + th + th + th + th + th + th + th + th + th + th +th{
+      width: 13%!important;
     }
 
     td.name {
@@ -3605,6 +3613,9 @@ table.graphs {
       label {
         font-size: 12px;
       }
+      li.active-filter {
+        background-color: #777777;
+      }
       li {
         display: block;
         padding: 3px 0 3px 5px;
@@ -3650,6 +3661,9 @@ table.graphs {
           font-size: 12px;
         }
       }
+      select {
+        width: 150px;
+      }
       ul {
         margin-left: 10px;
       }
@@ -3720,7 +3734,7 @@ table.graphs {
       }
     }
   }
-  .host-components-expander {
+  .expander {
     .icon-caret-right, .icon-caret-down {
       vertical-align: middle;
       margin-right: 5px;
@@ -3728,9 +3742,9 @@ table.graphs {
       text-decoration: none;
     }
   }
-  .host-components {
+  .collapsed-list {
     display: none;
-    padding-left: 13px;
+    padding-left: 10px;
   }
 }
 
@@ -5671,10 +5685,10 @@ ul.inline li {
       .col2,
       td:first-child + td + td,
       th:first-child + th + th {
-        width: 25%!important;
+        width: 15%!important;
 
         .filter-input-width{
-          width:80%;
+          width:75%;
         }
       }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/templates/main/host.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host.hbs b/ambari-web/app/templates/main/host.hbs
index 1c7fd5a..a9be8d8 100644
--- a/ambari-web/app/templates/main/host.hbs
+++ b/ambari-web/app/templates/main/host.hbs
@@ -68,6 +68,9 @@
         {{view view.parentView.memorySort}}
         {{view view.parentView.diskUsageSort}}
         {{view view.parentView.loadAvgSort}}
+        <th {{bindAttr class="App.supports.stackUpgrade::hidden :sort-view-11"}}>
+          {{t hosts.host.menu.stackVersions}}
+        </th>
         <th class="sort-view-6">{{t common.components}}</th>
       {{/view}}
       <tr class="filter-row">
@@ -81,6 +84,9 @@
         <th>{{view view.ramFilterView}}</th>
         <th> </th>
         <th>{{view view.loadAvgFilterView}}</th>
+        <th {{bindAttr class="App.supports.stackUpgrade::hidden"}}>
+          {{view view.versionsFilterView}}
+        </th>
         <th>{{view view.componentsFilterView}}</th>
       </tr>
     </thead>
@@ -122,10 +128,19 @@
           </td>
 
           <td>{{host.loadAvg}}</td>
+          <td {{bindAttr class="App.supports.stackUpgrade::hidden"}}>
+            <a href="#" class="expander" {{action toggleVersions target="view"}}>
+              <span {{bindAttr class="view.isVersionsCollapsed:icon-caret-right:icon-caret-down"}}></span>
+              {{view.content.stackVersions.length}} {{pluralize view.content.stackVersions.length singular="t:common.version" plural="t:common.versions"}}
+            </a>
+            <div class="stack-versions collapsed-list">
+              {{{view.versionlabels}}}
+            </div>
+          </td>
           <td>
-            <a href="#" class="host-components-expander" {{action toggleComponents target="view"}}> <span {{bindAttr class="view.isComponentsCollapsed:icon-caret-right:icon-caret-down"}}></span>
+            <a href="#" class="expander" {{action toggleComponents target="view"}}> <span {{bindAttr class="view.isComponentsCollapsed:icon-caret-right:icon-caret-down"}}></span>
               {{view.content.hostComponents.length}} {{pluralize view.content.hostComponents.length singular="t:common.component" plural="t:common.components"}}</a>
-            <div id="host-{{unbound host.hostName}}" class="host-components">
+            <div class="host-components collapsed-list">
               {{{view.labels}}}
             </div>
           </td>
@@ -134,7 +149,7 @@
     {{else}}
       <tr>
         <td class="first"> </td>
-        <td colspan="11">
+        <td colspan="12">
           {{t hosts.table.noHosts}}
         </td>
       </tr>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/templates/main/host/stack_versions.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/stack_versions.hbs b/ambari-web/app/templates/main/host/stack_versions.hbs
index 276bb40..32241a5 100644
--- a/ambari-web/app/templates/main/host/stack_versions.hbs
+++ b/ambari-web/app/templates/main/host/stack_versions.hbs
@@ -18,13 +18,13 @@
 <table class="table advanced-header-table table-bordered table-striped">
   <thead>
   {{#view view.sortView classNames="label-row" contentBinding="view.filteredContent"}}
-    {{view view.parentView.stackSort classNames="first"}}
+    {{view view.parentView.stackNameSort classNames="first"}}
     {{view view.parentView.versionSort}}
     {{view view.parentView.statusSort}}
     <th></th>
   {{/view}}
   <tr class="filter-row">
-    <th class="first">{{view view.stackFilterView}}</th>
+    <th class="first">{{view view.stackNameFilterView}}</th>
     <th>{{view view.versionFilterView}}</th>
     <th>{{view view.statusFilterView}}</th>
     <th> </th>
@@ -34,7 +34,7 @@
   {{#if view.pageContent}}
     {{#each view.pageContent}}
       <tr>
-        <td class="first">{{this.stack}}</td>
+        <td class="first">{{this.stackName}}</td>
         <td>{{this.version}}</td>
         <td>
           {{this.displayStatus}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/templates/main/host/summary.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/summary.hbs b/ambari-web/app/templates/main/host/summary.hbs
index 77e731c..bace511 100644
--- a/ambari-web/app/templates/main/host/summary.hbs
+++ b/ambari-web/app/templates/main/host/summary.hbs
@@ -91,7 +91,7 @@
                         </a>
                       </li>
                       <li>
-                        <a href="javscript:void(null)" {{bindAttr class="view.areClientsInstallFailed::disabled" }} data-toggle="modal" {{action reinstallClients view.clients target="controller"}}>
+                        <a href="javascript:void(null)" {{bindAttr class="view.areClientsInstallFailed::disabled" }} data-toggle="modal" {{action reinstallClients view.clients target="controller"}}>
                           {{t host.host.details.installClients}}
                         </a>
                       </li>
@@ -135,6 +135,9 @@
                         <dt>{{t common.memory}}:</dt><dd>&nbsp;{{view.content.memoryFormatted}}</dd>
                         <dt>{{t common.loadAvg}}:</dt><dd>&nbsp;{{view.content.loadAvg}}</dd>
                         <dt>{{t hosts.host.summary.agentHeartbeat}}:</dt><dd>&nbsp;{{view.timeSinceHeartBeat}}</dd>
+                        {{#if App.supports.stackUpgrade}}
+                          <dt>{{t hosts.host.summary.currentVersion}}:</dt><dd>&nbsp;{{view.content.currentVersion}}</dd>
+                        {{/if}}
                     </dl>
                 </div>
             </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/templates/main/host/version_filter.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/version_filter.hbs b/ambari-web/app/templates/main/host/version_filter.hbs
new file mode 100644
index 0000000..151a52d
--- /dev/null
+++ b/ambari-web/app/templates/main/host/version_filter.hbs
@@ -0,0 +1,30 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+
+<button class="btn single-btn-group filter-btn" {{action "clickFilterButton" target="view"}}>
+  {{t common.filter}}
+  <i class="icon-filter"></i>
+</button>
+<ul class="dropdown-menu filter-components">
+  <li>{{view view.versionSelectView}}</li>
+  <li>{{view view.statusSelectView}}</li>
+  <li id="button-bar">
+    <button class="btn btn-inverse" {{action "applyFilter" target="view"}}>{{t common.apply}}</button>
+  </li>
+</ul>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/views/common/filter_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/filter_view.js b/ambari-web/app/views/common/filter_view.js
index 7b45873..be5d069 100644
--- a/ambari-web/app/views/common/filter_view.js
+++ b/ambari-web/app/views/common/filter_view.js
@@ -268,7 +268,7 @@ var componentFieldView = Ember.View.extend({
    */
   clickFilterButton: function () {
     var self = this;
-    this.set('isFilterOpen', !this.get('isFilterOpen'));
+    this.toggleProperty('isFilterOpen');
     if (this.get('isFilterOpen')) {
 
       var dropDown = this.$('.filter-components');
@@ -539,6 +539,18 @@ module.exports = {
           return match;
         };
         break;
+      case 'sub-resource':
+        return function (origin, compareValue) {
+          if (Ember.isNone(compareValue) || App.isEmptyObject(compareValue)) return true;
+
+          return origin.some(function (item) {
+            for (var i in compareValue) {
+              if(item.get(i) !== compareValue[i]) return false
+            }
+            return true;
+          });
+        }
+        break;
       case 'multiple':
         return function (origin, compareValue) {
           var options = compareValue.split(',');

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/views/main/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host.js b/ambari-web/app/views/main/host.js
index 48fe603..d3f114f 100644
--- a/ambari-web/app/views/main/host.js
+++ b/ambari-web/app/views/main/host.js
@@ -63,6 +63,11 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     return [];
   }.property('controller.content'),
 
+  stackVersions: function () {
+    //TODO obtain versions from model App.StackVersion
+    return App.HostStackVersion.find().mapProperty('version').uniq();
+  }.property(),
+
   onRequestErrorHandler: function() {
     this.set('requestError', null);
     this.get('controller').get('dataSource').setEach('isRequested', false);
@@ -549,13 +554,19 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     didInsertElement: function(){
       App.tooltip(this.$("[rel='HealthTooltip'], [rel='UsageTooltip'], [rel='ComponentsTooltip']"));
       this.set('isComponentsCollapsed', true);
+      this.set('isVersionsCollapsed', true);
     },
 
     toggleComponents: function(event) {
-      this.set('isComponentsCollapsed', !this.get('isComponentsCollapsed'));
+      this.toggleProperty('isComponentsCollapsed');
       this.$('.host-components').toggle();
     },
 
+    toggleVersions: function(){
+      this.toggleProperty('isVersionsCollapsed');
+      this.$('.stack-versions').toggle();
+    },
+
     /**
      * Tooltip message for "Restart Required" icon
      * @returns {String}
@@ -594,6 +605,16 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     }.property('content.hostComponents.length'),
 
     /**
+     * String with list of host components <code>displayName</code>
+     * @returns {String}
+     */
+    versionlabels: function () {
+      return this.get('content.stackVersions').map(function (version) {
+        return Em.I18n.t('hosts.host.stackVersions.table.labels').format(version.get('version'), version.get('displayStatus'));
+      }).join("<br />");
+    }.property('content.stackVersions.length'),
+
+    /**
      * CSS value for disk usage bar
      * @returns {String}
      */
@@ -820,28 +841,28 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
   }),
 
   /**
-   * Filter view for LoadAverage column
+   * Filter view for Ram column
    * Based on <code>filters</code> library
    */
-  loadAvgFilterView: filters.createTextView({
+  ramFilterView: filters.createTextView({
     fieldType: 'filter-input-width',
-    fieldId: 'load_avg_filter',
-    column: 5,
-    onChangeValue: function(){
-      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'number');
+    fieldId: 'ram_filter',
+    column: 4,
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'ambari-bandwidth');
     }
   }),
 
   /**
-   * Filter view for Ram column
+   * Filter view for LoadAverage column
    * Based on <code>filters</code> library
    */
-  ramFilterView: filters.createTextView({
+  loadAvgFilterView: filters.createTextView({
     fieldType: 'filter-input-width',
-    fieldId: 'ram_filter',
-    column: 4,
+    fieldId: 'load_avg_filter',
+    column: 5,
     onChangeValue: function(){
-      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'ambari-bandwidth');
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'number');
     }
   }),
 
@@ -991,6 +1012,69 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     }
   }),
 
+  versionsFilterView: filters.wrapperView.extend({
+    column: 11,
+    filterView: filters.componentFieldView.extend({
+      templateName: require('templates/main/host/version_filter'),
+      selectedVersion: null,
+      selecteStatus: null,
+      value: {},
+
+      versionSelectView: filters.createSelectView({
+        fieldType: 'filter-input-width',
+        content: function () {
+          return this.get('parentView.parentView.parentView.stackVersions').map(function (version) {
+            return {
+              value: version,
+              label: version
+            }
+          });
+        }.property('App.router.clusterController.isLoaded'),
+        onChangeValue: function () {
+          this.set('parentView.selectedVersion', this.get('value'));
+        }
+      }),
+      statusSelectView: filters.createSelectView({
+        fieldType: 'filter-input-width',
+        content: function () {
+          return App.HostStackVersion.statusDefinition.map(function (status) {
+            return {
+              value: status,
+              label: App.HostStackVersion.formatStatus(status)
+            }
+          });
+        }.property('App.router.clusterController.isLoaded'),
+        onChangeValue: function () {
+          this.set('parentView.selectedStatus', this.get('value'));
+        }
+      }),
+      /**
+       * Onclick handler for <code>Apply filter</code> button
+       */
+      applyFilter: function () {
+        this._super();
+        var self = this;
+        var filterProperties = {};
+        if (this.get('selectedVersion')) {
+          filterProperties['version'] = this.get('selectedVersion');
+        }
+        if (this.get('selectedStatus')) {
+          filterProperties['status'] = this.get('selectedStatus');
+        }
+        self.set('value', filterProperties);
+      },
+      /**
+       * Clear filter to initial state
+       */
+      clearFilter: function () {
+        this.set('value', {});
+      }
+    }),
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'sub-resource');
+    }
+  }),
+
   /**
    * associations between host property and column index
    * @type {Array}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/app/views/main/host/stack_versions_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/stack_versions_view.js b/ambari-web/app/views/main/host/stack_versions_view.js
index 63151e0..8b004cb 100644
--- a/ambari-web/app/views/main/host/stack_versions_view.js
+++ b/ambari-web/app/views/main/host/stack_versions_view.js
@@ -41,9 +41,9 @@ App.MainHostStackVersionsView = App.TableView.extend({
   }.property('filteredCount', 'totalCount'),
 
   sortView: sort.wrapperView,
-  stackSort: sort.fieldView.extend({
+  stackNameSort: sort.fieldView.extend({
     column: 1,
-    name: 'stack',
+    name: 'stackName',
     displayName: Em.I18n.t('common.stack'),
     type: 'version'
   }),
@@ -60,10 +60,10 @@ App.MainHostStackVersionsView = App.TableView.extend({
   }),
 
   /**
-   * Filter view for stack column
+   * Filter view for stackName column
    * Based on <code>filters</code> library
    */
-  stackFilterView: filters.createSelectView({
+  stackNameFilterView: filters.createSelectView({
     column: 1,
     fieldType: 'filter-input-width',
     content: function () {
@@ -72,7 +72,7 @@ App.MainHostStackVersionsView = App.TableView.extend({
           value: '',
           label: Em.I18n.t('common.all')
         }
-      ].concat(this.get('parentView.content').mapProperty('stack').uniq().map(function (item) {
+      ].concat(this.get('parentView.content').mapProperty('stackName').uniq().map(function (item) {
         return {
           value: item,
           label: item
@@ -147,7 +147,7 @@ App.MainHostStackVersionsView = App.TableView.extend({
 
   colPropAssoc: function () {
     var associations = [];
-    associations[1] = 'stack';
+    associations[1] = 'stackName';
     associations[2] = 'version';
     associations[3] = 'status';
     return associations;

http://git-wip-us.apache.org/repos/asf/ambari/blob/f327b0a2/ambari-web/test/views/common/filter_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/filter_view_test.js b/ambari-web/test/views/common/filter_view_test.js
index 71c3eba..aa077bb 100644
--- a/ambari-web/test/views/common/filter_view_test.js
+++ b/ambari-web/test/views/common/filter_view_test.js
@@ -18,6 +18,7 @@
 
 var App = require('app');
 var filters = require('views/common/filter_view');
+require('utils/helper');
 
 describe('filters.getFilterByType', function () {
 
@@ -388,4 +389,80 @@ describe('filters.getFilterByType', function () {
       })
     });
   });
+
+  describe('sub-resource', function () {
+
+    var filter = filters.getFilterByType('sub-resource');
+
+    var testData = [
+      {
+        title: 'condition is null',
+        condition: null,
+        value: [Em.Object.create({
+          prop1: 1
+        })],
+        result: true
+      },
+      {
+        title: 'condition is empty',
+        condition: {},
+        value: [Em.Object.create({
+          prop1: 1
+        })],
+        result: true
+      },
+      {
+        title: 'condition match one property',
+        condition: {
+          prop1: 1
+        },
+        value: [Em.Object.create({
+          prop1: 1
+        })],
+        result: true
+      },
+      {
+        title: 'condition match two properties',
+        condition: {
+          prop1: 1,
+          prop2: 2
+        },
+        value: [Em.Object.create({
+          prop1: 1,
+          prop2: 2
+        })],
+        result: true
+      },
+      {
+        title: 'only one of two properties match',
+        condition: {
+          prop1: 3,
+          prop2: 2
+        },
+        value: [Em.Object.create({
+          prop1: 1,
+          prop2: 2
+        })],
+        result: false
+      },
+      {
+        title: 'none of two properties match',
+        condition: {
+          prop1: 3,
+          prop2: 4
+        },
+        value: [Em.Object.create({
+          prop1: 1,
+          prop2: 2
+        })],
+        result: false
+      }
+    ];
+
+    testData.forEach(function (test) {
+      it(test.title, function () {
+        expect(filter(test.value, test.condition)).to.equal(test.result);
+      })
+    });
+  });
 });