瀏覽代碼

AMBARI-757. Implement Installer Step 4. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1387450 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 年之前
父節點
當前提交
4f1aa39ea2

+ 2 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,8 @@ AMBARI-666 branch (unreleased changes)
 
 
   NEW FEATURES
   NEW FEATURES
 
 
+  AMBARI-757. Implement Installer Step 4 (Select Services). (yusaku)
+
   AMBARI-751. Re-structure servicecomponenthost fsm layout. (hitesh)
   AMBARI-751. Re-structure servicecomponenthost fsm layout. (hitesh)
 
 
   AMBARI-747. Add unit tests for step2 (Install option page) of installer.
   AMBARI-747. Add unit tests for step2 (Install option page) of installer.

+ 2 - 1
ambari-web/app/controllers.js

@@ -25,8 +25,9 @@ require('controllers/installer');
 require('controllers/installer/step1_controller');
 require('controllers/installer/step1_controller');
 require('controllers/installer/step2_controller');
 require('controllers/installer/step2_controller');
 require('controllers/installer/step3_controller');
 require('controllers/installer/step3_controller');
+require('controllers/installer/step4_controller');
 require('controllers/installer/step7_controller');
 require('controllers/installer/step7_controller');
 require('controllers/main');
 require('controllers/main');
 require('controllers/main/service');
 require('controllers/main/service');
 require('controllers/main/service/item');
 require('controllers/main/service/item');
-require('controllers/main/alert');
+require('controllers/main/alert');

+ 144 - 0
ambari-web/app/controllers/installer/step4_controller.js

@@ -0,0 +1,144 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+var db = require('utils/db');
+
+App.InstallerStep4Controller = Em.ArrayController.extend({
+  name: 'installerStep4Controller',
+  rawContent: [
+    {
+      serviceName: 'HDFS',
+      displayName: 'HDFS',
+      isDisabled: true,
+      description: Em.I18n.t('services.hdfs.description')
+    },
+    {
+      serviceName: 'MAPREDUCE',
+      displayName: 'MapReduce',
+      isDisabled: true,
+      description: Em.I18n.t('services.mapreduce.description')
+    },
+    {
+      serviceName: 'NAGIOS',
+      displayName: 'Nagios',
+      isDisabled: true,
+      description: Em.I18n.t('services.nagios.description')
+    },
+    {
+      serviceName: 'GANGLIA',
+      displayName: 'Ganglia',
+      isDisabled: true,
+      description: Em.I18n.t('services.ganglia.description')
+    },
+    {
+      serviceName: 'HIVE',
+      displayName: 'Hive + HCatalog',
+      isDisabled: false,
+      description: Em.I18n.t('services.hive.description')
+    },
+    {
+      serviceName: 'HBASE',
+      displayName: 'HBase + ZooKeeper',
+      isDisabled: false,
+      description: Em.I18n.t('services.hbase.description')
+    },
+    {
+      serviceName: 'PIG',
+      displayName: 'Pig',
+      isDisabled: false,
+      description: Em.I18n.t('services.pig.description')
+    },
+    {
+      serviceName: 'SQOOP',
+      displayName: 'Sqoop',
+      isDisabled: false,
+      description: Em.I18n.t('services.sqoop.description')
+    },
+    {
+      serviceName: 'OOZIE',
+      displayName: 'Oozie',
+      isDisabled: false,
+      description: Em.I18n.t('services.oozie.description')
+    },
+    {
+      serviceName: 'ZOOKEEPER',
+      isDisabled: false,
+      isHidden: true
+    },
+    {
+      serviceName: 'HCATALOG',
+      isDisabled: false,
+      isHidden: true
+    }
+  ],
+
+  content: [],
+
+  isAll: function() {
+    return this.everyProperty('isSelected', true);
+  }.property('@each.isSelected'),
+
+  isMinimum: function() {
+    return this.filterProperty('isDisabled', false).everyProperty('isSelected', false);
+  }.property('@each.isSelected'),
+
+  checkDependencies: function() {
+    var hbase = this.findProperty('serviceName', 'HBASE');
+    var zookeeper = this.findProperty('serviceName', 'ZOOKEEPER');
+    if (hbase && zookeeper) {
+      zookeeper.set('isSelected', hbase.get('isSelected'));
+    }
+    var hive = this.findProperty('serviceName', 'HIVE');
+    var hcatalog = this.findProperty('serviceName', 'HCATALOG');
+    if (hive && hcatalog) {
+      hcatalog.set('isSelected', hive.get('isSelected'));
+    }
+  }.observes('@each.isSelected'),
+
+  init: function() {
+    this._super();
+    // wrap each item with Ember.Object
+    this.rawContent.forEach(function(item) {
+      item.isSelected = true;
+      this.pushObject(Ember.Object.create(item));
+    }, this);
+  },
+
+  selectAll: function() {
+    this.setEach('isSelected', true);
+  },
+
+  selectMinimum: function() {
+    this.filterProperty('isDisabled', false).setEach('isSelected', false);
+  },
+
+  saveSelectedServiceNamesToDB: function() {
+    var serviceNames = [];
+    this.filterProperty('isSelected', true).forEach(function(item){
+      serviceNames.push(item.serviceName);
+    });
+    db.setSelectedServiceNames(serviceNames);
+  },
+
+  submit: function() {
+    this.saveSelectedServiceNamesToDB();
+    App.router.send('next');
+  }
+
+})

+ 103 - 142
ambari-web/app/controllers/installer/step7_controller.js

@@ -18,6 +18,16 @@
 
 
 var App = require('app');
 var App = require('app');
 
 
