You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2013/03/23 05:40:33 UTC

svn commit: r1460094 - in /incubator/ambari/trunk: ./ ambari-web/app/ ambari-web/app/controllers/global/ ambari-web/app/styles/ ambari-web/app/templates/common/ ambari-web/app/utils/

Author: yusaku
Date: Sat Mar 23 04:40:33 2013
New Revision: 1460094

URL: http://svn.apache.org/r1460094
Log:
AMBARI-1652. Background operation display enhancements. (yusaku)

Modified:
    incubator/ambari/trunk/CHANGES.txt
    incubator/ambari/trunk/ambari-web/app/controllers/global/background_operations_controller.js
    incubator/ambari/trunk/ambari-web/app/messages.js
    incubator/ambari/trunk/ambari-web/app/styles/application.less
    incubator/ambari/trunk/ambari-web/app/templates/common/host_progress_popup.hbs
    incubator/ambari/trunk/ambari-web/app/utils/host_progress_popup.js

Modified: incubator/ambari/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/CHANGES.txt?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/CHANGES.txt (original)
+++ incubator/ambari/trunk/CHANGES.txt Sat Mar 23 04:40:33 2013
@@ -147,6 +147,8 @@ Trunk (unreleased changes):
 
  IMPROVEMENTS
 
+ AMBARI-1652. Background operation display enhancements. (yusaku)
+
  AMBARI-1686. Implement Test IvoryService to functional test mirroring API.
  (tbeerbower)
 

Modified: incubator/ambari/trunk/ambari-web/app/controllers/global/background_operations_controller.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/controllers/global/background_operations_controller.js?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/controllers/global/background_operations_controller.js (original)
+++ incubator/ambari/trunk/ambari-web/app/controllers/global/background_operations_controller.js Sat Mar 23 04:40:33 2013
@@ -31,6 +31,12 @@ App.BackgroundOperationsController = Em.
   executeTasks: [],
 
   /**
+   * For host component popup
+   */
+  services:[],
+  serviceTimestamp: null,
+
+  /**
    * Task life time after finishing
    */
   taskLifeTime: 5*60*1000,
