Преглед изворни кода

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
28 измењених фајлова са 918 додато и 429 уклоњено
  1. 2 0
      AMBARI-666-CHANGES.txt
  2. 2 1
      ambari-web/app/controllers.js
  3. 144 0
      ambari-web/app/controllers/installer/step4_controller.js
  4. 103 142
      ambari-web/app/controllers/installer/step7_controller.js
  5. 156 128
      ambari-web/app/data/config_properties.js
  6. 63 0
      ambari-web/app/data/mock/slave_component_hosts.js
  7. 103 0
      ambari-web/app/data/service_configs.js
  8. 0 1
      ambari-web/app/initialize.js
  9. 13 1
      ambari-web/app/messages.js
  10. 6 9
      ambari-web/app/models/service_config.js
  11. 8 1
      ambari-web/app/routes/installer.js
  12. 19 6
      ambari-web/app/styles/application.less
  13. 0 44
      ambari-web/app/templates.js
  14. 1 1
      ambari-web/app/templates/installer.hbs
  15. 9 0
      ambari-web/app/templates/installer/master_hosts.hbs
  16. 5 0
      ambari-web/app/templates/installer/master_hosts_popup.hbs
  17. 0 9
      ambari-web/app/templates/installer/slaveHosts.hbs
  18. 0 3
      ambari-web/app/templates/installer/slaveHostsMatrix.hbs
  19. 9 0
      ambari-web/app/templates/installer/slave_hosts.hbs
  20. 21 0
      ambari-web/app/templates/installer/slave_hosts_popup.hbs
  21. 34 2
      ambari-web/app/templates/installer/step4.hbs
  22. 5 10
      ambari-web/app/templates/installer/step7.hbs
  23. 13 2
      ambari-web/app/utils/db.js
  24. 1 1
      ambari-web/app/views.js
  25. 1 6
      ambari-web/app/views/installer/step4_view.js
  26. 64 61
      ambari-web/app/views/installer/step7_view.js
  27. 119 0
      ambari-web/test/installer/step4_test.js
  28. 17 1
      ambari-web/test/installer/step7_test.js

+ 2 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,8 @@ AMBARI-666 branch (unreleased changes)
 
   NEW FEATURES
 
+  AMBARI-757. Implement Installer Step 4 (Select Services). (yusaku)
+
   AMBARI-751. Re-structure servicecomponenthost fsm layout. (hitesh)
 
   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/step2_controller');
 require('controllers/installer/step3_controller');
+require('controllers/installer/step4_controller');
 require('controllers/installer/step7_controller');
 require('controllers/main');
 require('controllers/main/service');
 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');
 