+/**
+ * Controller for Step 7 of the Installer Wizard.
+ * By Step 7, we have the following information stored in App.db (localStorage) and set on this
+ * controller by the router.
+ *
+ *   selectedServices: App.db.selectedServices (the services that the user selected in Step 4)
+ *   masterComponentHosts: App.db.masterComponentHosts (master-components-to-hosts mapping the user selected in Step 5)
+ *   slaveComponentHosts: App.db.slaveComponentHosts (slave-components-to-hosts mapping the user selected in Step 6)
+ *
+ */
 App.InstallerStep7Controller = Em.ArrayController.extend({
 App.InstallerStep7Controller = Em.ArrayController.extend({
 
 
   name: 'installerStep7Controller',
   name: 'installerStep7Controller',
@@ -26,177 +36,128 @@ App.InstallerStep7Controller = Em.ArrayController.extend({
 
 
   selectedService: null,
   selectedService: null,
 
 
-  selectedSlaveHosts: null,
+  slaveHostToGroup: null,
 
 
   isSubmitDisabled: function() {
   isSubmitDisabled: function() {
     return !this.everyProperty('errorCount', 0);
     return !this.everyProperty('errorCount', 0);
   }.property('@each.errorCount'),
   }.property('@each.errorCount'),
 
 
-  init: function () {
-    // TODO: get selected services from previous step
-
-    var selectedServices = [ 'HDFS', 'MapReduce', 'Ganglia', 'Nagios', 'HBase', 'Pig', 'Sqoop', 'Oozie', 'Hive', 'Templeton', 'ZooKeeper'];
-
-    var configProperties = App.ConfigProperties.create();
-    var mockData = [
-      {
-        serviceName: 'Nagios',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'General'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'NAGIOS')
-      },
-      {
-        serviceName: 'Hive/HCat',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'Hive Metastore'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'HIVE')
-      },
-      {
-        serviceName: 'HDFS',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'NameNode'}),
-          App.ServiceConfigCategory.create({ name: 'SNameNode'}),
-          App.ServiceConfigCategory.create({ name: 'DataNode'}),
-          App.ServiceConfigCategory.create({ name: 'General'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'HDFS')
-        /*
-        [
-          {
-            name: 'ambari.namenode.host',
-            displayName: 'NameNode host',
-            value: 'host0001.com.com',
-            defaultValue: '',
-            description: 'The host that has been assigned to run NameNode',
-            displayType: 'masterHost',
-            category: 'NameNode'
-          },
-          {
-            name: 'ambari.snamenode.host',
-            displayName: 'SNameNode host',
-            value: 'host0002.com.com',
-            defaultValue: '',
-            description: 'The host that has been assigned to run Secondary NameNode',
-            displayType: 'masterHost',
-            category: 'SNameNode'
-          },
-          {
-            name: 'ambari.datanode.hosts',
-            displayName: 'DataNode hosts',
-            value: [ 'host0003.com.com', 'host0004.com.com', 'host0005.com.com' ],
-            defaultValue: '',
-            description: 'The hosts that have been assigned to run DataNodes',
-            displayType: 'slaveHosts',
-            category: 'DataNode'
-          }
-        ]
-        */
-      },
-      {
-        serviceName: 'MapReduce',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'JobTracker'}),
-          App.ServiceConfigCategory.create({ name: 'TaskTracker'}),
-          App.ServiceConfigCategory.create({ name: 'General'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'MAPREDUCE')
-      },
-      {
-        serviceName: 'HBase',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'HBase Master'}),
-          App.ServiceConfigCategory.create({ name: 'RegionServer'}),
-          App.ServiceConfigCategory.create({ name: 'General'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'HBASE')
-      },
-      {
-        serviceName: 'ZooKeeper',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'ZooKeeper Server'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'ZOOKEEPER')
-      },
-      {
-        serviceName: 'Oozie',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'Oozie Server'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'OOZIE')
-      },
-      {
-        serviceName: 'Templeton',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'Templeton Server'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'TEMPLETON')
-      },
-      {
-        serviceName: 'Misc',
-        configCategories: [
-          App.ServiceConfigCategory.create({ name: 'General'}),
-          App.ServiceConfigCategory.create({ name: 'Advanced'})
-        ],
-        configs: configProperties.filterProperty('serviceName', 'MISC')
-      }
+  // TODO: set attributes from localStorage in router
+  // var selectedServiceNames = App.db.getSelectedServiceNames();
+  selectedServiceNames: [ 'HDFS', 'MAPREDUCE', 'GANGLIA', 'NAGIOS', 'HBASE', 'PIG', 'SQOOP', 'OOZIE', 'HIVE', 'ZOOKEEPER'],
+  masterComponentHosts: '',
+  slaveComponentHosts: '',
 
 
-    ];
+  doInit: true,
 
 
-    var self = this;
+  loadConfigs: function() {
 
 
-    mockData.forEach(function(_serviceConfig) {
-      var serviceConfig = App.ServiceConfig.create({
-        serviceName: _serviceConfig.serviceName,
-        configCategories: _serviceConfig.configCategories,
-        configs: []
-      });
-      _serviceConfig.configs.forEach(function(_serviceConfigProperty) {
-        var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
-        serviceConfigProperty.serviceConfig = serviceConfig;
-        serviceConfig.configs.pushObject(serviceConfigProperty);
-        serviceConfigProperty.validate();
+    var selectedServiceNamesInDB = App.db.getSelectedServiceNames();
+    if (selectedServiceNamesInDB !== undefined) {
+      this.set('selectedServiceNames', selectedServiceNamesInDB);
+    }
+    // TODO: check App.db to see if configs have been saved already
+    if (this.doInit) {
+      var serviceConfigs = require('data/service_configs');
+
+      var self = this;
+
+      this.set('content', []);
+
+      serviceConfigs.forEach(function(_serviceConfig) {
+        var serviceConfig = App.ServiceConfig.create({
+          serviceName: _serviceConfig.serviceName,
+          displayName: _serviceConfig.displayName,
+          configCategories: _serviceConfig.configCategories,
+          configs: []
+        });
+
+        if (self.selectedServiceNames.contains(serviceConfig.serviceName) || serviceConfig.serviceName === 'MISC') {
+          _serviceConfig.configs.forEach(function(_serviceConfigProperty) {
+            var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
+            serviceConfigProperty.serviceConfig = serviceConfig;
+            serviceConfig.configs.pushObject(serviceConfigProperty);
+            serviceConfigProperty.validate();
+          });
+
+          console.log('pushing ' + serviceConfig.serviceName);
+          self.content.pushObject(serviceConfig);
+        } else {
+          console.log('skipping ' + serviceConfig.serviceName);
+        }
       });
       });
 
 
-      console.log('pushing ' + serviceConfig.serviceName);
-      self.content.pushObject(serviceConfig);
-    });
-
-    this.set('selectedService', this.objectAt(0));
+      this.set('selectedService', this.objectAt(0));
+      this.doInit = false;
+    }
   },
   },
 
 
   submit: function() {
   submit: function() {
     if (!this.get('isSubmitDisabled')) {
     if (!this.get('isSubmitDisabled')) {
+      // TODO:
+      // save service configs in App.db (localStorage)
       App.get('router').transitionTo('step8');
       App.get('router').transitionTo('step8');
     }
     }
   },
   },
 
 
-  showSlaveHosts: function(event) {
-    this.set('selectedSlaveHosts', event.context);
+  showMasterHosts: function(event) {
+    var serviceConfig = event.context;
     App.ModalPopup.show({
     App.ModalPopup.show({
-      header: 'Slave Hosts',
+      header: serviceConfig.category + ' Hosts',
       bodyClass: Ember.View.extend({
       bodyClass: Ember.View.extend({
-        templateName: require('templates/installer/slaveHostsMatrix')
+        serviceConfig: serviceConfig,
+        templateName: require('templates/installer/master_hosts_popup')
       })
       })
     });
     });
   },
   },
 
 
-  addSlaveComponentGroup: function(event) {
+  showSlaveHosts: function(event) {
+    var serviceConfig = event.context;
     App.ModalPopup.show({
     App.ModalPopup.show({
-      header: 'Add a ' + event.context + ' Group',
+      header: serviceConfig.category + ' Hosts',
       bodyClass: Ember.View.extend({
       bodyClass: Ember.View.extend({
-        templateName: require('templates/installer/slaveHostsMatrix')
+        serviceConfig: serviceConfig,
+        templateName: require('templates/installer/slave_hosts_popup')
       })
       })
     });
     });
   }
   }
 
 
 });
 });
 
 
+App.SlaveComponentGroupsController = Ember.ArrayController.extend({
+
+  name: 'slaveComponentGroupsController',
+
+  // TODO: Set up binding to use actual data
+  //contentBinding: 'App.router.installerStep7Controller.slaveComponentHosts',
+  content: require('data/mock/slave_component_hosts'),
+
+  selectedComponentName: 'DataNode',
+
+  showAddSlaveComponentGroup: function (event) {
+    var componentName = event.context;
+    this.set('selectedComponentName', componentName);
+    App.ModalPopup.show({
+      header: componentName + ' Groups',
+      bodyClass: Ember.View.extend({
+        controllerBinding: 'App.router.slaveComponentGroupsController',
+        templateName: require('templates/installer/slave_hosts_popup')
+      }),
+      onPrimary: function() {
+      }
+    });
+  },
+
+  showEditSlaveComponentGroups: function(event) {
+    this.showAddSlaveComponentGroup(event);
+  },
+
+  hosts: function() {
+    return this.filterProperty('componentName', this.get('selectedComponentName'))[0].hosts;
+  }.property('@each.hosts'),
+
+  groups: function() {
+    return this.filterProperty('componentName', this.get('selectedComponentName'))[0].hosts.mapProperty('group').uniq();
+  }.property('@each.hosts')
+
+});

+ 156 - 128
ambari-web/app/data/config_properties.js

@@ -90,6 +90,7 @@ module.exports =
       "description": "Directory for HBase logs",
       "description": "Directory for HBase logs",
       "defaultValue": "/var/log/hbase",
       "defaultValue": "/var/log/hbase",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HBASE",
       "serviceName": "HBASE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -99,15 +100,7 @@ module.exports =
       "description": "Directory in which the pid files for HBase processes will be created",
       "description": "Directory in which the pid files for HBase processes will be created",
       "defaultValue": "/var/run/hbase",
       "defaultValue": "/var/run/hbase",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "serviceName": "HBASE",
-      "category": "Advanced"
-    },
-    {
-      "name": "hbase_user",
-      "displayName": "HBase User",
-      "description": "User to run HBase as",
-      "defaultValue": "hbase",
-      "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HBASE",
       "serviceName": "HBASE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -267,6 +260,7 @@ module.exports =
       "description": "Directory on the local filesystem where the Secondary NameNode should store the temporary images to merge",
       "description": "Directory on the local filesystem where the Secondary NameNode should store the temporary images to merge",
       "defaultValue": "",
       "defaultValue": "",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "serviceName": "HDFS",
       "category": "SNameNode"
       "category": "SNameNode"
     },
     },