@@ -174,26 +180,196 @@ App.BackgroundOperationsController = Em.
   },
 
   /**
+   * Start code for hostcomponent popup in test mode for now
+   */
+  POLL_INTERVAL: 10,
+  isPolling: false,
+  installedServices: App.Service.find(),
+  simulateAttempt:0,
+
+  startPolling1: function(){
+    var url = '';
+    if(!this.get('isPolling')){
+      this.set('isPolling', true);
+      if (App.testMode) {
+        this.simulatePolling();
+      } else {
+        //pass an interval "1" to start poll immediately first time
+        this.doPoll(url, 1);
+      }
+    }
+  },
+
+  simulatePolling: function(){
+    var simulateAttempt = this.get('simulateAttempt');
+    var URLs = [
+      '/data/wizard/upgrade/poll_1.json',
+      '/data/wizard/upgrade/poll_2.json',
+      '/data/wizard/upgrade/poll_3.json',
+      '/data/wizard/upgrade/poll_4.json',
+      '/data/wizard/upgrade/poll_5.json',
+    ];
+    if(simulateAttempt < 5){
+      if(this.get("simulateAttempt")==4){
+        this.set("POLL_INTERVAL",4000);
+      }
+      this.doPoll(URLs[simulateAttempt]);
+      this.set('simulateAttempt', ++simulateAttempt);
+    }
+  },
+
+  doPoll: function(url, interval){
+    var self = this;
+    var pollInterval = interval || self.POLL_INTERVAL;
+    if (self.get('isPolling')) {
+      setTimeout(function () {
+        $.ajax({
+          utype: 'GET',
+          url: url,
+          async: true,
+          timeout: App.timeout,
+          dataType: 'json',
+          success: function (data) {
+            var result = self.parseTasks(data);
+            if (App.testMode) {
+              self.simulatePolling();
+            } else {
+              self.doPoll(url);
+            }
+          },
+          error: function () {
+
+          },
+          statusCode: require('data/statusCodes')
+        }).retry({times: App.maxRetries, timeout: App.timeout}).then(null,
+            function () {
+              App.showReloadPopup();
+              console.log('Install services all retries failed');
+            }
+        );
+      }, pollInterval);
+    }
+  },
+
+  parseTasks: function(data){
+    var tasks = data.tasks || [];
+    this.get('services').forEach(function (service) {
+      var hosts = service.get('hosts');
+      var tasksPerService = [];
+      if(hosts.length){
+        hosts.forEach(function (host) {
+          var tasksPerHost = tasks.filter(function(task){
+            if(task.Tasks.host_name == host.name && host.get('components').contains(task.Tasks.role)){
+              return true;
+            }
+          });
+          if (tasksPerHost.length) {
+            this.setLogTasksStatePerHost(tasksPerHost, host);
+            tasksPerService = tasksPerService.concat(tasksPerHost);
+          }
+        }, this);
+      } else {
+        service.set('status', 'PENDING');
+        service.set('detailedMessage', Em.I18n.t('installer.stackUpgrade.step3.host.nothingToUpgrade'));
+      }
+    }, this);
+    this.set('serviceTimestamp', new Date().getTime());
+    return true;
+  },
+
+  setLogTasksStatePerHost: function (tasksPerHost, host) {
+    tasksPerHost.forEach(function (_task) {
+      var task = host.get('logTasks').findProperty('Tasks.id', _task.Tasks.id);
+      if (task) {
+        host.get('logTasks').removeObject(task);
+      }
+      host.get('logTasks').pushObject(_task);
+    }, this);
+  },
+
+  mockServices: [
+    Em.Object.create({
+      serviceName: 'GANGLIA',
+      displayName: 'Ganglia Update',
+      workStatus: 'STARTED',
+      hostComponents: []
+    }),
+    Em.Object.create({
+      serviceName: 'HDFS',
+      displayName: 'HDFS Update',
+      workStatus: 'STARTED',
+      hostComponents: []
+    })
+  ],
+
+  loadServices: function(){
+    var installedServices = App.testMode ? this.get('mockServices') : this.get('content.servicesInfo');
+    var services = [];
+    installedServices.forEach(function(_service){
+      services.push(Em.Object.create({
+        name: _service.get('serviceName'),
+        displayName: _service.get('displayName'),
+        hosts: this.loadHosts(_service),
+        progress: 0,
+        status: "PENDING",
+        detailMessage:''
+      }));
+    }, this);
+    this.set('services', services);
+  }.observes('content.servicesInfo'),
+
+  loadHosts: function(service){
+    var hostComponents = App.HostComponent.find().filterProperty('service.serviceName', service.get('serviceName'));
+    var hosts = hostComponents.mapProperty('host').uniq();
+    var result = [];
+    hosts.forEach(function(host){
+      result.push(Em.Object.create({
+        name: host.get('hostName'),
+        publicName: host.get('publicHostName'),
+        logTasks: [],
+        components: hostComponents.filterProperty('host.hostName', host.get('hostName')).mapProperty('componentName')
+      }));
+    });
+    return result;
+  },
+
+  /**
+   * End code for hostcomponent popup
+   */
+
+
+  /**
+
+  /**
    * Onclick handler for background operations number located right to logo
    * @return PopupObject For testing purposes
    */
   showPopup: function(){
-    this.set('executeTasks', []);
-    App.updater.immediateRun('loadOperations');
-    return App.ModalPopup.show({
-      headerClass: Ember.View.extend({
-        controller: this,
-        template:Ember.Handlebars.compile('{{allOperationsCount}} Background Operations Running')
-      }),
-      bodyClass: Ember.View.extend({
-        controller: this,
-        templateName: require('templates/main/background_operations_popup')
-      }),
-      onPrimary: function() {
-        this.hide();
-      },
-      secondary : null
-    });
+    if(App.testMode){
+      this.set("POLL_INTERVAL",10);
+      this.set("isPolling",false);
+      this.set("simulateAttempt",0);
+      this.loadServices();
+      this.startPolling1();
+      App.HostPopup.initPopup("", this, true);
+    }else{
+      this.set('executeTasks', []);
+      App.updater.immediateRun('loadOperations');
+      return App.ModalPopup.show({
+        headerClass: Ember.View.extend({
+          controller: this,
+          template:Ember.Handlebars.compile('{{allOperationsCount}} Background Operations Running')
+        }),
+        bodyClass: Ember.View.extend({
+          controller: this,
+          templateName: require('templates/main/background_operations_popup')
+        }),
+        onPrimary: function() {
+          this.hide();
+        },
+        secondary : null
+      });
+    }
   },
 
   /**

Modified: incubator/ambari/trunk/ambari-web/app/messages.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/messages.js?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/messages.js (original)
+++ incubator/ambari/trunk/ambari-web/app/messages.js Sat Mar 23 04:40:33 2013
@@ -47,6 +47,7 @@ Em.I18n.translations = {
   'common.next':'Next',
   'common.host':'Host',
   'common.hosts':'Hosts',
+  'common.services':'Services',
   'common.group':'Group',
   'common.progress':'Progress',
   'common.status':'Status',
@@ -115,6 +116,7 @@ Em.I18n.translations = {
   'common.exception':'Exception',
   'common.undo':'Undo',
 
+  'hostPopup.noServicesToShow':'No services to show',
   'hostPopup.noHostsToShow':'No hosts to show',
   'hostPopup.noTasksToShow':'No tasks to show',
 

Modified: incubator/ambari/trunk/ambari-web/app/styles/application.less
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/styles/application.less?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/styles/application.less (original)
+++ incubator/ambari/trunk/ambari-web/app/styles/application.less Sat Mar 23 04:40:33 2013
@@ -743,7 +743,7 @@ a:focus {
 
 
 
-  #host-info{
+  #host-info, #service-info{
     .task-list-line-cursor{
       width: 100%;
       height: 20px;

Modified: incubator/ambari/trunk/ambari-web/app/templates/common/host_progress_popup.hbs
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/templates/common/host_progress_popup.hbs?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/templates/common/host_progress_popup.hbs (original)
+++ incubator/ambari/trunk/ambari-web/app/templates/common/host_progress_popup.hbs Sat Mar 23 04:40:33 2013
@@ -18,12 +18,61 @@
 
 <div class="host-component-popup-wrap">
 
+    <!-- SERVICES --->
+
+    <div {{bindAttr class="view.isServiceListHidden:hidden :task-list-main-warp"}}>
+      <div class="task-top-wrap">
+        {{t common.services}}
+        <div class="select-wrap">
+          {{t common.show}}:
+          {{view Ember.Select
+            contentBinding="view.categories"
+            optionValuePath="content.value"
+            optionLabelPath="content.label"
+            selectionBinding="view.serviceCategory"
+          }}
+        </div>
+      </div>
+      <div id="service-info">
+        {{#each servicesInfo in view.services}}
+        <div {{bindAttr class="servicesInfo.isVisible::hidden :log-list-wrap"}}>
+          <div {{action gotoHosts servicesInfo}} class="task-list-line-cursor">
+            <div class="host-name-icon-wrap">
+              <i {{bindAttr class="servicesInfo.status servicesInfo.icon"}}></i>
+              <a href="#">
+                {{servicesInfo.name}}
+              </a>
+            </div>
+            <div class="progress-bar span2">
+                <div {{bindAttr class="progress-striped active servicesInfo.barColor :progress"}}>
+                    <div class="bar" {{bindAttr style="servicesInfo.barWidth"}}></div>
+                </div>
+            </div>
+            <div class="host-progress-num">{{servicesInfo.progress}}%</div>
+            <div class="show-details"><i class="icon-caret-right"></i></div>
+          </div>
+        </div>
+        {{/each}}
+        {{#if view.isServiceEmptyList}}
+          <div class="log-list-wrap">{{t hostPopup.noServicesToShow}}</div>
+        {{/if}}
+      </div>
+    </div>
+
+
   <!-- HOSTS --->
 
   <div {{bindAttr class="view.isHostListHidden:hidden :task-list-main-warp"}}>
     <div class="task-top-wrap">
-       {{t common.hosts}}
-      <div class="select-wrap">
+      {{#if controller.showServices}}
+        <a class="task-detail-back-to-hosts" href="javascript:void(null)" {{action backToServiceList}} ><i class="icon-arrow-left"></i>&nbsp;{{t common.services}}</a>
+        <div>
+          <span class="task-detail-log-rolename" >{{t common.services}}</span>
+        </div>
+      {{else}}
+         {{t common.hosts}}
+      {{/if}}
+      <div {{bindAttr class="controller.showServices:tasks-list-select :select-wrap"}}>
         {{t common.show}}:
         {{view Ember.Select
           contentBinding="view.categories"

Modified: incubator/ambari/trunk/ambari-web/app/utils/host_progress_popup.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/utils/host_progress_popup.js?rev=1460094&r1=1460093&r2=1460094&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/utils/host_progress_popup.js (original)
+++ incubator/ambari/trunk/ambari-web/app/utils/host_progress_popup.js Sat Mar 23 04:40:33 2013
@@ -23,23 +23,54 @@ var App = require('app');
  */
 App.HostPopup = Em.Object.create({
 
+  servicesInfo: null,
   hosts: null,
   inputData: null,
   serviceName: "",
   popupHeaderName: "",
   serviceController: null,
-  updateTimeOut: 100,
+  showServices: false,
 
-  initPopup: function (serviceName, controller) {
+  /**
+   * Sort object array
+   * @param array
+   * @param p
+   * @return {*}
+   */
+  sortArray: function (array, p) {
+    return array.sort(function (a, b) {
+      return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
+    });
+  },
+
+  /**
+   * Entering point of this component
+   * @param serviceName
+   * @param controller
+   * @param showServices
+   */
+  initPopup: function (serviceName, controller, showServices) {
     this.set("serviceName", serviceName);
     this.set("serviceController", controller);
+    if (showServices) {
+      this.set("showServices", true);
+    } else {
+      this.set("showServices", false);
+      this.set("popupHeaderName", serviceName);
+    }
+    this.set("hosts", null);
+    this.set("servicesInfo", null);
     this.set("inputData", null);
     this.set("inputData", this.get("serviceController.services"));
-    this.set("popupHeaderName", serviceName);
     this.createPopup();
   },
 
-  getHostStatus: function (tasks) {
+  /**
+   * Depending on tasks status
+   * @param tasks
+   * @return {Array} [Status, Icon type, Progressbar color]
+   */
+  getStatus: function (tasks) {
     if (tasks.everyProperty('Tasks.status', 'COMPLETED')) {
       return ['SUCCESS', 'icon-ok', 'progress-info'];
     }
@@ -58,8 +89,12 @@ App.HostPopup = Em.Object.create({
     return ['PENDING', 'icon-cog', 'progress-info'];
   },
 
-  getHostProgress: function (tasks) {
-
+  /**
+   * Progress of host or service depending on tasks status
+   * @param tasks
+   * @return {Number} percent of completion
+   */
+  getProgress: function (tasks) {
     var progress = 0;
     var actionsNumber = tasks.length;
     var completedActions = tasks.filterProperty('Tasks.status', 'COMPLETED').length
@@ -73,14 +108,89 @@ App.HostPopup = Em.Object.create({
     return progress;
   },
 
+  /**
+   * For Background operation popup calculate number of running Operations, and set popup header
+   */
+  setBackgroundOperationHeader: function () {
+    var allServices = this.get("servicesInfo");
+    var numRunning = allServices.filterProperty("status", App.format.taskStatus("IN_PROGRESS")).length;
+    this.set("popupHeaderName", numRunning + " Background operations Running");
+  },
+
+  /**
+   * Create services obj data structure for popup
+   * Set data for services
+   */
+  onServiceUpdate: function () {
+    if (this.showServices && this.get("inputData")) {
+      var self = this;
+      var allNewServices = [];
+      this.set("servicesInfo", null);
+      this.get("inputData").forEach(function (service) {
+        var newService = Ember.Object.create({
+          displayName: service.displayName,
+          detailMessage: service.detailMessage,
+          message: service.message,
+          progress: 0,
+          status: App.format.taskStatus("PENDING"),
+          name: service.name,
+          isVisible: true,
+          icon: 'icon-cog',
+          barColor: 'progress-info',
+          barWidth: 'width:0%;'
+        });
+        var allTasks = []
+        service.hosts.forEach(function (tasks) {
+          tasks.logTasks.forEach(function (task) {
+            allTasks.push(task);
+          });
+        });
+        if (allTasks.length > 0) {
+          var status = self.getStatus(allTasks);
+          var progress = self.getProgress(allTasks);
+          newService.set('status', App.format.taskStatus(status[0]));
+          newService.set('icon', status[1]);
+          newService.set('barColor', status[2]);
+          newService.set('progress', progress);
+          newService.set('barWidth', "width:" + progress + "%;");
+        }
+        allNewServices.push(newService);
+      })
+      self.set('servicesInfo', allNewServices);
+      if (this.get("serviceName") == "")
+        this.setBackgroundOperationHeader();
+    }
+  }.observes("this.inputData"),
+
+  /**
+   * Create hosts and tasks data structure for popup
+   * Set data for hosts and tasks
+   */
   onHostUpdate: function () {
     var self = this;
     if (this.get("inputData")) {
       var hostsArr = [];
-      var hostsData = this.get("inputData").filterProperty("name", this.get("serviceName")).objectAt(0);
-      var hosts = hostsData.hosts;
+      var hostsData = this.get("inputData")
+      var hosts = [];
+      if (this.get("showServices") && this.get("serviceName") == "") {
+        hostsData.forEach(function (service) {
+          var host = service.hosts;
+          host.setEach("serviceName", service.name);
+          hosts.push.apply(hosts, host);
+        });
+      } else {
+        hostsData = hostsData.filterProperty("name", this.get("serviceName")).objectAt(0);
+        hosts = hostsData.hosts;
+        hosts.setEach("serviceName", this.get("serviceName"));
+      }
     }
+
     if (hosts) {
+      /**
+       * sort host names by name value
+       */
+      this.sortArray(hosts, "name");
+
       hosts.forEach(function (_host) {
         var tasks = _host.logTasks;
         var hostInfo = Ember.Object.create({});
@@ -88,20 +198,19 @@ App.HostPopup = Em.Object.create({
         hostInfo.set('publicName', _host.publicName);
         hostInfo.set('progress', 0);
         hostInfo.set('status', App.format.taskStatus("PENDING"));
-        hostInfo.set('serviceName', hostsData.name);
+        hostInfo.set('serviceName', _host.serviceName);
         hostInfo.set('isVisible', true);
         hostInfo.set('icon', "icon-cog");
         hostInfo.set('barColor', "progress-info");
         hostInfo.set('barWidth', "width:0%;");
 
         tasks = self.sortTasksById(tasks);
-        tasks = self.groupTasksByRole(tasks);
         var tasksArr = [];
 
         if (tasks.length) {
 
-          var hostStatus = self.getHostStatus(tasks);
-          var hostProgress = self.getHostProgress(tasks);
+          var hostStatus = self.getStatus(tasks);
+          var hostProgress = self.getProgress(tasks);
           hostInfo.set('status', App.format.taskStatus(hostStatus[0]));
           hostInfo.set('icon', hostStatus[1]);
           hostInfo.set('barColor', hostStatus[2]);
@@ -111,7 +220,7 @@ App.HostPopup = Em.Object.create({
           tasks.forEach(function (_task) {
             var taskInfo = Ember.Object.create({});
             taskInfo.set('id', _task.Tasks.id);
-            taskInfo.set('hostName', _host.name);
+            taskInfo.set('hostName', _host.publicName);
             taskInfo.set('command', _task.Tasks.command.toLowerCase());
             taskInfo.set('status', App.format.taskStatus(_task.Tasks.status));
             taskInfo.set('role', App.format.role(_task.Tasks.role));
@@ -144,6 +253,11 @@ App.HostPopup = Em.Object.create({
     self.set("hosts", hostsArr);
   }.observes("this.inputData"),
 
+  /**
+   * Sort tasks by it`s id
+   * @param tasks
+   * @return {Array}
+   */
   sortTasksById: function (tasks) {
     var result = [];
     var id = 1;
@@ -162,19 +276,14 @@ App.HostPopup = Em.Object.create({
     return result;
   },
 
-  groupTasksByRole: function (tasks) {
-    var sortedTasks = [];
-    var taskRoles = tasks.mapProperty('Tasks.role').uniq();
-    for (var i = 0; i < taskRoles.length; i++) {
-      sortedTasks = sortedTasks.concat(tasks.filterProperty('Tasks.role', taskRoles[i]))
-    }
-    return sortedTasks;
-  },
-
-
+  /**
+   * Show popup
+   * @return PopupObject For testing purposes
+   */
   createPopup: function () {
     var self = this;
     var hostsInfo = this.get("hosts");
+    var servicesInfo = this.get("servicesInfo");
     return App.ModalPopup.show({
       headerClass: Ember.View.extend({
         controller: this,
@@ -191,38 +300,82 @@ App.HostPopup = Em.Object.create({
         templateName: require('templates/common/host_progress_popup'),
         isLogWrapHidden: true,
         isTaskListHidden: true,
-        isHostListHidden: false,
+        isHostListHidden: true,
+        isServiceListHidden: false,
         showTextArea: false,
+        isServiceEmptyList: true,
         isHostEmptyList: true,
         isTasksEmptyList: true,
         controller: this,
         hosts: hostsInfo,
+        services: servicesInfo,
 
         tasks: null,
 
         didInsertElement: function () {
-          this.setSelectCount(this.get("hosts"));
+          this.setOnStart();
+        },
+
+        /**
+         * Preset values on init
+         */
+        setOnStart: function () {
+          if (this.get("controller.showServices")) {
+            this.setSelectCount(this.get("services"));
+          } else {
+            this.set("isHostListHidden", false);
+            this.set("isServiceListHidden", true);
+          }
         },
 
+        /**
+         * When popup is opened, and data after polling has changed, update this data in component
+         */
         updateHostInfo: function () {
           this.get("controller").set("inputData", null);
           this.get("controller").set("inputData", this.get("controller.serviceController.services"));
           this.set("hosts", this.get("controller.hosts"));
+          this.set("services", this.get("controller.servicesInfo"));
         }.observes("this.controller.serviceController.serviceTimestamp"),
 
+        /**
+         * Depending on service filter, set which services should be shown
+         */
+        visibleServices: function () {
+          if (this.get("services")) {
+            this.set("isServiceEmptyList", true);
+            if (this.get('serviceCategory.value')) {
+              var filter = this.get('serviceCategory.value');
+              var services = this.get('services');
+              services.setEach("isVisible", false);
+              this.setVisability(filter, services);
+              if (services.filterProperty("isVisible", true).length > 0) {
+                this.set("isServiceEmptyList", false);
+              }
+            }
+          }
+        }.observes('serviceCategory', 'services'),
+
+        /**
+         * Depending on hosts filter, set which hosts should be shown
+         */
         visibleHosts: function () {
           this.set("isHostEmptyList", true);
-          if (this.get('hostCategory.value')) {
+          if (this.get('hostCategory.value') && this.get('hosts')) {
             var filter = this.get('hostCategory.value');
             var hosts = this.get('hosts');
             hosts.setEach("isVisible", false);
             this.setVisability(filter, hosts);
+
             if (hosts.filterProperty("isVisible", true).length > 0) {
               this.set("isHostEmptyList", false);
             }
           }
         }.observes('hostCategory', 'hosts'),
 
+        /**
+         * Depending on tasks filter, set which tasks should be shown
+         */
         visibleTasks: function () {
           this.set("isTasksEmptyList", true);
           if (this.get('taskCategory.value') && this.get('tasks')) {
@@ -235,6 +388,11 @@ App.HostPopup = Em.Object.create({
           }
         }.observes('taskCategory', 'tasks'),
 
+        /**
+         * Depending on selected filter type, set object visibility value
+         * @param filter
+         * @param obj
+         */
         setVisability: function (filter, obj) {
           obj.setEach("isVisible", false);
           if (filter == "all") {
@@ -263,6 +421,9 @@ App.HostPopup = Em.Object.create({
           }
         },
 
+        /**
+         * Select box, display names and values
+         */
         categories: [
           Ember.Object.create({value: 'all', label: Em.I18n.t('installer.step9.hostLog.popup.categories.all') }),
           Ember.Object.create({value: 'pending', label: Em.I18n.t('installer.step9.hostLog.popup.categories.pending')}),
@@ -273,9 +434,17 @@ App.HostPopup = Em.Object.create({
           Ember.Object.create({value: 'timedout', label: Em.I18n.t('installer.step9.hostLog.popup.categories.timedout') })
         ],
 
+        /**
+         * Selected option is binded to this values
+         */
+        serviceCategory: null,
         hostCategory: null,
         taskCategory: null,
 
+        /**
+         * Count number of operations for select box options
+         * @param obj
+         */
         setSelectCount: function (obj) {
           if (!obj) return;
           var countAll = obj.length;
@@ -295,14 +464,24 @@ App.HostPopup = Em.Object.create({
           this.categories.filterProperty("value", 'timedout').objectAt(0).set("label", "Timedout (" + countTimedout + ")");
         },
 
+        /**
+         * Depending on currently viewed tab, call setSelectCount function
+         */
         updateSelectView: function () {
           if (!this.get('isHostListHidden')) {
             this.setSelectCount(this.get("hosts"))
           } else if (!this.get('isTaskListHidden')) {
             this.setSelectCount(this.get("tasks"))
+          } else if (!this.get('isServiceListHidden')) {
+            this.setSelectCount(this.get("services"))
           }
         }.observes('hosts', 'isTaskListHidden', 'isHostListHidden'),
 
+        /**
+         * Onclick handler for button <-Tasks
+         * @param event
+         * @param context
+         */
         backToTaskList: function (event, context) {
           this.destroyClipBoard();
           this.set("openedTaskId", 0);
@@ -310,6 +489,11 @@ App.HostPopup = Em.Object.create({
           this.set("isTaskListHidden", false);
         },
 
+        /**
+         * Onclick handler for button <-Hosts
+         * @param event
+         * @param context
+         */
         backToHostList: function (event, context) {
           this.set("isHostListHidden", false);
           this.set("isTaskListHidden", true);
@@ -317,6 +501,45 @@ App.HostPopup = Em.Object.create({
           this.get("controller").set("popupHeaderName", this.get("controller.serviceName"));
         },
 
+        /**
+         * Onclick handler for button <-Services
+         * @param event
+         * @param context
+         */
+        backToServiceList: function (event, context) {
+          this.get("controller").set("serviceName", "");
+          this.set("isHostListHidden", true);
+          this.set("isServiceListHidden", false);
+          this.set("isTaskListHidden", true);
+          this.set("tasks", null);
+          this.set("hosts", null);
+          this.get("controller").setBackgroundOperationHeader();
+        },
+
+        /**
+         * Onclick handler for selected Service
+         * @param event
+         * @param context
+         */
+        gotoHosts: function (event, context) {
+          this.get("controller").set("serviceName", event.context.get("name"));
+          this.get("controller").onHostUpdate();
+          var servicesInfo = this.get("controller.hosts");
+          if (servicesInfo.length) {
+            this.get("controller").set("popupHeaderName", event.context.get("name"));
+          }
+          this.set('hosts', servicesInfo);
+          this.set("isServiceListHidden", true);
+          this.set("isHostListHidden", false);
+          $(".modal").scrollTop(0);
+          $(".modal-body").scrollTop(0);
+        },
+
+        /**
+         * Onclick handler for selected Host
+         * @param event
+         * @param context
+         */
         gotoTasks: function (event, context) {
           var taskInfo = event.context.tasks;
           if (taskInfo.length) {
@@ -329,6 +552,9 @@ App.HostPopup = Em.Object.create({
           $(".modal-body").scrollTop(0);
         },
 
+        /**
+         * Onclick handler for selected Task
+         */
         openTaskLogInDialog: function () {
           newwindow = window.open();
           newdocument = newwindow.document;
@@ -338,6 +564,9 @@ App.HostPopup = Em.Object.create({
 
         openedTaskId: 0,
 
+        /**
+         * Return task detail info of opened task
+         */
         openedTask: function () {
           if (!this.get('openedTaskId')) {
             return Ember.Object.create();
@@ -345,6 +574,11 @@ App.HostPopup = Em.Object.create({
           return this.get('tasks').findProperty('id', this.get('openedTaskId'));
         }.property('tasks', 'openedTaskId'),
 
+        /**
+         * Onclick event for show task detail info
+         * @param event
+         * @param context
+         */
         toggleTaskLog: function (event, context) {
           var taskInfo = event.context;
           this.set("isLogWrapHidden", false);
@@ -355,6 +589,10 @@ App.HostPopup = Em.Object.create({
           $(".modal-body").scrollTop(0);
         },
 
+        /**
+         * Onclick event for copy to clipboard button
+         * @param event
+         */
         textTrigger: function (event) {
           if ($(".task-detail-log-clipboard").length > 0) {
             this.destroyClipBoard();
@@ -362,6 +600,10 @@ App.HostPopup = Em.Object.create({
             this.createClipBoard();
           }
         },
+
+        /**
+         * Create Clip Board
+         */
         createClipBoard: function () {
           $(".task-detail-log-clipboard-wrap").html('<textarea class="task-detail-log-clipboard"></textarea>');
           $(".task-detail-log-clipboard")
@@ -372,6 +614,10 @@ App.HostPopup = Em.Object.create({
               .select();
           $(".task-detail-log-maintext").css("display", "none")
         },
+
+        /**
+         * Destroy Clip Board
+         */
         destroyClipBoard: function () {
           $(".task-detail-log-clipboard").remove();
           $(".task-detail-log-maintext").css("display", "block");