+/**
+ * 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({
 
   name: 'installerStep7Controller',
@@ -26,177 +36,128 @@ App.InstallerStep7Controller = Em.ArrayController.extend({
 
   selectedService: null,
 
-  selectedSlaveHosts: null,
+  slaveHostToGroup: null,
 
   isSubmitDisabled: function() {
     return !this.everyProperty('errorCount', 0);
   }.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() {
     if (!this.get('isSubmitDisabled')) {
+      // TODO:
+      // save service configs in App.db (localStorage)
       App.get('router').transitionTo('step8');
     }
   },
 
-  showSlaveHosts: function(event) {
-    this.set('selectedSlaveHosts', event.context);
+  showMasterHosts: function(event) {
+    var serviceConfig = event.context;
     App.ModalPopup.show({
-      header: 'Slave Hosts',
+      header: serviceConfig.category + ' Hosts',
       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({
-      header: 'Add a ' + event.context + ' Group',
+      header: serviceConfig.category + ' Hosts',
       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",
       "defaultValue": "/var/log/hbase",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HBASE",
       "category": "Advanced"
     },
@@ -99,15 +100,7 @@ module.exports =
       "description": "Directory in which the pid files for HBase processes will be created",
       "defaultValue": "/var/run/hbase",
       "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",
       "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",
       "defaultValue": "",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "category": "SNameNode"
     },
@@ -291,29 +285,22 @@ module.exports =
       "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,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "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,
+      "displayType": "directory",
       "serviceName": "HDFS",
       "category": "Advanced"
     },
@@ -437,6 +424,7 @@ module.exports =
       "defaultValue": "",
       "isRequired": false,
       "isReconfigurable": false,
+      "displayType": "host",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
     },
@@ -446,6 +434,7 @@ module.exports =
       "description": "MySQL database name used as the Hive Metastore",
       "defaultValue": "hive",
       "isReconfigurable": false,
+      "displayType": "host",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
     },
@@ -455,6 +444,7 @@ module.exports =
       "description": "MySQL user to use to connect to the MySQL database",
       "defaultValue": "hive",
       "isReconfigurable": false,
+      "displayType": "user",
       "serviceName": "HIVE",
       "category": "Hive Metastore"
     },
@@ -474,6 +464,7 @@ module.exports =
       "description": "Directory for Hive log files",
       "defaultValue": "/var/log/hive",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "category": "Advanced"
     },
@@ -483,15 +474,7 @@ module.exports =
       "description": "Directory in which the PID files for Hive processes will be created",
       "defaultValue": "/var/run/hive",
       "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",
       "category": "Advanced"
     },
@@ -501,6 +484,7 @@ module.exports =
       "description": "Directory for HCatalog log files",
       "defaultValue": "/var/log/hcatalog",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "HIVE",
       "category": "Advanced"
     },
@@ -510,15 +494,7 @@ module.exports =
       "description": "Directory in which the PID files for HCatalog processes will be created",
       "defaultValue": "/var/run/hcatalog",
       "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",
       "category": "Advanced"
     },
@@ -562,29 +538,22 @@ module.exports =
       "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,
+      "displayType": "directory",
       "serviceName": "MAPREDUCE",
       "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,
+      "displayType": "directory",
       "serviceName": "MAPREDUCE",
       "category": "Advanced"
     },
@@ -593,6 +562,7 @@ module.exports =
       "displayName": "MapReduce Capacity Scheduler",
       "description": "The scheduler to use for scheduling of MapReduce jobs",
       "defaultValue": "org.apache.hadoop.mapred.CapacityTaskScheduler",
+      "displayType": "directory",
       "serviceName": "MAPREDUCE"
     },
     {
@@ -784,14 +754,17 @@ module.exports =
       "defaultValue": "",
       "isRequired": false,
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC"
     },
+    /*
     {
       "name": "hadoop_log_dir",
       "displayName": "Hadoop Log Dir",
       "description": "Directory for Hadoop log files",
       "defaultValue": "/var/log/hadoop",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC",
       "category": "Advanced"
     },
@@ -801,25 +774,128 @@ module.exports =
       "description": "Directory in which the pid files for Hadoop processes will be created",
       "defaultValue": "/var/run/hadoop",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "MISC",
       "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",
       "displayName": "Path to local repo file",
       "description": "Path to local repository file that configures from where to download software packages",
       "defaultValue": "/etc/yum.repos.d/hdp.repo",
       "isReconfigurable": false,
+      "displayType": "directory",
       "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,
-      "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",
@@ -827,6 +903,7 @@ module.exports =
       "description": "Nagios Web UI Admin username",
       "defaultValue": "nagiosadmin",
       "isReconfigurable": false,
+      "displayType": "user",
       "serviceName": "NAGIOS"
     },
     {
@@ -862,6 +939,7 @@ module.exports =
       "description": "Data directory in which the Oozie DB exists",
       "defaultValue": "",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "OOZIE",
       "category": "Oozie Server"
     },
@@ -871,6 +949,7 @@ module.exports =
       "description": "Directory for oozie logs",
       "defaultValue": "/var/log/oozie",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "OOZIE",
       "category": "Advanced"
     },
@@ -880,15 +959,7 @@ module.exports =
       "description": "Directory in which the pid files for oozie processes will be created",
       "defaultValue": "/var/run/oozie",
       "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",
       "category": "Advanced"
     },
@@ -903,51 +974,14 @@ module.exports =
       "category": "Advanced"
     },
     {
-      "name": "templetonserver.host",
-      "displayName": "Templeton Server host",
+      "name": "zookeeperserver.hosts",
+      "displayName": "ZooKeeper Server hosts",
       "value": "",
       "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",
@@ -955,6 +989,7 @@ module.exports =
       "description": "Data directory for ZooKeeper",
       "defaultValue": "",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "ZOOKEEPER",
       "category": "ZooKeeper Server"
     },
@@ -964,6 +999,7 @@ module.exports =
       "description": "Directory for ZooKeeper log files",
       "defaultValue": "/var/log/zookeeper",
       "isReconfigurable": false,
+      "displayType": "directory",
       "serviceName": "ZOOKEEPER",
       "category": "Advanced"
     },
@@ -973,15 +1009,7 @@ module.exports =
       "description": "Directory in which the pid files for zookeeper processes will be created",
       "defaultValue": "/var/run/zookeeper",
       "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",
       "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('utils/db');
-require('templates');
 require('models');
 require('controllers');
 require('views');

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

@@ -26,6 +26,17 @@ Em.I18n.translations = {
   'login.loginButton': 'Sign in',
   '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',
 
   '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.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.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.sshKey.error.required': 'SSH Private Key is required',
   '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.',
 
   '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',
 

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

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

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

@@ -99,7 +99,13 @@ module.exports = Em.Route.extend({
       router.get('installerController').connectOutlet('installerStep4');
     },
     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({
@@ -126,6 +132,7 @@ module.exports = Em.Route.extend({
     route: '/step7',
     connectOutlets: function (router, context) {
       router.setInstallerCurrentStep('7', false);
+      router.get('installerStep7Controller').loadConfigs();
       router.get('installerController').connectOutlet('installerStep7');
     },
     back: Em.Router.transitionTo('step6'),

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

@@ -71,16 +71,24 @@ h1 {
     h2 {
         margin-top: 0;
     }
-    .content {
-        padding: 25px;
-        background-color: #fff;
-    }
     .btn.btn-success {
         /* float: right; */
     }
     .btn-area {
         margin-top: 20px;
     }