@@ -291,29 +285,22 @@ module.exports =
       "category": "DataNode"
       "category": "DataNode"
     },
     },
     {
     {
-      "name": "hdfs_log_dir",
-      "displayName": "HDFS Log Dir",
-      "description": "Directory for HDFS log files",
-      "defaultValue": "/var/log/hdfs",
-      "isReconfigurable": false,
-      "serviceName": "HDFS",
-      "category": "Advanced"
-    },
-    {
-      "name": "hdfs_pid_dir",
-      "displayName": "HDFS PID Dir",
-      "description": "Directory in which the PID files for HDFS processes will be created",
-      "defaultValue": "/var/run/hdfs",
+      "name": "hdfs_log_dir_prefix",
+      "displayName": "HDFS Log Dir Prefix",
+      "description": "The parent directory for HDFS log files.  The actual directory will be ${hdfs_log_dir_prefix}/${hdfs_user}",
+      "defaultValue": "/var/log/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "serviceName": "HDFS",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
     {
     {
-      "name": "hdfs_user",
-      "displayName": "HDFS User",
-      "description": "User to run HDFS as",
-      "defaultValue": "hdfs",
+      "name": "hdfs_pid_dir_prefix",
+      "displayName": "HDFS PID Dir Prefix",
+      "description": "The parent directory in which the PID files for HDFS processes will be created.  The actual directory will be ${hdfs_log_dir_prefix}/${hdfs_user}",
+      "defaultValue": "/var/run/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "serviceName": "HDFS",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -437,6 +424,7 @@ module.exports =
       "defaultValue": "",
       "defaultValue": "",
       "isRequired": false,
       "isRequired": false,
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "host",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
       "category": "Hive Metastore"
     },
     },
@@ -446,6 +434,7 @@ module.exports =
       "description": "MySQL database name used as the Hive Metastore",
       "description": "MySQL database name used as the Hive Metastore",
       "defaultValue": "hive",
       "defaultValue": "hive",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "host",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
       "category": "Hive Metastore"
     },
     },
@@ -455,6 +444,7 @@ module.exports =
       "description": "MySQL user to use to connect to the MySQL database",
       "description": "MySQL user to use to connect to the MySQL database",
       "defaultValue": "hive",
       "defaultValue": "hive",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "user",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
       "category": "Hive Metastore"
     },
     },
@@ -474,6 +464,7 @@ module.exports =
       "description": "Directory for Hive log files",
       "description": "Directory for Hive log files",
       "defaultValue": "/var/log/hive",
       "defaultValue": "/var/log/hive",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -483,15 +474,7 @@ module.exports =
       "description": "Directory in which the PID files for Hive processes will be created",
       "description": "Directory in which the PID files for Hive processes will be created",
       "defaultValue": "/var/run/hive",
       "defaultValue": "/var/run/hive",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "serviceName": "HIVE",
-      "category": "Advanced"
-    },
-    {
-      "name": "hive_user",
-      "displayName": "Hive User",
-      "description": "User to run Hive as",
-      "defaultValue": "hive",
-      "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -501,6 +484,7 @@ module.exports =
       "description": "Directory for HCatalog log files",
       "description": "Directory for HCatalog log files",
       "defaultValue": "/var/log/hcatalog",
       "defaultValue": "/var/log/hcatalog",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -510,15 +494,7 @@ module.exports =
       "description": "Directory in which the PID files for HCatalog processes will be created",
       "description": "Directory in which the PID files for HCatalog processes will be created",
       "defaultValue": "/var/run/hcatalog",
       "defaultValue": "/var/run/hcatalog",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "serviceName": "HIVE",
-      "category": "Advanced"
-    },
-    {
-      "name": "hcat_user",
-      "displayName": "HCat User",
-      "description": "User to run HCatalog as",
-      "defaultValue": "hcat",
-      "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "serviceName": "HIVE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -562,29 +538,22 @@ module.exports =
       "serviceName": "MAPREDUCE"
       "serviceName": "MAPREDUCE"
     },
     },
     {
     {
-      "name": "mapred_log_dir",
-      "displayName": "MapReduce Log Dir",
-      "description": "Directory for MapReduce log files",
-      "defaultValue": "/var/log/mapred",
-      "isReconfigurable": false,
-      "serviceName": "MAPREDUCE",
-      "category": "Advanced"
-    },
-    {
-      "name": "mapred_pid_dir",
-      "displayName": "MapReduce PID Dir",
-      "description": "Directory in which the PID files for MapReduce processes will be created",
-      "defaultValue": "/var/run/mapred",
+      "name": "mapred_log_dir_prefix",
+      "displayName": "MapReduce Log Dir Prefix",
+      "description": "The parent directory for MapReduce log files.  The actual directory will be ${mapred_log_dir_prefix}/${mapred_user}",
+      "defaultValue": "/var/log/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MAPREDUCE",
       "serviceName": "MAPREDUCE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
     {
     {
-      "name": "mapred_user",
-      "displayName": "MapReduce User",
-      "description": "User to run MapReduce as",
-      "defaultValue": "mapred",
+      "name": "mapred_pid_dir_prefix",
+      "displayName": "MapReduce PID Dir Prefix",
+      "description": "The parent directory in which the PID files for MapReduce processes will be created.  The actual directory will be ${mapred_log_dir_prefix}/${mapred_user}",
+      "defaultValue": "/var/run/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MAPREDUCE",
       "serviceName": "MAPREDUCE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -593,6 +562,7 @@ module.exports =
       "displayName": "MapReduce Capacity Scheduler",
       "displayName": "MapReduce Capacity Scheduler",
       "description": "The scheduler to use for scheduling of MapReduce jobs",
       "description": "The scheduler to use for scheduling of MapReduce jobs",
       "defaultValue": "org.apache.hadoop.mapred.CapacityTaskScheduler",
       "defaultValue": "org.apache.hadoop.mapred.CapacityTaskScheduler",
+      "displayType": "directory",
       "serviceName": "MAPREDUCE"
       "serviceName": "MAPREDUCE"
     },
     },
     {
     {
@@ -784,14 +754,17 @@ module.exports =
       "defaultValue": "",
       "defaultValue": "",
       "isRequired": false,
       "isRequired": false,
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC"
       "serviceName": "MISC"
     },
     },
+    /*
     {
     {
       "name": "hadoop_log_dir",
       "name": "hadoop_log_dir",
       "displayName": "Hadoop Log Dir",
       "displayName": "Hadoop Log Dir",
       "description": "Directory for Hadoop log files",
       "description": "Directory for Hadoop log files",
       "defaultValue": "/var/log/hadoop",
       "defaultValue": "/var/log/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC",
       "serviceName": "MISC",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -801,25 +774,128 @@ module.exports =
       "description": "Directory in which the pid files for Hadoop processes will be created",
       "description": "Directory in which the pid files for Hadoop processes will be created",
       "defaultValue": "/var/run/hadoop",
       "defaultValue": "/var/run/hadoop",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC",
       "serviceName": "MISC",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
+    */
+    {
+      "name": "using_local_repo",
+      "displayName": "Whether a local repo is being used",
+      "description": "Whether a local repo is being used",
+      "defaultValue": false,
+      "isReconfigurable": false,
+      "displayType": "checkbox",
+      "serviceName": "MISC"
+    },
     {
     {
       "name": "yum_repo_file",
       "name": "yum_repo_file",
       "displayName": "Path to local repo file",
       "displayName": "Path to local repo file",
       "description": "Path to local repository file that configures from where to download software packages",
       "description": "Path to local repository file that configures from where to download software packages",
       "defaultValue": "/etc/yum.repos.d/hdp.repo",
       "defaultValue": "/etc/yum.repos.d/hdp.repo",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC"
       "serviceName": "MISC"
     },
     },
     {
     {
-      "name": "using_local_repo",
-      "displayName": "Whether a local repo is being used",
-      "description": "Whether a local repo is being used",
-      "defaultValue": false,
+      "name": "hdfs_user",
+      "displayName": "HDFS User",
+      "description": "User to run HDFS as",
+      "defaultValue": "hdfs",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "displayType": "checkbox",
-      "serviceName": "MISC"
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "mapred_user",
+      "displayName": "MapReduce User",
+      "description": "User to run MapReduce as",
+      "defaultValue": "mapred",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "hbase_user",
+      "displayName": "HBase User",
+      "description": "User to run HBase as",
+      "defaultValue": "hbase",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "hive_user",
+      "displayName": "Hive User",
+      "description": "User to run Hive as",
+      "defaultValue": "hive",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "hcat_user",
+      "displayName": "HCat User",
+      "description": "User to run HCatalog as",
+      "defaultValue": "hcat",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "oozie_user",
+      "displayName": "Oozie User",
+      "description": "User to run Oozie as",
+      "defaultValue": "oozie",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "pig_user",
+      "displayName": "Pig User",
+      "description": "User to run Pig as",
+      "defaultValue": "pig",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "sqoop_user",
+      "displayName": "Sqoop User",
+      "description": "User to run Sqoop as",
+      "defaultValue": "sqoop",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "zk_user",
+      "displayName": "ZooKeeper User",
+      "description": "User to run ZooKeeper as",
+      "defaultValue": "zookeeper",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
+    },
+    {
+      "name": "user_group",
+      "displayName": "Group",
+      "description": "Group that the users specified above belong to",
+      "defaultValue": "hadoop",
+      "isReconfigurable": false,
+      "displayType": "user",
+      "serviceName": "MISC",
+      "category": "Users/Groups"
     },
     },
     {
     {
       "name": "nagios_web_login",
       "name": "nagios_web_login",
@@ -827,6 +903,7 @@ module.exports =
       "description": "Nagios Web UI Admin username",
       "description": "Nagios Web UI Admin username",
       "defaultValue": "nagiosadmin",
       "defaultValue": "nagiosadmin",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "user",
       "serviceName": "NAGIOS"
       "serviceName": "NAGIOS"
     },
     },
     {
     {
@@ -862,6 +939,7 @@ module.exports =
       "description": "Data directory in which the Oozie DB exists",
       "description": "Data directory in which the Oozie DB exists",
       "defaultValue": "",
       "defaultValue": "",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "OOZIE",
       "serviceName": "OOZIE",
       "category": "Oozie Server"
       "category": "Oozie Server"
     },
     },
@@ -871,6 +949,7 @@ module.exports =
       "description": "Directory for oozie logs",
       "description": "Directory for oozie logs",
       "defaultValue": "/var/log/oozie",
       "defaultValue": "/var/log/oozie",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "OOZIE",
       "serviceName": "OOZIE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -880,15 +959,7 @@ module.exports =
       "description": "Directory in which the pid files for oozie processes will be created",
       "description": "Directory in which the pid files for oozie processes will be created",
       "defaultValue": "/var/run/oozie",
       "defaultValue": "/var/run/oozie",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "serviceName": "OOZIE",
-      "category": "Advanced"
-    },
-    {
-      "name": "oozie_user",
-      "displayName": "Oozie User",
-      "description": "User to run Oozie as",
-      "defaultValue": "oozie",
-      "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "OOZIE",
       "serviceName": "OOZIE",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -903,51 +974,14 @@ module.exports =
       "category": "Advanced"
       "category": "Advanced"
     },
     },
     {
     {
-      "name": "templetonserver.host",
-      "displayName": "Templeton Server host",
+      "name": "zookeeperserver.hosts",
+      "displayName": "ZooKeeper Server hosts",
       "value": "",
       "value": "",
       "defaultValue": "",
       "defaultValue": "",
-      "description": "The host that has been assigned to run Templeton Server",
-      "displayType": "masterHost",
-      "serviceName": "TEMPLETON",
-      "category": "Templeton Server"
-    },
-    {
-      "name": "templeton_user",
-      "displayName": "Templeton User",
-      "description": "User to run Templeton as",
-      "defaultValue": "templeton",
-      "isReconfigurable": false,
-      "serviceName": "TEMPLETON",
-      "category": "Advanced"
-    },
-    {
-      "name": "templeton_pid_dir",
-      "displayName": "Templeton PID Dir",
-      "description": "Directory in which the pid files for templeton processes will be created",
-      "defaultValue": "/var/run/templeton",
-      "isReconfigurable": false,
-      "serviceName": "TEMPLETON",
-      "category": "Advanced"
-    },
-    {
-      "name": "templeton_log_dir",
-      "displayName": "Templeton Log Dir",
-      "description": "Directory for templeton logs",
-      "defaultValue": "/var/log/templeton",
-      "isReconfigurable": false,
-      "serviceName": "TEMPLETON",
-      "category": "Advanced"
-    },
-    {
-      "name": "templeton-site.xml",
-      "displayName": "Custom Templeton Configs",
-      "description": "If you wish to set configuration parameters not exposed through this page, you can specify them here.<br>The text you specify here will be injected into templeton-site.xml verbatim.",
-      "defaultValue": "",
-      "isRequired": false,
-      "displayType": "custom",
-      "serviceName": "TEMPLETON",
-      "category": "Advanced"
+      "description": "The host that has been assigned to run ZooKeeper Server",
+      "displayType": "masterHosts",
+      "serviceName": "ZOOKEEPER",
+      "category": "ZooKeeper Server"
     },
     },
     {
     {
       "name": "zk_data_dir",
       "name": "zk_data_dir",
@@ -955,6 +989,7 @@ module.exports =
       "description": "Data directory for ZooKeeper",
       "description": "Data directory for ZooKeeper",
       "defaultValue": "",
       "defaultValue": "",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "ZOOKEEPER",
       "serviceName": "ZOOKEEPER",
       "category": "ZooKeeper Server"
       "category": "ZooKeeper Server"
     },
     },
