浏览代码

AMBARI-762. Implement Confirm Hosts page for Ambari installer. (Jaimin Jetly)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1388306 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 年之前
父节点
当前提交
6c995c099f

+ 3 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,9 @@ AMBARI-666 branch (unreleased changes)
 
 
   NEW FEATURES
   NEW FEATURES
 
 
+  AMBARI-762. Implement Confirm Hosts page for Ambari installer
+  (Jaimin Jetly via yusaku)
+
   AMBARI-763. Implement Installer Step 6 (Assign Slaves). (yusaku)
   AMBARI-763. Implement Installer Step 6 (Assign Slaves). (yusaku)
 
 
   AMBARI-760. Fix injection in data access objects to use guice provider.
   AMBARI-760. Fix injection in data access objects to use guice provider.

+ 8 - 10
ambari-web/app/controllers/installer/step2_controller.js

@@ -53,14 +53,16 @@ App.InstallerStep2Controller = Em.Controller.extend({
   }.observes('localRepo'),
   }.observes('localRepo'),
 
 
   validateHostNames: function () {
   validateHostNames: function () {
-    this.hostNameArr = this.get('hostNames').split(new RegExp("\\s"));
-    for (var i = 0; i < this.hostNameArr.length; i++) {
+    this.hostNameArr = this.get('hostNames').trim().split(new RegExp("\\s+","g"));
+   // this.hostNameArr = this.get('hostNames').trim().match(/\w+|"[^"]+"/g);
+    for (var index in this.hostNameArr) {
+      console.log("host name is: " + this.hostNameArr[index]);
       //TODO: other validation for hostnames will be covered over here
       //TODO: other validation for hostnames will be covered over here
       // For now hostnames that start or end with '-' are not allowed
       // For now hostnames that start or end with '-' are not allowed
-      if (/^\-/.test(this.hostNameArr[i]) || /\-$/.test(this.hostNameArr[i])) {
-        console.log('Invalid host name: ' + this.hostNameArr[i]);
-        this.set('hostNameErrMsg', Em.I18n.t('installer.step2.hostName.error.invalid'));
+      if (/^\-/.test(this.hostNameArr[index]) || /\-$/.test(this.hostNameArr[index])) {
+        console.log('Invalid host name: ' + this.hostNameArr[index]);
         this.set('hostNameErr', true);
         this.set('hostNameErr', true);
+        this.set('hostNameErrMsg', Em.I18n.t('installer.step2.hostName.error.invalid'));
         this.set('hostNameEmptyError', false);
         this.set('hostNameEmptyError', false);
         this.set('hostNameNotRequiredErr', false);
         this.set('hostNameNotRequiredErr', false);
         return false;
         return false;
@@ -163,10 +165,6 @@ App.InstallerStep2Controller = Em.Controller.extend({
 
 
 
 
   evaluateStep2: function () {
   evaluateStep2: function () {
-
-    //task1 = do primary validations on whole step before executing any further steps
-    //task2 = parsing hostnames string to hostnames json array
-    //task3 = check validation for every hostname and store it in localstorage
     //task4 = Storing ambari agent Install type in localStorage (installType maps at host level and so every host will have this as an property)
     //task4 = Storing ambari agent Install type in localStorage (installType maps at host level and so every host will have this as an property)
     //task5 = Storing path of software repository(remote/local repo) to localStorage
     //task5 = Storing path of software repository(remote/local repo) to localStorage
     //task6 = call to rest API: @Post http://ambari_server/api/bootstrap
     //task6 = call to rest API: @Post http://ambari_server/api/bootstrap
@@ -177,7 +175,7 @@ App.InstallerStep2Controller = Em.Controller.extend({
     console.log('TRACE: Entering controller:InstallerStep2:evaluateStep2 function');
     console.log('TRACE: Entering controller:InstallerStep2:evaluateStep2 function');
     console.log('value of manual install is: ' + this.get('manualInstall'));
     console.log('value of manual install is: ' + this.get('manualInstall'));
 
 
-    var validateResult = this.validateStep2();
+    var validateResult = !this.validateStep2();
 
 
     if (this.get('isSubmitDisabled') === true ) {
     if (this.get('isSubmitDisabled') === true ) {
       console.log("ERROR: error in validation");
       console.log("ERROR: error in validation");

+ 150 - 128
ambari-web/app/controllers/installer/step3_controller.js

@@ -19,116 +19,141 @@ var App = require('app');
 
 
 App.InstallerStep3Controller = Em.ArrayController.extend({
 App.InstallerStep3Controller = Em.ArrayController.extend({
   name: 'installerStep3Controller',
   name: 'installerStep3Controller',
-  hostInfo: [],
-  hostNames: [],
-  category: 'All',
   content: [],
   content: [],
-  /*
-   This flag is "true" While bootstrapping is in process.
-   Parsing function or timeout on bootstrap rest call can make it false.
-   */
-  bootstrap: '',
-  mockData:{},
+  bootHosts: [],
+  isSubmitDisabled: false,
+  categories: ['Hosts', 'Succeeded', 'Failed'],
+  category: 'Hosts',
+  allChecked: true,
+
+  onAllChecked: function () {
+    var hosts = this.visibleHosts();
+    if (this.get('allChecked') === true) {
+      hosts.setEach('isChecked', true);
+    } else {
+      hosts.setEach('isChecked', false);
+    }
+  }.observes('allChecked'),
 
 
+  mockData: require('data/mock/step3_hosts'),
+  mockRetryData: require('data/mock/step3_retry_hosts'),
 
 
-  renderHosts: function() {
+  /* Loads the hostinfo from localStorage on the insertion of view. It's being called from view */
+  loadHosts: function () {
+    this.clear();
     var hostInfo = [];
     var hostInfo = [];
     hostInfo = App.db.getHosts();
     hostInfo = App.db.getHosts();
-    this.hostNames.clear();
-    this.clear();
-
-    this.hostNames = new Ember.Set();
-    for(var index in hostInfo) {
-      this.hostNames.add(hostInfo[index].name);
-     // alert(hostInfo[index].name);
-    }
-    /*hostInfo.forEach(function(_hostNames) {
-      hostNames.add = _hostNames.name;
-    });*/
-    console.log("TRACE: step3->controller->renderHosts");
-/*
-   this.hostInfo = [
-    {
-      hostName: 'jaimin',
-      status:'success'
-    },
-    {
-      hostName: 'jetly',
-      status:'success'
-    },
-    {
-      hostName: 'villa',
-      status:'Verifying SSH connection'
-    },
-    {
-      hostName: 'jack',
-      status:'SSH connection failed'
-    },
-    {
-      hostName: 'george',
-      status:'success'
-    },
-    {
-      hostName: 'maria',
-      status:'success'
-    },
-    {
-      hostName: 'adam',
-      status:'Verifying SSH connection'
-    },
-    {
-      hostName: 'jennifer',
-      status:'SSH connection failed'
-    },
-    {
-      hostName: 'john',
-      status:'success'
-    },
-    {
-      hostName: 'tom',
-      status:'success'
-    },
-    {
-      hostName: 'harry',
-      status:'success'
-    },
-    {
-      hostName: 'susan',
-      status:'success'
+    var hosts = new Ember.Set();
+    for (var index in hostInfo) {
+      hostInfo[index].status = "pending";
+      hosts.add(hostInfo[index]);
     }
     }
-  ];
-  */
-  var self = this;
-    this.hostNames.forEach(function(_hostInfo) {
-        var hostInfo = App.HostInfo.create({
-          hostName: _hostInfo
-        });
+    hosts.forEach(function (_host) {
+      console.log("TRACE: host name is: " + _host.name);
+    });
+    return hosts;
+  },
+
+  /* renders the set of passed hosts */
+  renderHosts: function (hostsInfo) {
+    var self = this;
+    hostsInfo.forEach(function (_hostInfo) {
+      var hostInfo = App.HostInfo.create({
+        hostName: _hostInfo.name,
+        status: _hostInfo.status
+      });
 
 
       console.log('pushing ' + hostInfo.hostName);
       console.log('pushing ' + hostInfo.hostName);
-      //self.set('content',hostInfo);
-      //self.replaceContent(0, hostInfo.get('length'), hostInfo);
       self.content.pushObject(hostInfo);
       self.content.pushObject(hostInfo);
     });
     });
+  },
+
+  /* Below function parses and updates the content, and governs the possibility
+   of the next doBootstrap (polling) call
+   */
+
+  parseHostInfo: function (hostsFrmServer, hostsFrmContent) {
+    var result = true;                    // default value as true implies if the data rendered by REST API has no hosts, polling will stop
+    hostsFrmServer.forEach(function (_hostFrmServer) {
+      var host = hostsFrmContent.findProperty('hostName', _hostFrmServer.name);
+      if (host !== null && host !== undefined) { // check if hostname extracted from REST API data matches any hostname in content
+        host.set('status', _hostFrmServer.status);
+      }
+    });
+    result = !this.content.someProperty('status', 'pending');
+    return result;
+  },
+
+  /* Below function returns the current set of visible hosts on view (All,succeded,failed) */
+  visibleHosts: function () {
+    var result;
+    if (this.get('category') === 'Succeeded') {
+      return (this.filterProperty('status', 'success'));
+    } else if (this.get('category') === 'Failed') {
+      return (this.filterProperty('status', 'error'));
+    } else if (this.get('category') === 'Hosts') {
+      return this.content;
+    }
+  },
 
 
-  //this.startBootstrap();
+  /* Below function removes a single element on the trsah icon click. Being called from view */
+  removeElement: function (hostInfo) {
+    console.log('TRACE: In removeElement');
+    var hosts = [hostInfo];
+    App.db.removeHosts(hosts);    // remove from localStorage
+    this.removeObject(hostInfo);     // remove from the content to rerender the view
   },
   },
 
 
-  /*
-   * Below function will be called on successfully leaving step2 and entering
-   * step3. "Retry" button shall also make use of it.
+
+  retry: function () {
+    if (this.get('isSubmitDisabled')) {
+      return;
+    }
+    var hosts = this.visibleHosts();
+    var selectedHosts = hosts.filterProperty('isChecked', true);
+    selectedHosts.forEach(function (_host) {
+      console.log('Retrying:  ' + _host.hostName);
+    });
+
+    //TODO: uncomment below code to hookup with @GET bootstrap API
+    /*
+     this.set('bootHosts',selectedHosts);
+     this.doBootstrap();
+     */
+
+    //TODO: comment below lines while hooking up with actual @GET bootstrap API
+    var mockHosts = this.mockRetryData;
+    mockHosts.forEach(function (_host) {
+      console.log('Retrying:  ' + _host.name);
+    });
+    this.parseHostInfo(mockHosts, selectedHosts);
+  },
+
+
+  /* Below function checks the category selected and then collects all the
+   selected hosts in that category and deletes them from localStorage,
+   in-memory data structure and controller content
    */
    */
 
 
-  startBootstrap: function () {
-    console.log("TRACE: Entering controller->installer->step3->startBootstrap() function");
-    var self = this;
-    this.set('bootstrap',window.setInterval(function () {
-      self.doBootstrap()
-    }, 5000));
+  /* Removes set of selected visisble hosts on the remove button click */
+  removeHosts: function () {
+    if (this.get('isSubmitDisabled')) {
+      return;
+    }
+    var hostResult = this.visibleHosts();
+    var selectedHosts = hostResult.filterProperty('isChecked', true);
+    selectedHosts.forEach(function (_hostInfo) {
+      console.log('Removing:  ' + _hostInfo.hostName);
+    });
+    App.db.removeHosts(selectedHosts);
+    this.removeObjects(selectedHosts);
   },
   },
 
 
 
 
-  stopBootstrap: function () {
-    window.clearInterval(this.bootstrap);
+  startBootstrap: function () {
+    this.set('isSubmitDisabled', true);
+    this.set('bootHosts', this.get('content'));
+    this.doBootstrap();
   },
   },
 
 
   doBootstrap: function () {
   doBootstrap: function () {
@@ -137,62 +162,59 @@ App.InstallerStep3Controller = Em.ArrayController.extend({
       type: 'GET',
       type: 'GET',
       url: '/ambari_server/api/bootstrap',
       url: '/ambari_server/api/bootstrap',
       async: false,
       async: false,
-      timeout: 2000,
+      timeout: 5000,
       success: function (data) {
       success: function (data) {
         console.log("TRACE: In success function for the GET bootstrap call");
         console.log("TRACE: In success function for the GET bootstrap call");
-
-
+        var result = self.parseHostInfo(data, this.get('bootHosts'));
+        if (result !== true) {
+          window.setTimeout(self.doBootstrap, 3000);
+        } else {
+          self.stopBootstrap();
+        }
       },
       },
+
       error: function () {
       error: function () {
         console.log("ERROR");
         console.log("ERROR");
-
-        self.stopBootstrap(); //Never toggle this for now, flow goes in infinite loop
+        self.stopBootstrap();
       },
       },
+
       statusCode: {
       statusCode: {
         404: function () {
         404: function () {
           console.log("URI not found.");
           console.log("URI not found.");
-          alert("URI not found");
-          result = false;
+          //alert("URI not found");
         }
         }
       },
       },
+
       dataType: 'application/json'
       dataType: 'application/json'
     });
     });
 
 
   },
   },
 
 
-
-
-
-  retry: function () {
-    this.doBootstrap();
+  stopBootstrap: function () {
+    //TODO: uncomment following line after the hook up with the API call
+    // this.set('isSubmitDisabled',false);
   },
   },
 
 
-  remove: function () {
-
+  submit: function () {
+    if (!this.get('isSubmitDisabled')) {
+      App.get('router').transitionTo('step4');
+    }
+  },
+  hostLogPopup: function (event) {
+    App.ModalPopup.show({
+      header: Em.I18n.t('installer.step3.hostLog.popup.header'),
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/installer/step3HostLogPopup')
+      })
+    });
   },
   },
 
 
-  evaluateStep3: function () {
-    // TODO: evaluation at the end of step3
-    /* Not sure if below tasks are to be covered over here
-     * as these functions are meant to be called at the end of a step
-     * and the following tasks are interactive to the page and not on clicking next button.
-     *
-     *
-     * task2 will be a parsing function that on reaching a particular condition(all hosts are in success or faliue status)  will stop task1
-     * task3 will be a function binded to remove button
-     * task4 will be a function binded to retry button
-     *
-     *
-     * keeping it over here for now
-     */
-
-
-    //task1 = start polling with rest API @Get http://ambari_server/api/bootstrap.
-    //task2 = stop polling when all the hosts have either success or failure status.
-    //task3(prerequisite = remove) = Remove set of selected hosts from the localStorage
-    //task4(prerequisite = retry) = temporarily store list of checked host and call to rest API: @Post http://ambari_server/api/bootstrap
-
-    return true;
+  // TODO: dummy button. Remove this after the hook up with actual REST API.
+  mockBtn: function () {
+    this.set('isSubmitDisabled', false);
+    var hostInfo = this.mockData;
+    this.renderHosts(hostInfo);
   }
   }
+
 });
 });
 
 

+ 50 - 0
ambari-web/app/data/mock/step3_hosts.js

@@ -0,0 +1,50 @@
+module.exports = new Ember.Set([
+  {
+    name: '192.168.1.1',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.2',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.3',
+    status: 'pending'
+  },
+  {
+    name: '192.168.1.4',
+    status: 'error'
+  },
+  {
+    name: '192.168.1.5',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.6',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.7',
+    status: 'pending'
+  },
+  {
+    name: '192.168.1.8',
+    status: 'error'
+  },
+  {
+    name: '192.168.1.9',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.10',
+    status: 'pending'
+  },
+  {
+    name: '192.168.1.11',
+    status: 'pending'
+  },
+  {
+    name: '192.168.1.12',
+    status: 'pending'
+  }
+]);

+ 55 - 0
ambari-web/app/data/mock/step3_retry_hosts.js

@@ -0,0 +1,55 @@
+module.exports = new Ember.Set([
+  {
+    name: '192.168.1.1',
+    status: 'error'
+  },
+  {
+    name: '192.168.1.2',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.3',
+    status: 'error'
+  },
+  {
+    name: '192.168.1.4',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.5',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.6',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.7',
+    status: 'success'
+
+  },
+  {
+    name: '192.168.1.8',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.9',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.10',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.11',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.12',
+    status: 'success'
+  },
+  {
+    name: '192.168.1.13',
+    status: 'success'
+  }
+]);

+ 19 - 1
ambari-web/app/ember.js

@@ -1 +1,19 @@
-module.exports = Ember;
+/**
+ * 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.
+ */
+
+module.exports=Ember;

+ 2 - 0
ambari-web/app/messages.js

@@ -82,6 +82,8 @@ Em.I18n.translations = {
   'installer.step3.header': 'Confirm Hosts',
   'installer.step3.header': 'Confirm Hosts',
   'installer.step3.body': 'Here are the results of the host discovery process.<br>' +
   'installer.step3.body': 'Here are the results of the host discovery process.<br>' +
     'Please verify and remove the ones that you do not want to be the part of the cluster.',
     'Please verify and remove the ones that you do not want to be the part of the cluster.',
+  'installer.step3.hostLog.popup.header': 'Log file for the host',
+  'installer.step3.hostLog.popup.body': 'Placeholder for the log file',
 
 
   'installer.step4.header': 'Choose Services',
   'installer.step4.header': 'Choose Services',
   'installer.step4.body': 'Choose which services you want to install on your cluster.<br>Note that some services have dependencies (e.g., HBase requires ZooKeeper.)',
   'installer.step4.body': 'Choose which services you want to install on your cluster.<br>Note that some services have dependencies (e.g., HBase requires ZooKeeper.)',

+ 3 - 1
ambari-web/app/models/hosts.js

@@ -23,6 +23,8 @@ App.HostInfo = Ember.Object.extend({
   status: 'info',
   status: 'info',
   cpu: '',
   cpu: '',
   memory: '',
   memory: '',
-  message: 'starting'
+  message: 'Information',
+  isChecked: true
+
 });
 });
 
 

+ 0 - 1
ambari-web/app/routes/installer.js

@@ -84,7 +84,6 @@ module.exports = Em.Route.extend({
     },
     },
     back: Em.Router.transitionTo('step2'),
     back: Em.Router.transitionTo('step2'),
     next: function (router, context) {
     next: function (router, context) {
-      var result = router.get('installerStep3Controller').evaluateStep3();
       if (result) {
       if (result) {
         console.log('In step3 transiting to step4');
         console.log('In step3 transiting to step4');
         router.transitionTo('step4');
         router.transitionTo('step4');

+ 1 - 1
ambari-web/app/templates/installer/step1.hbs

@@ -34,7 +34,7 @@
 </div>
 </div>
 
 
 <div class="btn-area">
 <div class="btn-area">
-  <a class="btn btn-success" {{bindAttr disabled= "invalidClusterName"}} {{action next}}>Next</a>
+  <a class="btn btn-success pull-right" {{bindAttr disabled= "invalidClusterName"}} {{action next}}>Next &rarr;</a>
 </div>
 </div>
 
 
 
 

+ 6 - 7
ambari-web/app/templates/installer/step2.hbs

@@ -83,7 +83,7 @@
   {{view Ember.Checkbox checkedBinding="localRepo"}}
   {{view Ember.Checkbox checkedBinding="localRepo"}}
 </label>
 </label>
 
 
-{{#if localRepo}}
+{{#if "localRepo"}}
 <div {{bindAttr class="softRepoLocalPathNullErr:error :control-group"}}>
 <div {{bindAttr class="softRepoLocalPathNullErr:error :control-group"}}>
   <div class="alert alert-info">
   <div class="alert alert-info">
     {{t installer.step2.localRepo.info}}
     {{t installer.step2.localRepo.info}}
@@ -98,14 +98,13 @@
 {{/if}}
 {{/if}}
 
 
 <div class="btn-area">
 <div class="btn-area">
-  <a class="btn" {{action back}}>Previous</a>
+  <a class="btn pull-left" {{action back}}>&larr; Back</a>
   {{#if manualInstall}}
   {{#if manualInstall}}
-  <a role="button" class="btn btn-success"
-     style="float:right"
-     data-toggle="modal" {{action evaluateStep2 target="controller"}}>Validate</a>
+  <a role="button" class="btn btn-success pull-right"
+     data-toggle="modal" {{action evaluateStep2 target="controller"}}>Validate &rarr;</a>
   {{else}}
   {{else}}
-  <a class="btn btn-success" style="float:right" {{action next}}> Discover and
-    Validate </a>
+  <a class="btn btn-success pull-right" {{action next}}> Discover and
+    Validate &rarr; </a>
   {{/if}}
   {{/if}}
 </div>
 </div>
 
 

+ 36 - 89
ambari-web/app/templates/installer/step3.hbs

@@ -21,16 +21,29 @@
 <div id="hosts" class="box">
 <div id="hosts" class="box">
   <div class="box-header">
   <div class="box-header">
     <div class="button-section">
     <div class="button-section">
-      <a class="btn btn-primary decommission"
+      <a class="btn btn-primary decommission" {{bindAttr disabled="isSubmitDisabled"}}
          href="#" {{action retry target="controller"}}><i
          href="#" {{action retry target="controller"}}><i
         class="icon-repeat icon-white"></i>
         class="icon-repeat icon-white"></i>
         Retry
         Retry
       </a>
       </a>
-      <a class="btn btn-primary"
-         href="#" {{action remove target="controller" }}><i
+      <a class="btn btn-primary" {{bindAttr disabled="isSubmitDisabled"}}
+         href="#" {{action removeHosts target="controller" }}><i
         class="icon-trash icon-white"></i>
         class="icon-trash icon-white"></i>
         Remove
         Remove
       </a>
       </a>
+      <a class="btn btn-info"
+         href="#" {{action mockBtn target="controller" }}>
+        Mock Data
+      </a>
+
+      <div class="dropdown pull-right">
+        {{view Ember.Select class="pull-right"
+       contentBinding="controller.categories"
+       selectionBinding="controller.category"
+       }}
+        <h5 class="pull-right text-info">Filter By: &nbsp</h5>
+      </div>
+
     </div>
     </div>
   </div>
   </div>
 
 
@@ -39,9 +52,8 @@
     <thead>
     <thead>
     <tr>
     <tr>
       <th>
       <th>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
+        {{view Ember.Checkbox checkedBinding="allChecked"}}
+        {{controller.category}}
       </th>
       </th>
       <th>Status</th>
       <th>Status</th>
       <!--  given by the parsing function that parses data from bootstrap call -->
       <!--  given by the parsing function that parses data from bootstrap call -->
@@ -56,105 +68,40 @@
 
 
 
 
     <tbody>
     <tbody>
+
     {{#each host in controller}}
     {{#each host in controller}}
+    {{#view App.HostView categoryBinding="controller.category" hostInfoBinding="host"}}
+    {{#if view.isVisible}}
     <tr {{bindAttr class = "host.status"}}>
     <tr {{bindAttr class = "host.status"}}>
       <td>
       <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
+        {{view Ember.Checkbox checkedBinding="host.isChecked"}}
       </td>
       </td>
       <td>
       <td>
+        {{host.status}}
       </td>
       </td>
-      </td>
+
       <td>
       <td>
         {{host.hostName}}
         {{host.hostName}}
       </td>
       </td>
       <td>
       <td>
-        {{host.message}}
-      </td>
-      <td>
-        <a href="javascript:void(null)" {{action remove}}><i
-          class="icon-trash"></i></a>
-      </td>
-    </tr>
-    {{/each}}
-    </tbody>
-  </table>
+       <a href=""></a>
+
+        <a  href="javascript:void(null)" data-toggle="modal" {{action "hostLogPopup" target="controller"}}>{{host.message}}</a>
 
 
-  <!--
-    <tr class="warning">
-      <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
-      </td>
-      <td></td>
-      <td>ip-12-12-12-1231.co2.ama.internal</td>
-      <td>Verifying SSH connection</td>
-      <td>
-        <a href="javascript:void(null)" {{action remove}}><i
-          class="icon-trash"></i></a>
-      </td>
-    </tr>
-    <tr class="success">
-      <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
-      </td>
-      <td></td>
-      <td>ip-12-12-12-1232.co2.ama.internal</td>
-      <td>Succeed</td>
-      <td>
-        <a href="javascript:void(null)" {{action remove}}><i
-          class="icon-trash"></i></a>
-      </td>
-    </tr>
-    <tr class="warning">
-      <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
-      </td>
-      <td></td>
-      <td>ip-12-12-12-1233.co2.ama.internal</td>
-      <td>Verifying SSH connection</td>
-      <td>
-        <a href="javascript:void(null)" {{action remove}}><i
-          class="icon-trash"></i></a>
-      </td>
-    </tr>
-    <tr class="warning">
-      <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
-      </td>
-      <td></td>
-      <td>ip-12-12-12-1234.co2.ama.internal</td>
-      <td>Verifying SSH connection</td>
-      <td>
-        <a href="javascript:void(null)" {{action remove}}><i
-          class="icon-trash"></i></a>
-      </td>
-    </tr>
-    <tr class="error">
-      <td>
-        <label class="checkbox">
-          <input type="checkbox">
-        </label>
       </td>
       </td>
-      <td></td>
-      <td>ip-12-12-12-1235.co2.ama.internal</td>
-      <td>ssh connection failed</td>
       <td>
       <td>
-        <a href="javascript:void(null)" {{action remove}}><i
+        <a href="javascript:void(null)" {{action removeItem target="view"}}><i
           class="icon-trash"></i></a>
           class="icon-trash"></i></a>
       </td>
       </td>
     </tr>
     </tr>
+    {{/if}}
+    {{/view}}
+
+
+
+    {{/each}}
     </tbody>
     </tbody>
   </table>
   </table>
-  -->
   <div class="box-footer">
   <div class="box-footer">
     <hr/>
     <hr/>
     <div class="footer-pagination">
     <div class="footer-pagination">
@@ -162,6 +109,6 @@
   </div>
   </div>
 </div>
 </div>
 <div class="btn-area">
 <div class="btn-area">
-  <a class="btn" {{action back}}>Previous</a>
-  <a class="btn btn-success" style="float:right" {{action next}}>Next</a>
+  <a class="btn pull-left" {{bindAttr disabled="isSubmitDisabled"}} {{action back}}>&larr; Previous</a>
+  <a class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}} {{action submit target="controller"}}>Next &rarr;</a>
 </div>
 </div>

+ 1 - 0
ambari-web/app/templates/installer/step3HostLogPopup.hbs

@@ -0,0 +1 @@
+<p>{{t installer.step3.hostLog.popup.body}}</p>

+ 2 - 2
ambari-web/app/templates/installer/step4.hbs

@@ -50,7 +50,7 @@
   </table>
   </table>
 
 
   <div class="btn-area">
   <div class="btn-area">
-    <a class="btn" {{action back}}>Back</a>
-    <a class="btn btn-success" style="float:right" {{action submit target="controller"}}>Next</a>
+    <a class="btn pull-left" {{action back}}>&larr; Back</a>
+    <a class="btn btn-success pull-right" {{action submit target="controller"}}>Next &rarr;</a>
   </div>
   </div>
 </div>
 </div>

+ 13 - 3
ambari-web/app/utils/db.js

@@ -154,9 +154,19 @@ App.db.setSoftRepo = function(softRepo) {
 	var user = App.db.data.app.loginName;
 	var user = App.db.data.app.loginName;
 	if(App.db.data[user] == undefined) {
 	if(App.db.data[user] == undefined) {
 		App.db.data[user] = {'name':user};
 		App.db.data[user] = {'name':user};
-		App.db.data[user].Installer.softRepo = softRepo;
-		localStorage.setObject('ambari',App.db.data);
 	}
 	}
+  App.db.data[user].Installer.softRepo = softRepo;
+  localStorage.setObject('ambari',App.db.data);
+}
+
+App.db.removeHosts = function(hostInfo) {
+  console.log('TRACE: Entering db:setSoftRepo function');
+  var hostList = App.db.getHosts();
+  hostInfo.forEach(function(_hostInfo) {
+    var host = _hostInfo.hostName;
+    delete hostList[host];
+  });
+  App.db.setHosts(hostList);
 }
 }
 
 
 App.db.setSelectedServiceNames = function(serviceNames) {
 App.db.setSelectedServiceNames = function(serviceNames) {
@@ -238,7 +248,7 @@ App.db.isCompleted = function() {
   return App.db.data[user].Installer.completed;
   return App.db.data[user].Installer.completed;
 }
 }
 
 
-App.db.getHosts = function(name,hostInfo) {
+App.db.getHosts = function() {
 	console.log('TRACE: Entering db:getHosts function');
 	console.log('TRACE: Entering db:getHosts function');
   App.db.data = localStorage.getObject('ambari');
   App.db.data = localStorage.getObject('ambari');
   var user = App.db.data.app.loginName;
   var user = App.db.data.app.loginName;

+ 33 - 4
ambari-web/app/views/installer/step3_view.js

@@ -22,14 +22,43 @@ var App = require('app');
 App.InstallerStep3View = Em.View.extend({
 App.InstallerStep3View = Em.View.extend({
 
 
   templateName: require('templates/installer/step3'),
   templateName: require('templates/installer/step3'),
+  category: '',
 
 
   didInsertElement: function () {
   didInsertElement: function () {
     $("[rel=popover]").popover({'placement': 'right', 'trigger': 'hover'});
     $("[rel=popover]").popover({'placement': 'right', 'trigger': 'hover'});
-    var result = this.get('controller');
     console.log("TRACE: step3->didInsetElement");
     console.log("TRACE: step3->didInsetElement");
-    var result = this.get('controller');
-    result.renderHosts();
+    var controller = this.get('controller');
+    var hosts = controller.loadHosts();
+    controller.renderHosts(hosts);
+    controller.startBootstrap();
     console.log("TRACE: Back to step3 view");
     console.log("TRACE: Back to step3 view");
   }
   }
+});
 
 
-});
+App.HostView = Em.View.extend({
+
+  isVisible: true,
+  category: 'Hosts',
+  removeItem: function () {
+    var hostInfo = this.get('hostInfo');
+    this.get('controller').removeElement(hostInfo);
+
+  },
+
+  hideItem: function () {
+    var controller = this.get('controller');
+    var hostInfo = this.get('hostInfo');
+    var category = this.get('category');
+    if (category === "Hosts") {
+      this.set('isVisible', true);
+    } else if (category === "Succeeded" && hostInfo.get('status') == "success") {
+      this.set('isVisible', true);
+      return true;
+    } else if (category === "Failed" && hostInfo.get('status') == "error") {
+      this.set('isVisible', true);
+    } else {
+      console.log("TRACE: In View->hideItem->false condition item....");
+      this.set('isVisible', false);
+    }
+  }.observes('category')
+});

+ 88 - 1
ambari-web/test/installer/step3_test.js

@@ -16,4 +16,91 @@
  * limitations under the License.
  * limitations under the License.
  */
  */
 
 
-// TODO
+
+var Ember = require('ember');
+var App = require('app');
+require('models/hosts');
+require('controllers/installer/step3_controller');
+
+describe('App.InstallerStep3Controller', function () {
+  //var controller = App.InstallerStep3Controller.create();
+
+  describe('#parseHostInfo', function () {
+    var controller = App.InstallerStep3Controller.create();
+    it('should return true if there is no host with pending status in the data provided by REST bootstrap call.It should also update the status on the client side', function () {
+      var hostFrmServer = [
+        {
+          name: '192.168.1.1',
+          status: 'error'
+        },
+        {
+          name: '192.168.1.2',
+          status: 'success'
+        },
+        {
+          name: '192.168.1.3',
+          status: 'error'
+        },
+        {
+          name: '192.168.1.4',
+          status: 'success'
+        }
+      ];
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.1',
+        status: 'error'
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.2',
+        status: 'success'
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.3',
+        status: 'pending'        //status should be overriden to 'error' after the parseHostInfo call
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.4',
+        status: 'success'
+      }));
+
+      var result = controller.parseHostInfo(hostFrmServer, controller.content);
+      var host = controller.content.findProperty('hostName', '192.168.1.3');
+      expect(result).to.equal(true);
+      expect(host.status).to.equal('error');
+    })
+  })
+
+
+  describe('#onAllChecked', function () {
+    var controller = App.InstallerStep3Controller.create();
+    it('should set all hosts to true on checking a global checkbox', function () {
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.1',
+        status: 'error',
+        isChecked: false
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.2',
+        status: 'success',
+        isChecked: false
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.3',
+        status: 'pending', //status should be overriden to 'error' after the parseHostInfo call
+        isChecked: true
+      }));
+      controller.content.pushObject(App.HostInfo.create({
+        hostName: '192.168.1.4',
+        status: 'success',
+        isChecked: false
+      }));
+      controller.onAllChecked();
+      controller.content.forEach(function (hostName) {
+        var result = hostName.isChecked;
+        expect(result).to.equal(true);
+      });
+
+    })
+  })
+})
+