+    #installer-content {
+        padding: 25px;
+        background-color: #fff;
+
+    }
+    #step4 {
+        a.selected {
+            color: #333;
+        }
+        a.deselected {
+        }
+    }
     #serviceConfig {
         .accordion-heading {
             background-color: #f0f0f0;
@@ -100,7 +108,7 @@ h1 {
         .add-slave-component-group {
             margin-bottom: 20px;
         }
-        .master-host, .slave-hosts {
+        .master-host, .master-hosts, .slave-hosts {
             padding-top: 5px;
             line-height: 20px;
         }
@@ -108,7 +116,12 @@ h1 {
             margin: 20px 0;
         }
         .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>
           </div>
         </div>
-        <div class="well span9 content">
+        <div id="installer-content" class="well span9">
           {{outlet}}
         </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>
-<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 btn-success" {{action next}}>Next</a>
+    <a class="btn btn-success" style="float:right" {{action submit target="controller"}}>Next</a>
+  </div>
 </div>

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

@@ -20,15 +20,10 @@
 <div class="alert alert-info">
   {{t installer.step7.body}}
 </div>
-<div id="attention">
-{{#if isSubmitDisabled}}
-<div class="alert">{{t installer.step7.attentionNeeded}}</div>
-{{/if}}
-</div>
 {{#view App.ServiceConfigTabs}}
 <ul class="nav nav-tabs">
 {{#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}}
 </ul>
 {{/view}}
@@ -60,17 +55,17 @@
     </div>
     {{#if category.isForSlaveComponent}}
     {{#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}}
     {{/if}}
     {{/each}}
 </div>
+{{#if isSubmitDisabled}}
+<div class="alert">{{t installer.step7.attentionNeeded}}</div>
+{{/if}}
 <div class="btn-area">
     <a class="btn" {{action back}}>Back</a>
     <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>
     </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');
   App.db.data = localStorage.getObject('ambari');
   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].Installer.hostInfo = hostInfo;
@@ -157,9 +157,14 @@ App.db.setSoftRepo = function(softRepo) {
 		App.db.data[user].Installer.softRepo = softRepo;
 		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
@@ -223,4 +228,10 @@ App.db.getHosts = function(name,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;

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

@@ -36,7 +36,7 @@ require('views/installer');
 require('views/installer/step1_view');
 require('views/installer/step2_view');
 require('views/installer/step3_view');
-require('views/installer/step4');
+require('views/installer/step4_view');
 require('views/installer/step5');
 require('views/installer/step6');
 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({
 
-  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() {
-    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,
   isPopoverEnabled: true,
   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() {
     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,
   valueBinding: 'serviceConfig.value',
   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() {
     return !this.get('serviceConfig.isEditable');
-  }.property('serviceConfig.isEditable'),
+  }.property('serviceConfig.isEditable')
 
-  didInsertElement: function() {
-    popover(this);
-  }
 });
 
 App.ServiceConfigPasswordField = Ember.TextField.extend({
   serviceConfig: null,
   type: 'password',
   valueBinding: 'serviceConfig.value',
-  classNames: [ 'input-medium' ],
+  classNames: [ 'span3' ],
+  placeholder: 'Type password',
 
   template: Ember.Handlebars.compile('{{view view.retypePasswordView placeholder="Retype password"}}'),
 
   retypePasswordView: Ember.TextField.extend({
     type: 'password',
-    classNames: [ 'input-medium', 'retyped-password' ],
+    classNames: [ 'span3', 'retyped-password' ],
     valueBinding: 'parentView.serviceConfig.retypedPassword'
   })
 
 });
 
-App.ServiceConfigTextArea = Ember.TextArea.extend({
+App.ServiceConfigTextArea = Ember.TextArea.extend(App.ServiceConfigPopoverSupport, {
 
   serviceConfig: null,
   valueBinding: 'serviceConfig.value',
   rows: 4,
-  classNames: ['span6'],
-
-  didInsertElement: function() {
-    popover(this);
-  }
+  classNames: ['span6']
 
 });
 
@@ -126,46 +128,38 @@ App.ServiceConfigBigTextArea = App.ServiceConfigTextArea.extend({
   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,
-  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,
   classNames: ['master-host', 'span6'],
   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',
 
-  templateName: require('templates/installer/slaveHosts'),
-
   hasNoHosts: function() {
     return this.get('value').length === 0;
   }.property('value'),
@@ -180,18 +174,27 @@ App.ServiceConfigSlaveHostsView = Ember.View.extend({
 
   otherLength: function() {
     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({
 
   tagName: 'span',
-
   slaveComponentName: null,
 
   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.
  */
 
-// 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);
+    })
+  })
+  */
+
+})