@@ -964,6 +999,7 @@ module.exports =
       "description": "Directory for ZooKeeper log files",
       "description": "Directory for ZooKeeper log files",
       "defaultValue": "/var/log/zookeeper",
       "defaultValue": "/var/log/zookeeper",
       "isReconfigurable": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "ZOOKEEPER",
       "serviceName": "ZOOKEEPER",
       "category": "Advanced"
       "category": "Advanced"
     },
     },
@@ -973,15 +1009,7 @@ module.exports =
       "description": "Directory in which the pid files for zookeeper processes will be created",
       "description": "Directory in which the pid files for zookeeper processes will be created",
       "defaultValue": "/var/run/zookeeper",
       "defaultValue": "/var/run/zookeeper",
       "isReconfigurable": false,
       "isReconfigurable": false,
-      "serviceName": "ZOOKEEPER",
-      "category": "Advanced"
-    },
-    {
-      "name": "zk_user",
-      "displayName": "ZooKeeper User",
-      "description": "User to run ZooKeeper as",
-      "defaultValue": "zookeeper",
-      "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "ZOOKEEPER",
       "serviceName": "ZOOKEEPER",
       "category": "Advanced"
       "category": "Advanced"
     },
     },

+ 63 - 0
ambari-web/app/data/mock/slave_component_hosts.js

