You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by on...@apache.org on 2014/01/06 14:18:47 UTC

[3/5] AMBARI-4227. New ability to filter hosts based on "Maintenance" status. (onechiporenko)

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/assets/font/fontawesome-webfont.woff
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/font/fontawesome-webfont.woff b/ambari-web/app/assets/font/fontawesome-webfont.woff
index 09f2469..b9bd17e 100644
Binary files a/ambari-web/app/assets/font/fontawesome-webfont.woff and b/ambari-web/app/assets/font/fontawesome-webfont.woff differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/assets/licenses/NOTICE.txt
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/licenses/NOTICE.txt b/ambari-web/app/assets/licenses/NOTICE.txt
index 7a464dc..d2aa8f8 100644
--- a/ambari-web/app/assets/licenses/NOTICE.txt
+++ b/ambari-web/app/assets/licenses/NOTICE.txt
@@ -30,9 +30,10 @@ Copyright (c) 2012, Michael Bostock.
 This product includes bootstrap-datepicker.js (http://www.eyecon.ro/bootstrap-datepicker - Apache License, Version 2.0)
 Copyright (c) 2012 Stefan Petre
 
-This product includes Font Awesome 2.0 (http://fortawesome.github.com/Font-Awesome - Creative Commons 3.0)
+This product includes Font Awesome 3.2.1 (http://fortawesome.github.com/Font-Awesome - Creative Commons 3.0)
+Copyright (c) 2013 Dave Gandy
 
-This product incudes Rickshaw 1.1.2 (http://code.shutterstock.com/rickshaw/ - MIT License)
+This product includes Rickshaw 1.1.2 (http://code.shutterstock.com/rickshaw/ - MIT License)
 Copyright (C) 2011 by Shutterstock Images, LLC
 
 This product includes Timeago (http://timeago.yarp.com/ - MIT License)

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/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 c037e3b..65f9981 100644
--- a/ambari-web/app/controllers/main/host.js
+++ b/ambari-web/app/controllers/main/host.js
@@ -32,6 +32,7 @@ App.MainHostController = Em.ArrayController.extend({
 
   /**
    * Components which will be shown in component filter
+   * @returns {Array}
    */
   componentsForFilter:function() {
     var installedComponents = componentHelper.getInstalledComponents();
@@ -39,21 +40,33 @@ App.MainHostController = Em.ArrayController.extend({
     return installedComponents;
   }.property('App.router.clusterController.isLoaded'),
 
+  /**
+   * Master components
+   * @returns {Array}
+   */
   masterComponents:function () {
     return this.get('componentsForFilter').filterProperty('isMaster', true);
   }.property('componentsForFilter'),
 
+  /**
+   * Slave components
+   * @returns {Array}
+   */
   slaveComponents:function () {
     return this.get('componentsForFilter').filterProperty('isSlave', true);
   }.property('componentsForFilter'),
 
+  /**
+   * Client components
+   * @returns {Array}
+   */
   clientComponents: function() {
     return this.get('componentsForFilter').filterProperty('isClient', true);
   }.property('componentsForFilter'),
 
   /**
    * Filter hosts by componentName of <code>component</code>
-   * @param component App.HostComponent
+   * @param {App.HostComponent} component
    */
   filterByComponent:function (component) {
     if(!component)
@@ -99,9 +112,6 @@ App.MainHostController = Em.ArrayController.extend({
         templateName: require('templates/main/host/alerts_popup')
       }),
       primary: Em.I18n.t('common.close'),
-      onPrimary: function() {
-        this.hide();
-      },
       secondary : null,
       didInsertElement: function () {
         this.$().find('.modal-footer').addClass('align-center');
@@ -125,7 +135,7 @@ App.MainHostController = Em.ArrayController.extend({
 
   /**
    * remove hosts with id equal host_id
-   * @param host_id
+   * @param {String} host_id
    */
   checkRemoved:function (host_id) {
     var hosts = this.get('content');

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 532a1eb..8ded97d 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -164,6 +164,7 @@ Em.I18n.translations = {
   'common.restart': 'Restart',
   'common.discard': 'Discard',
   'common.actions': 'Actions',
+  'common.maintenance': 'Maintenance',
 
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
@@ -1303,6 +1304,9 @@ Em.I18n.translations = {
   'hosts.table.restartComponents.withNames':'Restart {0}',
   'hosts.table.restartComponents.withoutNames':'{0} components should be restarted',
 
+  'hosts.table.componentsInMaintenance.withNames':'{0} in maintenance mode',
+  'hosts.table.componentsInMaintenance.withoutNames':'{0} components in maintenance mode',
+
   'hosts.selectHostsDialog.title': 'Select Configuration Group Hosts',
   'hosts.selectHostsDialog.message': 'Select hosts that should belong to this {0} Configuration Group.  All hosts belonging to this group will have the same set of {0} configurations.',
   'hosts.selectHostsDialog.filter.placeHolder': 'Filter...',

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/models/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/host.js b/ambari-web/app/models/host.js
index 91fe812..c215b73 100644
--- a/ambari-web/app/models/host.js
+++ b/ambari-web/app/models/host.js
@@ -44,61 +44,104 @@ App.Host = DS.Model.extend({
   cpuSystem:DS.attr('number'),
   cpuUser:DS.attr('number'),
 
+  /**
+   * Overall CPU usage (system and user)
+   * @returns {Number}
+   */
   cpuUsage: function () {
-    if (this.get('cpuSystem') && this.get('cpu_user')) {
-      return this.get('cpuSystem') + this.get('cpu_user');
+    if (this.get('cpuSystem') && this.get('cpuUser')) {
+      return this.get('cpuSystem') + this.get('cpuUser');
     }
+    return 0;
   }.property('cpuSystem', 'cpuUser'),
 
+  /**
+   * Percent value of used memory
+   * @returns {Number}
+   */
   memoryUsage: function () {
     if (this.get('memFree') && this.get('memTotal')) {
       var memUsed = this.get('memTotal') - this.get('memFree');
       return (100 * memUsed) / this.get('memTotal');
     }
+    return 0;
   }.property('memTotal', 'memFree'),
 
+  /**
+   * Get count of critical alerts for current host
+   * @returns {Number}
+   */
   criticalAlertsCount: function () {
     return App.router.get('clusterController.alertsHostMap')[this.get('hostName')];
   }.property('App.router.clusterController.alerts.length'),
 
+  /**
+   * Get count of host components with stale configs
+   * @returns {Number}
+   */
   componentsWithStaleConfigsCount: function() {
     return this.get('hostComponents').filterProperty('staleConfigs', true).length;
   }.property('hostComponents.@each.staleConfigs'),
 
+  /**
+   * Get count of host components in maintenance mode
+   * @returns {Number}
+   */
+  componentsInMaintenanceCount: function() {
+    return this.get('hostComponents').filterProperty('workStatus', App.HostComponentStatus.maintenance).length;
+  }.property('hostsComponents.@each.workStatus').volatile(),
+
+  /**
+   * Truncate hostName if it longer than 43 symbols
+   * @returns {String}
+   */
   publicHostNameFormatted: function() {
     return this.get('publicHostName').length < 43 ? this.get('publicHostName') : this.get('publicHostName').substr(0, 40) + '...';
   }.property('publicHostName'),
 
+  /**
+   * Count of mounted on host disks
+   * @returns {Number}
+   */
   disksMounted: function() {
     return this.get('diskInfo.length');
   }.property('diskInfo.length'),
 
   /**
    * API return diskTotal and diskFree. Need to save their different
+   * @returns {Number}
    */
   diskUsed: function(){
     return this.get('diskTotal') - this.get('diskFree');
   }.property('diskFree', 'diskTotal'),
+
   /**
    * Format diskUsed value to float with 2 digits (also convert to GB)
+   * @returns {String} Format: '*** GB'
    */
   diskUsedFormatted: function() {
     return Math.round(this.get('diskUsed') * Math.pow(10, 2)) / Math.pow(10, 2) + 'GB';
   }.property('diskUsed'),
+
   /**
    * Format diskTotal value to float with 2 digits (also convert to GB)
+   * @returns {String} Format: '*** GB'
    */
   diskTotalFormatted: function() {
     return Math.round(this.get('diskTotal') * Math.pow(10, 2)) / Math.pow(10, 2) + 'GB';
   }.property('diskTotal'),
+
   /**
    * Percent value of used disk space
+   * @returns {Number}
    */
   diskUsage: function() {
     return (this.get('diskUsed')) / this.get('diskTotal') * 100;
   }.property('diskUsed', 'diskTotal'),
+
   /**
    * Format diskUsage to float with 2 digits
+   * @returns {String} Format: '**.** %'
    */
   diskUsageFormatted: function() {
     if (isNaN(this.get('diskUsage')) || this.get('diskUsage') < 0) {
@@ -111,6 +154,10 @@ App.Host = DS.Model.extend({
     return s + '%';
   }.property('diskUsage'),
 
+  /**
+   * Formatted string with data about disk usage
+   * @returns {String}
+   */
   diskInfoBar: function() {
     if (isNaN(this.get('diskUsage')) || this.get('diskUsage') < 0) {
       return this.get('diskUsageFormatted');
@@ -118,25 +165,39 @@ App.Host = DS.Model.extend({
     return this.get('diskUsedFormatted') + '/' + this.get('diskTotalFormatted') + ' (' + this.get('diskUsageFormatted')
       + ' ' + Em.I18n.t('services.service.summary.diskInfoBar.used') + ')';
   }.property('diskUsedFormatted', 'diskTotalFormatted'),
+
   /**
-   * formatted bytes to appropriate value
+   * Formatted bytes to appropriate value
+   * @returns {String}
    */
   memoryFormatted: function () {
     return misc.formatBandwidth(this.get('memory') * 1024);
   }.property('memory'),
+
   /**
    * Return true if the host has not sent heartbeat within the last 180 seconds
+   * @returns {bool}
    */
   isNotHeartBeating : function() {
     return (App.testMode) ? false : ((new Date()).getTime() - this.get('lastHeartBeatTime')) > 180 * 1000;
   }.property('lastHeartBeatTime'),
 
+  /**
+   * Average load
+   * @returns {Number}
+   */
   loadAvg: function() {
     if (this.get('loadOne') != null) return this.get('loadOne').toFixed(2);
     if (this.get('loadFive') != null) return this.get('loadFive').toFixed(2);
     if (this.get('loadFifteen') != null) return this.get('loadFifteen').toFixed(2);
+    return null;
   }.property('loadOne', 'loadFive', 'loadFifteen'),
 
+  /**
+   * Host health indicator
+   * Based on  <code>healthStatus</code>
+   * @returns {String}
+   */
   healthClass: function(){
     var statusMap = {
       'UNKNOWN': 'health-status-DEAD-YELLOW',
@@ -147,6 +208,11 @@ App.Host = DS.Model.extend({
     return statusMap[this.get('healthStatus')] || 'health-status-DEAD-YELLOW';
   }.property('healthStatus'),
 
+  /**
+   * Tooltip for host indicator
+   * Contains affected host components names (based on <code>healthClass</code>)
+   * @returns {String}
+   */
   healthToolTip: function(){
     var hostComponents = this.get('hostComponents').filter(function(item){
       if(item.get('workStatus') !== App.HostComponentStatus.started){

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/models/host_component.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js
index f94446b..081442c 100644
--- a/ambari-web/app/models/host_component.js
+++ b/ambari-web/app/models/host_component.js
@@ -26,6 +26,10 @@ App.HostComponent = DS.Model.extend({
   staleConfigs: DS.attr('boolean'),
   host: DS.belongsTo('App.Host'),
   service: DS.belongsTo('App.Service'),
+  /**
+   * Determine if component is client
+   * @returns {bool}
+   */
   isClient:function () {
     if(['PIG', 'SQOOP', 'HCAT', 'MAPREDUCE2_CLIENT'].contains(this.get('componentName'))){
       return true;
@@ -33,12 +37,27 @@ App.HostComponent = DS.Model.extend({
 
     return Boolean(this.get('componentName').match(/_client/gi));
   }.property('componentName'),
+  /**
+   * Determine if component is running now
+   * Based on <code>workStatus</code>
+   * @returns {bool}
+   */
   isRunning: function(){
     return (this.get('workStatus') == 'STARTED' || this.get('workStatus') == 'STARTING');
   }.property('workStatus'),
+
+  /**
+   * Formatted <code>componentName</code>
+   * @returns {String}
+   */
   displayName: function () {
     return App.format.role(this.get('componentName'));
   }.property('componentName'),
+
+  /**
+   * Determine if component is master
+   * @returns {bool}
+   */
   isMaster: function () {
     switch (this.get('componentName')) {
       case 'NAMENODE':
@@ -64,6 +83,11 @@ App.HostComponent = DS.Model.extend({
         return false;
     }
   }.property('componentName'),
+
+  /**
+   * Determine if component is slave
+   * @returns {bool}
+   */
   isSlave: function(){
     switch (this.get('componentName')) {
       case 'DATANODE':
@@ -82,6 +106,7 @@ App.HostComponent = DS.Model.extend({
    * They include some from master components, 
    * some from slave components, and rest from 
    * client components.
+   * @returns {bool}
    */
   isDeletable: function() {
     var canDelete = false;
@@ -104,6 +129,7 @@ App.HostComponent = DS.Model.extend({
   /**
    * A host-component is decommissioning when it is in HDFS service's list of
    * decomNodes.
+   * @returns {bool}
    */
   isDecommissioning: function () {
     var decommissioning = false;
@@ -119,6 +145,7 @@ App.HostComponent = DS.Model.extend({
   }.property('componentName', 'host.hostName', 'App.router.clusterController.isLoaded', 'App.router.updateController.isUpdated'),
   /**
    * User friendly host component status
+   * @returns {String}
    */
   componentTextStatus: function () {
     return App.HostComponentStatus.getTextStatus(this.get("workStatus"));
@@ -138,6 +165,11 @@ App.HostComponentStatus = {
   maintenance: "MAINTENANCE",
   unknown: "UNKNOWN",
 
+  /**
+   * Get host component status in "machine" format
+   * @param {String} value
+   * @returns {String}
+   */
   getKeyName:function(value){
     switch(value){
       case this.started:
@@ -162,6 +194,11 @@ App.HostComponentStatus = {
     return 'Unknown';
   },
 
+  /**
+   * Get user-friendly host component status
+   * @param {String} value
+   * @returns {String}
+   */
   getTextStatus: function (value) {
     switch (value) {
       case this.installing:

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index 1c25d2d..05531cf 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -2691,6 +2691,9 @@ table.graphs {
     background-image: @status-dead-yellow-marker;
     .status-dot-position;
   }
+  .maintenance {
+    color: #000;
+  }
   .host-name-search {
     position: relative;
     top: 0px;
@@ -2715,6 +2718,7 @@ table.graphs {
     .health-status-bar {
       font-size: 0.9em;
       margin-left: 0;
+      margin-bottom: 15px;
       min-width: 790px;
       color: #b4b4b4;
       .health-status {
@@ -2778,7 +2782,7 @@ table.graphs {
     .col0,
     td:first-child,
     th:first-child {
-      width: 3%;
+      width: 2%;
       min-width:13px!important;
     }
     .col1,
@@ -2786,15 +2790,15 @@ table.graphs {
     th:first-child + th{
       width: 19%;
     }
-    .col2,
+    .col2, .col3,
     td:first-child + td + td,
-    th:first-child + th + th{
-      width:3%;
+    th:first-child + th + th,
+    td:first-child + td + td + td,
+    th:first-child + th + th + th,{
+      width:2%;
       min-width:13px!important;
     }
-    .col3,.col4,.col5,.col6,.col7,
-    td:first-child + td + td + td,
-    th:first-child + th + th + th,
+    .col4,.col5,.col6,.col7,.col8,
     td:first-child + td + td + td + td,
     th:first-child + th + th + th + th,
     td:first-child + td + td + td + td + td,
@@ -2802,12 +2806,14 @@ table.graphs {
     td:first-child + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th,
     td:first-child + td + td + td + td + td + td + td,
-    th:first-child + th + th + th + th + th + th + th{
-      width: 12%;
-    }
-    .col8,
+    th:first-child + th + th + th + th + th + th + th,
     td:first-child + td + td + td + td + td + td + td + td,
     th:first-child + th + th + th + th + th + th + th + th{
+      width: 12%;
+    }
+    .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: 15%;
     }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/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 fcadc6f..8962ec7 100644
--- a/ambari-web/app/templates/main/host.hbs
+++ b/ambari-web/app/templates/main/host.hbs
@@ -27,16 +27,24 @@
       {{#view view.statusFilter categoriesBinding="view.categories"}}
         {{#each category in view.categories}}
           {{#if category.isVisible}}
-            |
+            {{#if category.alerts}}
+                <br /><br />
+            {{else}}
+                |
+            {{/if}}
             <span {{bindAttr class="aaa :category-item category.itemClass"}}>
               <a {{action selectCategory category target="view"}} href="#">
                 {{#if category.alerts}}
                   <span class="label label-important">{{t hosts.host.alerts.st}}</span>
                 {{else}}
                   {{#if category.restart}}
-                    <span class="muted icon-refresh"></span>
+                    <span class="icon-refresh"></span>
                   {{else}}
-                    <span {{bindAttr class=":health-status category.healthStatusValue"}}> &nbsp;&nbsp;&nbsp; </span>
+                    {{#if category.maintenance}}
+                      <span class="maintenance icon-medkit"></span>
+                    {{else}}
+                      <span {{bindAttr class=":health-status category.healthStatusValue"}}> &nbsp;&nbsp;&nbsp; </span>
+                    {{/if}}
                   {{/if}}
                 {{/if}}
                 {{category.label}}
@@ -63,6 +71,7 @@
         <th class="first"> </th>
         {{view view.parentView.nameSort}}
         <th> </th>
+        <th> </th>
         {{view view.parentView.ipSort}}
         {{view view.parentView.cpuSort}}
         {{view view.parentView.memorySort}}
@@ -74,6 +83,7 @@
         <th class="first"> </th>
         <th>{{view view.nameFilterView}}</th>
         <th> </th>
+        <th> </th>
         <th>{{view view.ipFilterView}}</th>
         <th>{{view view.cpuFilterView}}</th>
         <th>{{view view.ramFilterView}}</th>
@@ -102,6 +112,11 @@
               <span class="muted icon-refresh" rel="ComponentsTooltip" {{bindAttr title="view.restartRequiredComponentsMessage"}}></span>
             {{/if}}
           </td>
+          <td class="maintenance">
+            {{#if host.componentsInMaintenanceCount}}
+                <span class="icon-medkit" rel="ComponentsTooltip" {{bindAttr title="view.componentsInMaintenanceMessage"}}></span>
+            {{/if}}
+          </td>
           <td>{{host.ip}}</td>
           <td>{{host.cpu}}</td>
           <td>{{host.memoryFormatted}}</td>
@@ -114,7 +129,7 @@
 
           <td>{{host.loadAvg}}</td>
           <td>
-            <a href="#" class="host-components-expander" {{action toggleComponents target="view"}}> <span class="caret right"></span>{{view.componentsMessage}}</a>
+            <a href="#" class="host-components-expander" {{action toggleComponents target="view"}}> <span class="caret right"></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">
               {{{view.labels}}}
             </div>
@@ -124,7 +139,7 @@
     {{else}}
       <tr>
         <td class="first"></td>
-        <td colspan="8">
+        <td colspan="9">
           {{t hosts.table.noHosts}}
         </td>
       </tr>

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/ambari-web/app/views/common/table_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/table_view.js b/ambari-web/app/views/common/table_view.js
index e80c4d8..ecbf7d9 100644
--- a/ambari-web/app/views/common/table_view.js
+++ b/ambari-web/app/views/common/table_view.js
@@ -348,6 +348,7 @@ App.TableView = Em.View.extend({
     var filterConditions = this.get('filterConditions');
     if (!filterConditions.length) {
       this.set('filtersUsed', false);
+      return;
     }
     var filtersUsed = false;
     filterConditions.forEach(function(filterCondition) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/a440c0a8/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 4e455f7..110d9c2 100644
--- a/ambari-web/app/views/main/host.js
+++ b/ambari-web/app/views/main/host.js
@@ -44,6 +44,7 @@ App.MainHostView = App.TableView.extend({
   didInsertElement: function() {
     this.addObserver('controller.clearFilters', this, this.clearFiltersObs);
     this.clearFiltersObs();
+    this.addObserver('content.@each.hostComponents.@each', this, this.filter);
   },
 
   sortView: sort.wrapperView,
@@ -93,16 +94,10 @@ App.MainHostView = App.TableView.extend({
       this.$('.host-components').toggle();
     },
 
-    componentsMessage: function() {
-      var count = this.get('content.hostComponents.length');
-      if (count == 1) {
-        return count + ' ' + Em.I18n.t('common.component');
-      }
-      else {
-        return count + ' ' + Em.I18n.t('common.components');
-      }
-    }.property('content.hostComponents.@each'),
-
+    /**
+     * Tooltip message for "Restart Required" icon
+     * @returns {String}
+     */
     restartRequiredComponentsMessage: function() {
       var restartRequiredComponents = this.get('content.hostComponents').filterProperty('staleConfigs', true);
       var count = restartRequiredComponents.length;
@@ -113,10 +108,31 @@ App.MainHostView = App.TableView.extend({
       return Em.I18n.t('hosts.table.restartComponents.withoutNames').format(count);
     }.property('content.hostComponents.@each.staleConfigs'),
 
+    /**
+     * Tooltip message for "Maintenance" icon
+     * @returns {String}
+     */
+    componentsInMaintenanceMessage: function() {
+      var componentsInMaintenance = this.get('content.hostComponents').filterProperty('workStatus', App.HostComponentStatus.maintenance);
+      var count = componentsInMaintenance.length;
+      if (count <= 5) {
+        return Em.I18n.t('hosts.table.componentsInMaintenance.withNames').format(componentsInMaintenance.getEach('displayName').join(', '));
+      }
+      return Em.I18n.t('hosts.table.componentsInMaintenance.withoutNames').format(count);
+    }.property('content.hostComponents.@each.workStatus'),
+
+    /**
+     * String with list of host components <code>displayName</code>
+     * @returns {String}
+     */
     labels: function() {
       return this.get('content.hostComponents').getEach('displayName').join("<br />");
     }.property('content.hostComponents.@each'),
 
+    /**
+     * CSS value for disk usage bar
+     * @returns {String}
+     */
     usageStyle:function () {
       return "width:" + this.get('content.diskUsage') + "%";
     }.property('content.diskUsage')
@@ -132,6 +148,7 @@ App.MainHostView = App.TableView.extend({
       var statusString = this.get('healthStatusValue');
       var alerts = this.get('alerts');
       var restart = this.get('restart');
+      var maintenance = this.get('maintenance');
       if(alerts) {
         return this.get('view.content').filterProperty('criticalAlertsCount').get('length');
       }
@@ -140,15 +157,20 @@ App.MainHostView = App.TableView.extend({
           return this.get('view.content').filterProperty('componentsWithStaleConfigsCount').get('length');
         }
         else {
-          if (statusString == "") {
-            return this.get('view.content').get('length');
+          if (maintenance) {
+            return this.get('view.content').filterProperty('componentsInMaintenanceCount').get('length');
           }
           else {
-            return this.get('view.content').filterProperty('healthClass', statusString ).get('length');
+            if (statusString == "") {
+              return this.get('view.content').get('length');
+            }
+            else {
+              return this.get('view.content').filterProperty('healthClass', statusString ).get('length');
+            }
           }
         }
       }
-    }.property('view.content.@each.healthClass', 'view.content.@each.criticalAlertsCount', 'view.content.@each.hostComponents.@each.staleConfigs'),
+    }.property('view.content.@each.healthClass', 'view.content.@each.criticalAlertsCount', 'view.content.@each.componentsInMaintenanceCount', 'view.content.@each.hostComponents.@each.staleConfigs'),
 
     label: function () {
       return "%@ (%@)".fmt(this.get('value'), this.get('hostsCount'));
@@ -172,7 +194,8 @@ App.MainHostView = App.TableView.extend({
       self.categoryObject.create({value: Em.I18n.t('hosts.host.healthStatusCategory.orange'), healthStatusValue: 'health-status-DEAD-ORANGE', isVisible: true}),
       self.categoryObject.create({value: Em.I18n.t('hosts.host.healthStatusCategory.yellow'), healthStatusValue: 'health-status-DEAD-YELLOW', isVisible: true}),
       self.categoryObject.create({value: Em.I18n.t('hosts.host.alerts.label'), healthStatusValue: 'health-status-WITH-ALERTS', alerts: true, isVisible: true }),
-      self.categoryObject.create({value: Em.I18n.t('common.restart'), healthStatusValue: 'health-status-RESTART', restart: true, last: true, isVisible: true })
+      self.categoryObject.create({value: Em.I18n.t('common.restart'), healthStatusValue: 'health-status-RESTART', restart: true, isVisible: true }),
+      self.categoryObject.create({value: Em.I18n.t('common.maintenance'), healthStatusValue: 'health-status-MAINTENANCE', maintenance: true, last: true, isVisible: true })
     ];
 
     return categories;
@@ -203,17 +226,29 @@ App.MainHostView = App.TableView.extend({
         this.get('parentView').updateFilter(0, '', 'string');
         this.get('parentView').updateFilter(7, '>0', 'number');
         this.get('parentView').updateFilter(8, '', 'number');
+        this.get('parentView').updateFilter(9, '', 'number');
       }
       else {
         if(category.get('restart')) {
           this.get('parentView').updateFilter(0, '', 'string');
           this.get('parentView').updateFilter(7, '', 'number');
           this.get('parentView').updateFilter(8, '>0', 'number');
+          this.get('parentView').updateFilter(9, '', 'number');
+
         }
         else {
-          this.get('parentView').updateFilter(7, '', 'number');
-          this.get('parentView').updateFilter(8, '', 'number');
-          this.get('parentView').updateFilter(0, category.get('healthStatusValue'), 'string');
+          if(category.get('maintenance')) {
+            this.get('parentView').updateFilter(0, '', 'string');
+            this.get('parentView').updateFilter(7, '', 'number');
+            this.get('parentView').updateFilter(8, '', 'number');
+            this.get('parentView').updateFilter(9, '>0', 'number');
+          }
+          else {
+            this.get('parentView').updateFilter(0, category.get('healthStatusValue'), 'string');
+            this.get('parentView').updateFilter(7, '', 'number');
+            this.get('parentView').updateFilter(8, '', 'number');
+            this.get('parentView').updateFilter(9, '', 'number');
+          }
         }
       }
     },
@@ -259,6 +294,23 @@ App.MainHostView = App.TableView.extend({
   }),
 
   /**
+   * view of the maintenance filter implemented as a category of host statuses
+   */
+  maintenanceFilter: Em.View.extend({
+    column: 9,
+    value: null,
+    classNames: ['noDisplay'],
+    showClearFilter: function(){
+      var mockEvent = {
+        context: this.get('parentView.categories').findProperty('healthStatusValue', 'health-status-MAINTENANCE')
+      };
+      if(this.get('value')) {
+        this.get('parentView.childViews').findProperty('column', 0).selectCategory(mockEvent);
+      }
+    }
+  }),
+
+  /**
    * Filter view for name column
    * Based on <code>filters</code> library
    */
@@ -453,6 +505,7 @@ App.MainHostView = App.TableView.extend({
     associations[6] = 'hostComponents';
     associations[7] = 'criticalAlertsCount';
     associations[8] = 'componentsWithStaleConfigsCount';
+    associations[9] = 'componentsInMaintenanceCount';
     return associations;
   }.property()
 });
\ No newline at end of file