@@ -0,0 +1,63 @@
+/**
+ * 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 = [
+  {
+    componentName: 'DataNode',
+    hosts: [
+      {
+        hostname: 'host0001.company.com',
+        group: 'Default'
+      },
+      {
+        hostname: 'host0002.company.com',
+        group: 'Default'
+      },
+      {
+        hostname: 'host0003.company.com',
+        group: 'Default'
+      }
+    ]
+  },
+  {
+    componentName: 'RegionServer',
+    hosts: [
+      {
+        hostname: 'host0001.company.com',
+        group: 'Default'
+      },
+      {
+        hostname: 'host0002.company.com',
+        group: 'Default'
+      }
+    ]
+  },
+  {
+    componentName: 'TaskTracker',
+    hosts: [
+      {
+        hostname: 'host0002.company.com',
+        group: 'Default'
+      },
+      {
+        hostname: 'host0003.company.com',
+        group: 'Default'
+      }
+    ]
+  }
+]

+ 103 - 0
ambari-web/app/data/service_configs.js

@@ -0,0 +1,103 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+var configProperties = App.ConfigProperties.create();
+
+module.exports = [
+  {
+    serviceName: 'NAGIOS',
+    displayName: 'Nagios',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'General'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'NAGIOS')
+  },
+  {
+    serviceName: 'HIVE',
+    displayName: 'Hive/HCat',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'Hive Metastore'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'HIVE')
+  },
+  {
+    serviceName: 'HDFS',
+    displayName: 'HDFS',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'NameNode'}),
+      App.ServiceConfigCategory.create({ name: 'SNameNode'}),
+      App.ServiceConfigCategory.create({ name: 'DataNode'}),
+      App.ServiceConfigCategory.create({ name: 'General'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'HDFS')
+  },
+  {
+    serviceName: 'MAPREDUCE',
+    displayName: 'MapReduce',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'JobTracker'}),
+      App.ServiceConfigCategory.create({ name: 'TaskTracker'}),
+      App.ServiceConfigCategory.create({ name: 'General'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'MAPREDUCE')
+  },
+  {
+    serviceName: 'HBASE',
+    displayName: 'HBase',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'HBase Master'}),
+      App.ServiceConfigCategory.create({ name: 'RegionServer'}),
+      App.ServiceConfigCategory.create({ name: 'General'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'HBASE')
+  },
+  {
+    serviceName: 'ZOOKEEPER',
+    displayName: 'ZooKeeper',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'ZooKeeper Server'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'ZOOKEEPER')
+  },
+  {
+    serviceName: 'OOZIE',
+    displayName: 'Oozie',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'Oozie Server'}),
+      App.ServiceConfigCategory.create({ name: 'Advanced'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'OOZIE')
+  },
+  {
+    serviceName: 'MISC',
+    displayName: 'Misc',
+    configCategories: [
+      App.ServiceConfigCategory.create({ name: 'General'}),
+      App.ServiceConfigCategory.create({ name: 'Users/Groups'})
+    ],
+    configs: configProperties.filterProperty('serviceName', 'MISC')
+  }
+
+]

+ 0 - 1
ambari-web/app/initialize.js

@@ -21,7 +21,6 @@ window.App = require('app');
 
 
 require('messages');
 require('messages');
 require('utils/db');
 require('utils/db');
-require('templates');
 require('models');
 require('models');
 require('controllers');
 require('controllers');
 require('views');
 require('views');

+ 13 - 1
ambari-web/app/messages.js

@@ -26,6 +26,17 @@ Em.I18n.translations = {
   'login.loginButton': 'Sign in',
   'login.loginButton': 'Sign in',
   'login.error': 'Invalid username/password combination.',
   'login.error': 'Invalid username/password combination.',
 
 
+  'services.nagios.description': 'Nagios desc',
+  'services.ganglia.description': 'Ganglia desc',
+  'services.hdfs.description': 'HDFS desc',
+  'services.mapreduce.description': 'MapReduce desc',
+  'services.sqoop.description': 'Sqoop desc',
+  'services.pig.description': 'Pig desc',
+  'services.hive.description': 'Hive/HCat desc',
+  'services.oozie.description': 'Oozie desc',
+  'services.zookeeper.description': 'ZooKeeper desc',
+  'services.hbase.description': 'HBase desc',
+
   'topnav.help.href': 'http://incubator.apache.org/ambari/install.html',
   'topnav.help.href': 'http://incubator.apache.org/ambari/install.html',
 
 
   'installer.step1.header': 'Ambari Cluster Install Wizard',
   'installer.step1.header': 'Ambari Cluster Install Wizard',
@@ -45,7 +56,7 @@ Em.I18n.translations = {
   'installer.step2.hostPattern.tooltip.title': 'Pattern Expressions',
   'installer.step2.hostPattern.tooltip.title': 'Pattern Expressions',
   'installer.step2.hostPattern.tooltip.content': 'You can use pattern expressions to specify a number of target hosts.  Explain brackets.',
   'installer.step2.hostPattern.tooltip.content': 'You can use pattern expressions to specify a number of target hosts.  Explain brackets.',
   'installer.step2.hostName.error.required': 'Host Names cannot be left empty',
   'installer.step2.hostName.error.required': 'Host Names cannot be left empty',
-  'installer.step2.hostName.error.notRequired': 'Host Names will be ignored for if not using SSH to automatically configure hosts',
+  'installer.step2.hostName.error.notRequired': 'Host Names will be ignored if not using SSH to automatically configure hosts',
   'installer.step2.hostName.error.invalid': 'Invalid Host Name(s) - cannot start or end with a hyphen',
   'installer.step2.hostName.error.invalid': 'Invalid Host Name(s) - cannot start or end with a hyphen',
   'installer.step2.sshKey.error.required': 'SSH Private Key is required',
   'installer.step2.sshKey.error.required': 'SSH Private Key is required',
   'installer.step2.passphrase.error.match': 'Passphrases do not match',
   'installer.step2.passphrase.error.match': 'Passphrases do not match',
@@ -73,6 +84,7 @@ Em.I18n.translations = {
     '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.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.step5.header': 'Assign Masters',
   'installer.step5.header': 'Assign Masters',
 
 

+ 6 - 9
ambari-web/app/models/service_config.js

@@ -20,14 +20,9 @@ var App = require('app');
 var validator = require('utils/validator');
 var validator = require('utils/validator');
 
 
 App.ConfigProperties = Ember.ArrayProxy.extend({
 App.ConfigProperties = Ember.ArrayProxy.extend({
-  content: require('data/config_properties').configProperties,
-
-  init: function() {
-
-  }
+  content: require('data/config_properties').configProperties
 });
 });
 
 
-
 App.ServiceConfig = Ember.Object.extend({
 App.ServiceConfig = Ember.Object.extend({
   serviceName: '',
   serviceName: '',
   configCategories: [],
   configCategories: [],
@@ -113,15 +108,15 @@ App.ServiceConfigProperty = Ember.Object.extend({
       case 'regionserver.hosts':
       case 'regionserver.hosts':
         this.set('value', [ 'host0001.company.com', 'host0002.company.com', 'host0003.company.com' ]);
         this.set('value', [ 'host0001.company.com', 'host0002.company.com', 'host0003.company.com' ]);
         break;
         break;
+      case 'zookeeperserver.hosts':
+        this.set('value', [ 'zk1.company.com', 'zk2.company.com', 'zk3.company.com' ]);
+        break;
       case 'hivemetastore.host':
       case 'hivemetastore.host':
         this.set('value', 'hive.company.com');
         this.set('value', 'hive.company.com');
         break;
         break;
       case 'oozieserver.host':
       case 'oozieserver.host':
         this.set('value', 'oozie.company.com');
         this.set('value', 'oozie.company.com');
         break;
         break;
-      case 'templetonserver.host':
-        this.set('value', 'templeton.company.com');
-        break;
     }
     }
   },
   },
 
 
@@ -141,6 +136,8 @@ App.ServiceConfigProperty = Ember.Object.extend({
         return App.ServiceConfigBigTextArea;
         return App.ServiceConfigBigTextArea;
       case 'masterHost':
       case 'masterHost':
         return App.ServiceConfigMasterHostView;
         return App.ServiceConfigMasterHostView;
+      case 'masterHosts':
+        return App.ServiceConfigMasterHostsView;
       case 'slaveHosts':
       case 'slaveHosts':
         return App.ServiceConfigSlaveHostsView;
         return App.ServiceConfigSlaveHostsView;
       default:
       default:

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

@@ -99,7 +99,13 @@ module.exports = Em.Route.extend({
       router.get('installerController').connectOutlet('installerStep4');
       router.get('installerController').connectOutlet('installerStep4');
     },
     },
     back: Em.Router.transitionTo('step3'),
     back: Em.Router.transitionTo('step3'),
-    next: Em.Router.transitionTo('step5')
+    next: function (router, context) {
+      // TODO:
+      // since the service selection could have changed, invalidate service configs
+      // this is a little aggressive and unfriendly - to be optimized later
+      router.set('installerStep7Controller.doInit', true);
+      router.transitionTo('step5');
+    }
   }),
   }),
 
 
   step5: Em.Route.extend({
   step5: Em.Route.extend({
@@ -126,6 +132,7 @@ module.exports = Em.Route.extend({
     route: '/step7',
     route: '/step7',
     connectOutlets: function (router, context) {
     connectOutlets: function (router, context) {
       router.setInstallerCurrentStep('7', false);
       router.setInstallerCurrentStep('7', false);
+      router.get('installerStep7Controller').loadConfigs();
       router.get('installerController').connectOutlet('installerStep7');
       router.get('installerController').connectOutlet('installerStep7');
     },
     },
     back: Em.Router.transitionTo('step6'),
     back: Em.Router.transitionTo('step6'),

+ 19 - 6
ambari-web/app/styles/application.less

@@ -71,16 +71,24 @@ h1 {
     h2 {
     h2 {
         margin-top: 0;
         margin-top: 0;
     }
     }
-    .content {
-        padding: 25px;
-        background-color: #fff;
-    }
     .btn.btn-success {
     .btn.btn-success {
         /* float: right; */
         /* float: right; */
     }
     }
     .btn-area {
     .btn-area {
         margin-top: 20px;
         margin-top: 20px;
     }
     }
+    #installer-content {
+        padding: 25px;
+        background-color: #fff;
+
+    }
+    #step4 {
+        a.selected {
+            color: #333;
+        }
+        a.deselected {
+        }
+    }
     #serviceConfig {
     #serviceConfig {
         .accordion-heading {
         .accordion-heading {
             background-color: #f0f0f0;
             background-color: #f0f0f0;
@@ -100,7 +108,7 @@ h1 {
         .add-slave-component-group {
         .add-slave-component-group {
             margin-bottom: 20px;
             margin-bottom: 20px;
         }
         }
-        .master-host, .slave-hosts {
+        .master-host, .master-hosts, .slave-hosts {
             padding-top: 5px;
             padding-top: 5px;
             line-height: 20px;
             line-height: 20px;
         }
         }
@@ -108,7 +116,12 @@ h1 {
             margin: 20px 0;
             margin: 20px 0;
         }
         }
         .retyped-password {
         .retyped-password {
-            margin-left: 10px;
+            margin-left: 14px;
+        }
+        #slave-hosts-popup {
+            ul {
+                list-style-type: none;
+            }
         }
         }
     }
     }
 }
 }

+ 0 - 44
ambari-web/app/templates.js

@@ -1,44 +0,0 @@
-/**
- * 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.
- */
-
-
-// load all templates here
-
-require('templates/application');
-require('templates/login');
-require('templates/main');
-require('templates/main/admin');
-require('templates/main/charts');
-require('templates/main/dashboard');
-require('templates/main/hosts');
-require('templates/main/menu');
-require('templates/main/service');
-require('templates/main/service/item');
-require('templates/installer');
-require('templates/installer/step1');
-require('templates/installer/step2');
-require('templates/installer/step3');
-require('templates/installer/step4');
-require('templates/installer/step5');
-require('templates/installer/step6');
-require('templates/installer/step7');
-require('templates/installer/slaveHostsMatrix');
-require('templates/installer/step8');
-require('templates/installer/step9');
-require('templates/installer/step10');
-

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

@@ -37,7 +37,7 @@
             </ul>
             </ul>
           </div>
           </div>
         </div>
         </div>
-        <div class="well span9 content">
+        <div id="installer-content" class="well span9">
           {{outlet}}
           {{outlet}}
         </div>
         </div>
       </div>
       </div>

+ 9 - 0
ambari-web/app/templates/installer/master_hosts.hbs

@@ -0,0 +1,9 @@
+{{#if view.hasNoHosts}}
+No host assigned
+{{/if}}
+{{#if view.hasOneHost}}
+{{value}}
+{{/if}}
+{{#if view.hasMultipleHosts}}
+<a href="#" {{action showMasterHosts view.serviceConfig target="controller"}}>{{value.firstObject}} and {{view.otherLength}} others</a>
+{{/if}}

+ 5 - 0
ambari-web/app/templates/installer/master_hosts_popup.hbs

@@ -0,0 +1,5 @@
+<ul>
+{{#each host in view.serviceConfig.value}}
+<li>{{host}}</li>
+{{/each}}
+</ul>

+ 0 - 9
ambari-web/app/templates/installer/slaveHosts.hbs

@@ -1,9 +0,0 @@
-{{#if view.hasNoHosts}}
-No host assigned
-{{/if}}
-{{#if view.hasOneHost}}
-{{value}}
-{{/if}}
-{{#if view.hasMultipleHosts}}
-<a href="#" {{action showSlaveHosts value target="controller"}}>{{value.firstObject}} and {{view.otherLength}} others</a>
-{{/if}}

+ 0 - 3
ambari-web/app/templates/installer/slaveHostsMatrix.hbs

@@ -1,3 +0,0 @@
-{{#each slaveHost in App.router.installerStep7Controller.selectedSlaveHosts}}
-<label class="checkbox">{{view Ember.Checkbox}}{{slaveHost}}</label>
-{{/each}}

+ 9 - 0
ambari-web/app/templates/installer/slave_hosts.hbs

@@ -0,0 +1,9 @@
+{{#if view.hasNoHosts}}
+No host assigned
+{{/if}}
+{{#if view.hasOneHost}}
+{{value}}
+{{/if}}
+{{#if view.hasMultipleHosts}}
+<a href="#" {{action showEditSlaveComponentGroups view.serviceConfig.category target="App.router.slaveComponentGroupsController"}}>{{value.firstObject}} and {{view.otherLength}} others</a>
+{{/if}}

+ 21 - 0
ambari-web/app/templates/installer/slave_hosts_popup.hbs

@@ -0,0 +1,21 @@
+<div id="slave-hosts-popup" class="alert alert-info">Select which hosts should belong to which {{selectedComponentName}} group.</div>
+<table class="table table-striped">
+  <thead>
+    <tr>
+      <th>Host</th>
+      <th>Group</th>
+    </tr>
+  </thead>
+  <tbody>
+  {{#each host in hosts}}
+    <tr>
+      <td>
+        <label>{{host.hostname}}</label>
+      </td>
+      <td>
+        {{view Ember.Select contentBinding="groups"}}
+      </td>
+    </tr>
+  {{/each}}
+  </tbody>
+</table>

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

@@ -18,7 +18,39 @@
 
 
 
 
 <h2>{{t installer.step4.header}}</h2>
 <h2>{{t installer.step4.header}}</h2>
-<div class="btn-area">
+<div class="alert alert-info">
+  {{t installer.step4.body}}
+</div>
+<div id="step4">
+  <table class="table table-striped">
+    <thead>
+    <tr>
+      <th class="span3">Service
+        <span style="margin-left:10px">
+          <a href="#" {{action selectAll target="controller"}} {{bindAttr class="isAll:selected:deselected"}}>all</a>
+          |
+          <a href="#" {{action selectMinimum target="controller"}} {{bindAttr class="isMinimum:selected:deselected"}}>minimum</a>
+        </span>
+      </th>
+      <th>Description</th>
+    </tr>
+    </thead>
+    <tbody>
+    {{#each controller}}
+    {{#unless isHidden}}
+    <tr {{bindAttr class="isSelected:success:"}}>
+      <td><label
+        class="checkbox">{{view Ember.Checkbox disabledBinding="isDisabled" checkedBinding="isSelected"}}{{displayName}}</label>
+      </td>
+      <td>{{description}}</td>
+    </tr>
+    {{/unless}}
+    {{/each}}
+    </tbody>
+  </table>
+
+  <div class="btn-area">
     <a class="btn" {{action back}}>Back</a>
     <a class="btn" {{action back}}>Back</a>
-    <a class="btn btn-success" {{action next}}>Next</a>
+    <a class="btn btn-success" style="float:right" {{action submit target="controller"}}>Next</a>
+  </div>
 </div>
 </div>

+ 5 - 10
ambari-web/app/templates/installer/step7.hbs

@@ -20,15 +20,10 @@
 <div class="alert alert-info">
 <div class="alert alert-info">
   {{t installer.step7.body}}
   {{t installer.step7.body}}
 </div>
 </div>
-<div id="attention">
-{{#if isSubmitDisabled}}
-<div class="alert">{{t installer.step7.attentionNeeded}}</div>
-{{/if}}
-</div>
 {{#view App.ServiceConfigTabs}}
 {{#view App.ServiceConfigTabs}}
 <ul class="nav nav-tabs">
 <ul class="nav nav-tabs">
 {{#each service in controller}}
 {{#each service in controller}}
-    <li><a href="#" data-toggle="tab" {{action selectService service on="click" target="view"}}>{{service.serviceName}}{{#if service.errorCount}}<span class="badge badge-important">{{service.errorCount}}</span>{{/if}}</a></li>
+    <li><a class="active" {{bindAttr href="service.serviceName"}} data-toggle="tab" {{action selectService service on="click" target="view"}}>{{service.displayName}}{{#if service.errorCount}}<span class="badge badge-important">{{service.errorCount}}</span>{{/if}}</a></li>
 {{/each}}
 {{/each}}
 </ul>
 </ul>
 {{/view}}
 {{/view}}
@@ -60,17 +55,17 @@
     </div>
     </div>
     {{#if category.isForSlaveComponent}}
     {{#if category.isForSlaveComponent}}
     {{#view App.AddSlaveComponentGroupButton slaveComponentNameBinding="category.name"}}
     {{#view App.AddSlaveComponentGroupButton slaveComponentNameBinding="category.name"}}
-    <a class="btn add-slave-component-group" {{action addSlaveComponentGroup category.name target="controller"}}><i class="icon-plus-sign"></i> Add a {{category.name}} Group</a>
+    <a class="btn add-slave-component-group" {{action showAddSlaveComponentGroup category.name target="App.router.slaveComponentGroupsController"}}><i class="icon-plus-sign"></i> Add a {{category.name}} Group</a>
     {{/view}}
     {{/view}}
     {{/if}}
     {{/if}}
     {{/each}}
     {{/each}}
 </div>
 </div>
+{{#if isSubmitDisabled}}
+<div class="alert">{{t installer.step7.attentionNeeded}}</div>
+{{/if}}
 <div class="btn-area">
 <div class="btn-area">
     <a class="btn" {{action back}}>Back</a>
     <a class="btn" {{action back}}>Back</a>
     <div style="float:right">
     <div style="float:right">
-      {{#if isSubmitDisabled}}
-        <span class="alert" style="margin-right:10px">{{t installer.step7.attentionNeeded}}</span>
-      {{/if}}
       <a {{bindAttr class=":btn :btn-success"}} {{bindAttr disabled="isSubmitDisabled"}} {{action submit target="controller"}}>Next</a>
       <a {{bindAttr class=":btn :btn-success"}} {{bindAttr disabled="isSubmitDisabled"}} {{action submit target="controller"}}>Next</a>
     </div>
     </div>
 </div>
 </div>

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

@@ -141,7 +141,7 @@ App.db.setHosts = function(hostInfo) {
 	console.log('TRACE: Entering db:setHosts function');
 	console.log('TRACE: Entering db:setHosts 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;
-  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.hostInfo = hostInfo;
   App.db.data[user].Installer.hostInfo = hostInfo;
@@ -157,9 +157,14 @@ App.db.setSoftRepo = function(softRepo) {
 		App.db.data[user].Installer.softRepo = softRepo;
 		App.db.data[user].Installer.softRepo = softRepo;
 		localStorage.setObject('ambari',App.db.data);
 		localStorage.setObject('ambari',App.db.data);
 	}
 	}
-
 }
 }
 
 
+App.db.setSelectedServiceNames = function(serviceNames) {
+  App.db.data = localStorage.getObject('ambari');
+  var user = App.db.data.app.loginName;
+  App.db.data[user].Installer.selectedServiceNames = serviceNames;
+  localStorage.setObject('ambari', App.db.data);
+}
 
 
 /*
 /*
  *  getter methods
  *  getter methods
@@ -223,4 +228,10 @@ App.db.getHosts = function(name,hostInfo) {
   return App.db.data[user].Installer.hostInfo;
   return App.db.data[user].Installer.hostInfo;
 }
 }
 
 
+App.db.getSelectedServiceNames = function() {
+  App.db.data = localStorage.getObject('ambari');
+  var user = App.db.data.app.loginName;
+  return App.db.data[user].Installer.selectedServiceNames;
+}
+
 module.exports = App.db;
 module.exports = App.db;

+ 1 - 1
ambari-web/app/views.js

@@ -36,7 +36,7 @@ require('views/installer');
 require('views/installer/step1_view');
 require('views/installer/step1_view');
 require('views/installer/step2_view');
 require('views/installer/step2_view');
 require('views/installer/step3_view');
 require('views/installer/step3_view');
-require('views/installer/step4');
+require('views/installer/step4_view');
 require('views/installer/step5');
 require('views/installer/step5');
 require('views/installer/step6');
 require('views/installer/step6');
 require('views/installer/step7_view');
 require('views/installer/step7_view');

+ 1 - 6
ambari-web/app/views/installer/step4.js → ambari-web/app/views/installer/step4_view.js

@@ -21,11 +21,6 @@ var App = require('app');
 
 
 App.InstallerStep4View = Em.View.extend({
 App.InstallerStep4View = Em.View.extend({
 
 
-  templateName: require('templates/installer/step4'),
-
-  submit: function(e) {
-    alert(this.get('controller.clusterName'));
-    App.router.transitionTo('step5');
-  }
+  templateName: require('templates/installer/step4')
 
 
 });
 });

+ 64 - 61
ambari-web/app/views/installer/step7_view.js

@@ -44,81 +44,83 @@ App.ServiceConfigTabs = Ember.View.extend({
   },
   },
 
 
   didInsertElement: function() {
   didInsertElement: function() {
-    this.$('a:first').tab('show');
+    var serviceName = this.get('controller.selectedService').serviceName;
+    this.$('a[href="' + serviceName + '"]').tab('show');
   }
   }
 
 
 });
 });
 
 
-var popover = function (view) {
-  view.$().popover({
-    title: view.get('serviceConfig.displayName') + '<br><small>' + view.get('serviceConfig.name') + '</small>',
-    content: view.get('serviceConfig.description'),
-    placement: 'right',
-    trigger: 'hover'
-  });
-
-};
+App.ServiceConfigPopoverSupport = Ember.Mixin.create({
+  didInsertElement: function() {
+    if (this.get('isPopoverEnabled') !== 'false') {
+      this.$().popover({
+        title: this.get('serviceConfig.displayName') + '<br><small>' + this.get('serviceConfig.name') + '</small>',
+        content: this.get('serviceConfig.description'),
+        placement: 'right',
+        trigger: 'hover'
+      });
+    }
+  }
+});
 
 
-App.ServiceConfigTextField = Ember.TextField.extend({
+App.ServiceConfigTextField = Ember.TextField.extend(App.ServiceConfigPopoverSupport, {
 
 
   serviceConfig: null,
   serviceConfig: null,
   isPopoverEnabled: true,
   isPopoverEnabled: true,
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
-  classNames: [ 'input-xlarge' ],
+  classNameBindings: 'textFieldClassName',
+
+  textFieldClassName: function() {
+    // sets the width of the field depending on display type
+    if (['directory','url','email','user','host'].contains(this.get('serviceConfig.displayType'))) {
+      return ['span6'];
+    } else {
+      return ['input-small'];
+    }
+  }.property('serviceConfig.displayType'),
 
 
   disabled: function() {
   disabled: function() {
     return !this.get('serviceConfig.isEditable');
     return !this.get('serviceConfig.isEditable');
-  }.property('serviceConfig.isEditable'),
+  }.property('serviceConfig.isEditable')
 
 
-  didInsertElement: function() {
-    if (this.get('isPopoverEnabled')) {
-      popover(this);
-    }
-  }
 });
 });
 
 
-App.ServiceConfigTextFieldWithUnit = Ember.View.extend({
+App.ServiceConfigTextFieldWithUnit = Ember.View.extend(App.ServiceConfigPopoverSupport, {
   serviceConfig: null,
   serviceConfig: null,
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
   classNames: [ 'input-append' ],
   classNames: [ 'input-append' ],
 
 
-  template: Ember.Handlebars.compile('{{view App.ServiceConfigTextField serviceConfigBinding="view.serviceConfig" isPopoverEnabledBinding="false"}}<span class="add-on">{{view.serviceConfig.unit}}</span>'),
+  template: Ember.Handlebars.compile('{{view App.ServiceConfigTextField serviceConfigBinding="view.serviceConfig" isPopoverEnabled="false"}}<span class="add-on">{{view.serviceConfig.unit}}</span>'),
 
 
   disabled: function() {
   disabled: function() {
     return !this.get('serviceConfig.isEditable');
     return !this.get('serviceConfig.isEditable');
-  }.property('serviceConfig.isEditable'),
+  }.property('serviceConfig.isEditable')
 
 
-  didInsertElement: function() {
-    popover(this);
-  }
 });
 });
 
 
 App.ServiceConfigPasswordField = Ember.TextField.extend({
 App.ServiceConfigPasswordField = Ember.TextField.extend({
   serviceConfig: null,
   serviceConfig: null,
   type: 'password',
   type: 'password',
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
-  classNames: [ 'input-medium' ],
+  classNames: [ 'span3' ],
+  placeholder: 'Type password',
 
 
   template: Ember.Handlebars.compile('{{view view.retypePasswordView placeholder="Retype password"}}'),
   template: Ember.Handlebars.compile('{{view view.retypePasswordView placeholder="Retype password"}}'),
 
 
   retypePasswordView: Ember.TextField.extend({
   retypePasswordView: Ember.TextField.extend({
     type: 'password',
     type: 'password',
-    classNames: [ 'input-medium', 'retyped-password' ],
+    classNames: [ 'span3', 'retyped-password' ],
     valueBinding: 'parentView.serviceConfig.retypedPassword'
     valueBinding: 'parentView.serviceConfig.retypedPassword'
   })
   })
 
 
 });
 });
 
 
-App.ServiceConfigTextArea = Ember.TextArea.extend({
+App.ServiceConfigTextArea = Ember.TextArea.extend(App.ServiceConfigPopoverSupport, {
 
 
   serviceConfig: null,
   serviceConfig: null,
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
   rows: 4,
   rows: 4,
-  classNames: ['span6'],
-
-  didInsertElement: function() {
-    popover(this);
-  }
+  classNames: ['span6']
 
 
 });
 });
 
 
@@ -126,46 +128,38 @@ App.ServiceConfigBigTextArea = App.ServiceConfigTextArea.extend({
   rows: 10
   rows: 10
 });
 });
 
 
-var hostPopover = function (view) {
-  view.$().popover({
-    title: view.get('serviceConfig.displayName'),
-    content: view.get('serviceConfig.description'),
-    placement: 'right',
-    trigger: 'hover'
-  });
-};
-
-App.ServiceConfigCheckbox = Ember.Checkbox.extend({
+App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSupport, {
 
 
   serviceConfig: null,
   serviceConfig: null,
-  checkedBinding: 'serviceConfig.value',
+  checkedBinding: 'serviceConfig.value'
 
 
-  diInsertElement: function() {
-    popover(this);
-  }
+});
 
 
+App.ServiceConfigHostPopoverSupport = Ember.Mixin.create({
+  didInsertElement: function() {
+    this.$().popover({
+      title: this.get('serviceConfig.displayName'),
+      content: this.get('serviceConfig.description'),
+      placement: 'right',
+      trigger: 'hover'
+    });
+  }
 });
 });
 
 
-App.ServiceConfigMasterHostView = Ember.View.extend({
+App.ServiceConfigMasterHostView = Ember.View.extend(App.ServiceConfigHostPopoverSupport, {
 
 
   serviceConfig: null,
   serviceConfig: null,
   classNames: ['master-host', 'span6'],
   classNames: ['master-host', 'span6'],
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
 
 
-  template: Ember.Handlebars.compile('{{value}}'),
+  template: Ember.Handlebars.compile('{{value}}')
 
 
-  didInsertElement: function() {
-    hostPopover(this);
-  }
- });
+});
 
 
-App.ServiceConfigSlaveHostsView = Ember.View.extend({
+App.ServiceConfigMultipleHostsDisplay = Ember.Mixin.create(App.ServiceConfigHostPopoverSupport, {
 
 
-  classNames: ['slave-hosts', 'span6'],
   valueBinding: 'serviceConfig.value',
   valueBinding: 'serviceConfig.value',
 
 
-  templateName: require('templates/installer/slaveHosts'),
-
   hasNoHosts: function() {
   hasNoHosts: function() {
     return this.get('value').length === 0;
     return this.get('value').length === 0;
   }.property('value'),
   }.property('value'),
@@ -180,18 +174,27 @@ App.ServiceConfigSlaveHostsView = Ember.View.extend({
 
 
   otherLength: function() {
   otherLength: function() {
     return this.get('value').length - 1;
     return this.get('value').length - 1;
-  }.property('value'),
+  }.property('value')
 
 
-  didInsertElement: function() {
-    hostPopover(this);
-  }
+})
+
+App.ServiceConfigMasterHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
+
+  classNames: ['master-hosts', 'span6'],
+  templateName: require('templates/installer/master_hosts')
+
+});
+
+App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
+
+  classNames: ['slave-hosts', 'span6'],
+  templateName: require('templates/installer/slave_hosts')
 
 
 });
 });
 
 
 App.AddSlaveComponentGroupButton = Ember.View.extend({
 App.AddSlaveComponentGroupButton = Ember.View.extend({
 
 
   tagName: 'span',
   tagName: 'span',
-
   slaveComponentName: null,
   slaveComponentName: null,
 
 
   didInsertElement: function () {
   didInsertElement: function () {

+ 119 - 0
ambari-web/test/installer/step4_test.js

@@ -0,0 +1,119 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+require('controllers/installer/step4_controller');
+
+describe('App.InstallerStep4Controller', function () {
+
+  var DEFAULT_SERVICES = ['HDFS', 'MAPREDUCE', 'NAGIOS', 'GANGLIA'];
+  var OPTIONAL_SERVICES = ['OOZIE', 'HIVE', 'HBASE', 'PIG', 'SQOOP', 'ZOOKEEPER', 'HCATALOG'];
+
+  var controller = App.InstallerStep4Controller.create();
+
+  describe('#selectMinimum()', function () {
+    it('should set isSelected is false on all non-default services and isSelected is true on all default services', function() {
+      controller.selectMinimum();
+      DEFAULT_SERVICES.forEach(function (serviceName) {
+        expect(controller.findProperty('serviceName', serviceName).get('isSelected')).to.equal(true);
+      });
+      OPTIONAL_SERVICES.forEach(function (serviceName) {
+        expect(controller.findProperty('serviceName', serviceName).get('isSelected')).to.equal(false);
+      });
+    })
+  })
+
+  describe('#selectAll()', function () {
+    it('should set isSelected is true on all non-default services and isSelected is true on all default services', function() {
+      controller.selectAll();
+      DEFAULT_SERVICES.forEach(function (serviceName) {
+        expect(controller.findProperty('serviceName', serviceName).get('isSelected')).to.equal(true);
+      });
+      OPTIONAL_SERVICES.forEach(function (serviceName) {
+        expect(controller.findProperty('serviceName', serviceName).get('isSelected')).to.equal(true);
+      });
+    })
+  })
+
+  describe('#isAll()', function () {
+
+    beforeEach(function() {
+      DEFAULT_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', true);
+      });
+      OPTIONAL_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', true);
+      });
+    });
+
+    it('should return true if isSelected is true for all services', function() {
+      expect(controller.get('isAll')).to.equal(true);
+    })
+
+    it('should return false if isSelected is false for one of the services', function() {
+      controller.findProperty('serviceName', 'HBASE').set('isSelected', false);
+      expect(controller.get('isAll')).to.equal(false);
+    })
+  })
+
+  describe('#isMinimum()', function () {
+
+    beforeEach(function() {
+      DEFAULT_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', true);
+      });
+      OPTIONAL_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', false);
+      });
+    });
+
+    it('should return true if isSelected is true for all default services and isSelected is false for all optional services', function() {
+      expect(controller.get('isMinimum')).to.equal(true);
+    })
+
+    it('should return false if isSelected is true for all default serices and isSelected is true for one of optional services', function() {
+      controller.findProperty('serviceName', 'HBASE').set('isSelected', true);
+      expect(controller.get('isMinimum')).to.equal(false);
+    })
+
+  })
+
+  describe('#saveSelectedServiceNamesToDB', function() {
+
+    beforeEach(function() {
+      DEFAULT_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', true);
+      });
+      OPTIONAL_SERVICES.forEach(function(serviceName) {
+        controller.findProperty('serviceName', serviceName).set('isSelected', true);
+      });
+    });
+
+    it('should store the selected service names in App.db.selectedServiceNames', function() {
+      App.db.setLoginName('tester');
+      App.db.setClusterName('test');
+      controller.saveSelectedServiceNamesToDB();
+      console.log('controller length=' + controller.get('length'));
+      var selectedServiceNames = App.db.getSelectedServiceNames();
+      console.log('service length=' + selectedServiceNames.get('length'));
+      expect(selectedServiceNames.length === DEFAULT_SERVICES.length + OPTIONAL_SERVICES.length).to.equal(true);
+    })
+
+  })
+
+})

+ 17 - 1
ambari-web/test/installer/step7_test.js

@@ -16,4 +16,20 @@
  * limitations under the License.
  * limitations under the License.
  */
  */
 
 
-// TODO
+var App = require('app');
+require('controllers/installer/step7_controller');
+
+describe('App.InstallerStep7Controller', function () {
+
+  /*
+  describe('#validateStep1()', function () {
+    it('should return false and sets invalidClusterName to true if cluster name is empty', function () {
+      var controller = App.InstallerStep1Controller.create();
+      controller.set('clusterName', '');
+      expect(controller.validateStep1()).to.equal(false);
+      expect(controller.get('invalidClusterName')).to.equal(true);
+    })
+  })
+  */
+
+})