Browse Source

AMBARI-891. Initial work to refactor Wizards in Ambari Web. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1400489 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 years ago
parent
commit
7ae7f580a2
39 changed files with 3304 additions and 774 deletions
  1. 2 0
      AMBARI-666-CHANGES.txt
  2. 2 1
      ambari-web/app/controllers.js
  3. 64 15
      ambari-web/app/controllers/installer/step7_controller.js
  4. 64 0
      ambari-web/app/controllers/main/dashboard.js
  5. 113 17
      ambari-web/app/controllers/main/host/add_controller.js
  6. 55 78
      ambari-web/app/controllers/wizard/step5_controller.js
  7. 210 0
      ambari-web/app/controllers/wizard/step6_controller.js
  8. 268 251
      ambari-web/app/messages.js
  9. 26 13
      ambari-web/app/routes/add_host_routes.js
  10. 2 1
      ambari-web/app/styles/app.css
  11. 117 23
      ambari-web/app/styles/application.less
  12. 40 3
      ambari-web/app/templates/common/metric.hbs
  13. 27 0
      ambari-web/app/templates/common/time_range.hbs
  14. 15 0
      ambari-web/app/templates/installer/slave_component_hosts.hbs
  15. 31 0
      ambari-web/app/templates/installer/slave_component_hosts_popup.hbs
  16. 7 18
      ambari-web/app/templates/installer/step7.hbs
  17. 2 1
      ambari-web/app/templates/main/charts.hbs
  18. 6 6
      ambari-web/app/templates/main/dashboard.hbs
  19. 2 2
      ambari-web/app/templates/main/dashboard/service/hbase.hbs
  20. 1 1
      ambari-web/app/templates/main/dashboard/service/hdfs.hbs
  21. 3 3
      ambari-web/app/templates/main/dashboard/service/mapreduce.hbs
  22. 177 166
      ambari-web/app/templates/main/service/info/summary.hbs
  23. 3 3
      ambari-web/app/templates/wizard/step5.hbs
  24. 77 0
      ambari-web/app/templates/wizard/step6.hbs
  25. 1 1
      ambari-web/app/utils/db.js
  26. 17 23
      ambari-web/app/utils/helper.js
  27. 2 0
      ambari-web/app/views.js
  28. 62 27
      ambari-web/app/views/common/metric.js
  29. 169 0
      ambari-web/app/views/common/time_range.js
  30. 44 19
      ambari-web/app/views/installer/step7_view.js
  31. 9 12
      ambari-web/app/views/main/charts/horizon/chart.js
  32. 17 17
      ambari-web/app/views/main/dashboard/service.js
  33. 21 18
      ambari-web/app/views/main/dashboard/service/hbase.js
  34. 9 21
      ambari-web/app/views/main/dashboard/service/hdfs.js
  35. 39 21
      ambari-web/app/views/main/dashboard/service/mapreduce.js
  36. 33 9
      ambari-web/app/views/main/service/menu.js
  37. 6 4
      ambari-web/app/views/wizard/step5_view.js
  38. 31 0
      ambari-web/app/views/wizard/step6_view.js
  39. 1530 0
      ambari-web/vendor/scripts/jquery-ui-timepicker-addon.js

+ 2 - 0
AMBARI-666-CHANGES.txt

@@ -303,6 +303,8 @@ AMBARI-666 branch (unreleased changes)
 
 
   IMPROVEMENTS
   IMPROVEMENTS
 
 
+  AMBARI-891. Initial work to refactor the Wizards in Ambari Web. (yusaku)
+
   AMBARI-883. Improve user interactions on Confirm Hosts page of the
   AMBARI-883. Improve user interactions on Confirm Hosts page of the
   Installer. (yusaku)
   Installer. (yusaku)
 
 

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

@@ -59,4 +59,5 @@ require('controllers/main/rack');
 require('controllers/wizard/step2_controller');
 require('controllers/wizard/step2_controller');
 require('controllers/wizard/step3_controller');
 require('controllers/wizard/step3_controller');
 require('controllers/wizard/step4_controller');
 require('controllers/wizard/step4_controller');
-require('controllers/wizard/step5_controller');
+require('controllers/wizard/step5_controller');
+require('controllers/wizard/step6_controller');

+ 64 - 15
ambari-web/app/controllers/installer/step7_controller.js

@@ -202,26 +202,56 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
   }.property('App.router.installerStep7Controller.selectedService'),
   }.property('App.router.installerStep7Controller.selectedService'),
 
 
   selectedSlaveComponent: function () {
   selectedSlaveComponent: function () {
-    var component = this.findProperty('componentName', this.get('selectedComponentName'));
-    return component;
+    if (this.get('selectedComponentName') !== null && this.get('selectedComponentName') !== undefined) {
+      return this.findProperty('componentName', this.get('selectedComponentName'));
+    }
   }.property('selectedComponentName'),
   }.property('selectedComponentName'),
 
 
   showAddSlaveComponentGroup: function (event) {
   showAddSlaveComponentGroup: function (event) {
     var componentName = event.context;
     var componentName = event.context;
+    var component = this.get('selectedSlaveComponent');
     App.ModalPopup.show({
     App.ModalPopup.show({
       header: componentName + ' Groups',
       header: componentName + ' Groups',
       bodyClass: Ember.View.extend({
       bodyClass: Ember.View.extend({
         controllerBinding: 'App.router.slaveComponentGroupsController',
         controllerBinding: 'App.router.slaveComponentGroupsController',
-        templateName: require('templates/installer/slave_hosts_popup')
+        templateName: require('templates/installer/slave_component_hosts_popup')
       }),
       }),
-      onPrimary: function () {
+      onPrimary: function (event) {
+        if (component.tempSelectedGroups !== undefined && component.tempSelectedGroups.length){
+          component.tempSelectedGroups.forEach(function(item){
+            var changed = component.hosts.filterProperty('hostname', item.hostName);
+            changed.setEach('group', item.groupName);
+          })
+        }
+        delete component.tempSelectedGroups;
+        this.hide();
+      },
+      onSecondary: function (event) {
+        delete component.tempSelectedGroups;
+        this.hide();
+      },
+      onClose: function (event) {
+        delete component.tempSelectedGroups;
+        this.hide();
       }
       }
     });
     });
   },
   },
 
 
+  changeHostGroup: function(host, groupName){
+    var component = this.get('selectedSlaveComponent');
+    if (component.tempSelectedGroups === undefined){
+      component.tempSelectedGroups = [];
+    }
+    var values = component.tempSelectedGroups.filterProperty('hostName', host.hostname);
+    if (values.length === 0)
+      component.tempSelectedGroups.pushObject({hostName: host.hostname, groupName:groupName});
+    else
+      values.setEach('groupName', groupName);
+
+  },
+
   addSlaveComponentGroup: function (event) {
   addSlaveComponentGroup: function (event) {
-    var componentName = event.context;
-    var component = this.findProperty('componentName', componentName);
+    var component = this.get('selectedSlaveComponent');
     var newGroupName;
     var newGroupName;
     component.groups.setEach('active', false);
     component.groups.setEach('active', false);
     var newGroups = component.groups.filterProperty('type', 'new');
     var newGroups = component.groups.filterProperty('type', 'new');
@@ -234,6 +264,7 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
     }
     }
     var newGroup = {name: newGroupName, index: component.newGroupIndex, type: 'new', active: true};
     var newGroup = {name: newGroupName, index: component.newGroupIndex, type: 'new', active: true};
     component.groups.pushObject(newGroup);
     component.groups.pushObject(newGroup);
+    $('.remove-group-error').hide();
   },
   },
 
 
   showEditSlaveComponentGroups: function (event) {
   showEditSlaveComponentGroups: function (event) {
@@ -260,7 +291,7 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
 
 
   componentGroups: function () {
   componentGroups: function () {
     if (this.get('selectedComponentName') !== null) {
     if (this.get('selectedComponentName') !== null) {
-      var component = this.findProperty('componentName', this.get('selectedComponentName'));
+      var component = this.get('selectedSlaveComponent');
       if (component !== undefined && component !== null) {
       if (component !== undefined && component !== null) {
         if (component.groups === undefined){
         if (component.groups === undefined){
           component.groups = [];
           component.groups = [];
@@ -272,20 +303,35 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
     }
     }
   }.property('selectedComponentName'),
   }.property('selectedComponentName'),
 
 
-//  activeGroup: function(){
-//    return this.get('componentGroups').findProperty('active', true);
-//  }.property('selectedComponentName', 'componentGroups.@each'),
+  getHostsByGroup: function(group){
+    var component = this.get('selectedSlaveComponent');
+    return component.hosts.filterProperty('group', group.name);
+  },
+
+  getGroupsForDropDown: function(){
+     return this.get('componentGroups').getEach('name');
+  }.property('selectedComponentName', 'componentGroups.@each'),
+
+  activeGroup: function(){
+    var active = this.get('componentGroups').findProperty('active', true);
+    if (active !== undefined)
+      return active;
+  }.property('selectedComponentName', 'componentGroups.@each.active'),
 
 
   showSlaveComponentGroup: function(event){
   showSlaveComponentGroup: function(event){
-    var component = this.findProperty('componentName', this.get('selectedComponentName'));
+    var component = this.get('selectedSlaveComponent');
     component.groups.setEach('active', false);
     component.groups.setEach('active', false);
     var group = component.groups.filterProperty('name', event.context.name);
     var group = component.groups.filterProperty('name', event.context.name);
     group.setEach('active', true);
     group.setEach('active', true);
+    var assignedHosts = component.hosts.filterProperty('group', event.context.name);
+    if (assignedHosts.length === 0){
+      $('.remove-group-error').hide();
+    }
   },
   },
 
 
   removeSlaveComponentGroup: function(event){
   removeSlaveComponentGroup: function(event){
     var group = event.context;
     var group = event.context;
-    var component = this.findProperty('componentName', this.get('selectedComponentName'));
+    var component = this.get('selectedSlaveComponent');
     var assignedHosts = component.hosts.filterProperty('group', group.name);
     var assignedHosts = component.hosts.filterProperty('group', group.name);
     if (assignedHosts.length !== 0){
     if (assignedHosts.length !== 0){
       $('.remove-group-error').show();
       $('.remove-group-error').show();
@@ -293,10 +339,13 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
       $('.remove-group-error').hide();
       $('.remove-group-error').hide();
       var key = component.groups.indexOf(group);
       var key = component.groups.indexOf(group);
       component.groups.removeObject(component.groups[key]);
       component.groups.removeObject(component.groups[key]);
-//      $('#slave-group'+ group.index).remove();
 
 
-      if(group.type === 'new' && component.newGroupIndex === group.index){
-        component.newGroupIndex--;
+      var newGroups = component.groups.filterProperty('type', 'new');
+      if (newGroups.length == 0)
+        component.newGroupIndex = 0;
+      else {
+        var lastNewGroup = newGroups[newGroups.length - 1];
+        component.newGroupIndex = lastNewGroup.index;
       }
       }
       if (group.active){
       if (group.active){
         var lastGroup;
         var lastGroup;

+ 64 - 0
ambari-web/app/controllers/main/dashboard.js

@@ -21,6 +21,70 @@ var App = require('app');
 App.MainDashboardController = Em.Controller.extend({
 App.MainDashboardController = Em.Controller.extend({
   name:'mainDashboardController',
   name:'mainDashboardController',
   alerts: App.Alert.find(),
   alerts: App.Alert.find(),
+  data: {
+    hdfs:{
+      "namenode_addr":"namenode:50070",
+      "secondary_namenode_addr":"snamenode:50090",
+      "namenode_starttime":1348935028,
+      "total_nodes":"1",
+      "live_nodes":1,
+      "dead_nodes":0,
+      "decommissioning_nodes":0,
+      "dfs_blocks_underreplicated":145,
+      "safemode":false,
+      "pending_upgrades":false,
+      "dfs_configured_capacity":885570207744,
+      "dfs_percent_used":0.01,
+      "dfs_percent_remaining":95.09,
+      "dfs_total_bytes":885570207744,
+      "dfs_used_bytes":104898560,
+      "nondfs_used_bytes":43365113856,
+      "dfs_free_bytes":842100195328,
+      // additionals
+      "total_files_and_dirs": 1354,
+      "namenode_heap_used":63365113856,
+      "namenode_heap_total": 163365113856
+    },
+    mapreduce: {
+      "jobtracker_addr": "jobtracker:50030",
+      "jobtracker_starttime": 1348935243,
+      "running_jobs": 1,
+      "waiting_jobs": 0,
+      "trackers_total": "1",
+      "trackers_live": 1,
+      "trackers_graylisted": 0,
+      "trackers_blacklisted": 0,
+      "chart": [4,8,7,2,1,4,3,3,3],
+      // additionals
+      "map_slots_occuped": 4,
+      "map_slots_reserved": 8,
+      "map_slots_total": 12,
+
+      "reduce_slots_occuped": 3,
+      "reduce_slots_reserved": 7,
+      "reduce_slots_total": 11,
+
+      "completed_jobs": 3,
+      "failed_jobs": 2,
+
+      "trackers_heap_used": 1348935243,
+      "trackers_heap_total": 6648935243
+    },
+    hbase : {
+      "hbasemaster_addr": "hbasemaster:60010",
+      "total_regionservers": "1",
+      "hbasemaster_starttime": 1348935496,
+      "live_regionservers": 1,
+      "dead_regionservers": 0,
+      "regions_in_transition_count": 0,
+      "chart": [3,7,7,5,5,3,5,3,7],
+
+      "master_server_heap_used": 2348935243,
+      "master_server_heap_total": 5648935243,
+      "average_load": 1.4
+    }
+  },
+
   services:function(){
   services:function(){
     return App.router.get('mainServiceController.content');
     return App.router.get('mainServiceController.content');
   }.property('App.router.mainServiceController.content'),
   }.property('App.router.mainServiceController.content'),

+ 113 - 17
ambari-web/app/controllers/main/host/add_controller.js

@@ -178,7 +178,7 @@ App.AddHostController = Em.Controller.extend({
    *   localRepoPath: ''
    *   localRepoPath: ''
    * }
    * }
    */
    */
-  loadHosts: function () {
+  loadInstallOptions: function () {
 
 
     if (!this.content.hosts) {
     if (!this.content.hosts) {
       this.content.hosts = Em.Object.create();
       this.content.hosts = Em.Object.create();
@@ -249,8 +249,7 @@ App.AddHostController = Em.Controller.extend({
       });
       });
 
 
       hosts.pushObject(hostInfo);
       hosts.pushObject(hostInfo);
-    }
-    ;
+    };
 
 
     console.log('TRACE: pushing ' + hosts);
     console.log('TRACE: pushing ' + hosts);
     return hosts;
     return hosts;
@@ -281,6 +280,15 @@ App.AddHostController = Em.Controller.extend({
     });
     });
     console.log('addHostController:saveConfirmedHosts: save hosts ', hostInfo);
     console.log('addHostController:saveConfirmedHosts: save hosts ', hostInfo);
     App.db.setHosts(hostInfo);
     App.db.setHosts(hostInfo);
+    this.set('content.hostsInfo', hostInfo);
+  },
+
+  /**
+   * Load confirmed hosts.
+   * Will be used at <code>Assign Masters(step5)</code> step
+   */
+  loadConfirmedHosts : function(){
+    this.set('content.hostsInfo', App.db.getHosts());
   },
   },
 
 
   /**
   /**
@@ -309,6 +317,7 @@ App.AddHostController = Em.Controller.extend({
     });
     });
     this.set('content.services', servicesInfo);
     this.set('content.services', servicesInfo);
     console.log('addHostController.loadServices: loaded data ', servicesInfo);
     console.log('addHostController.loadServices: loaded data ', servicesInfo);
+    console.log('selected services ', servicesInfo.filterProperty('isSelected', true).mapProperty('serviceName'));
   },
   },
 
 
   /**
   /**
@@ -317,6 +326,8 @@ App.AddHostController = Em.Controller.extend({
    */
    */
   saveServices: function (stepController) {
   saveServices: function (stepController) {
     var serviceNames = [];
     var serviceNames = [];
+    // we can also do it without stepController since all data,
+    // changed at page, automatically changes in model(this.content.services)
     App.db.setService(stepController.get('content'));
     App.db.setService(stepController.get('content'));
     stepController.filterProperty('isSelected', true).forEach(function (item) {
     stepController.filterProperty('isSelected', true).forEach(function (item) {
       serviceNames.push(item.serviceName);
       serviceNames.push(item.serviceName);
@@ -325,31 +336,116 @@ App.AddHostController = Em.Controller.extend({
     console.log('addHostController.saveServices: saved data ', serviceNames);
     console.log('addHostController.saveServices: saved data ', serviceNames);
   },
   },
 
 
+  /**
+   * Save Master Component Hosts data to Main Controller
+   * @param stepController App.WizardStep5Controller
+   */
+  saveMasterComponentHosts: function (stepController) {
+    var obj = stepController.get('selectedServicesMasters');
+    var masterComponentHosts = [];
+    obj.forEach(function (_component) {
+      masterComponentHosts.push({
+        component: _component.component_name,
+        hostName: _component.selectedHost
+      });
+    });
+
+    console.log("AddHostController.saveComponentHosts: saved hosts ", masterComponentHosts);
+    App.db.setMasterComponentHosts(masterComponentHosts);
+    this.set('content.masterComponentHosts', masterComponentHosts);
+  },
+
+  /**
+   * Load master component hosts data for using in required step controllers
+   */
+  loadMasterComponentHosts: function () {
+    var masterComponentHosts = App.db.getMasterComponentHosts();
+    this.set("content.masterComponentHosts", masterComponentHosts);
+    console.log("AddHostController.loadMasterComponentHosts: loaded hosts ", masterComponentHosts);
+  },
+
+  /**
+   * Save slaveHostComponents to main controller
+   * @param stepController
+   */
+  saveSlaveComponentHosts : function(stepController){
+
+    var hosts = stepController.get('hosts');
+    var isMrSelected = stepController.get('isMrSelected');
+    var isHbSelected = stepController.get('isHbSelected');
+
+    App.db.setHostSlaveComponents(hosts);
+    this.set('content.hostSlaveComponents', hosts);
+
+    var dataNodeHosts = [];
+    var taskTrackerHosts = [];
+    var regionServerHosts = [];
+
+    hosts.forEach(function (host) {
+      if (host.get('isDataNode')) {
+        dataNodeHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+      if (isMrSelected && host.get('isTaskTracker')) {
+        taskTrackerHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+      if (isHbSelected && host.get('isRegionServer')) {
+        regionServerHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+    }, this);
+
+    var slaveComponentHosts = [];
+    slaveComponentHosts.push({
+      componentName: 'DATANODE',
+      displayName: 'DataNode',
+      hosts: dataNodeHosts
+    });
+    if (isMrSelected) {
+      slaveComponentHosts.push({
+        componentName: 'TASKTRACKER',
+        displayName: 'TaskTracker',
+        hosts: taskTrackerHosts
+      });
+    }
+    if (isHbSelected) {
+      slaveComponentHosts.push({
+        componentName: 'HBASE_REGIONSERVER',
+        displayName: 'RegionServer',
+        hosts: regionServerHosts
+      });
+    }
+
+    App.db.setSlaveComponentHosts(slaveComponentHosts);
+    this.set('content.slaveComponentHosts', slaveComponentHosts);
+  },
+
   /**
   /**
    * Load data for all steps until <code>current step</code>
    * Load data for all steps until <code>current step</code>
    */
    */
   loadAllPriorSteps: function () {
   loadAllPriorSteps: function () {
     var step = this.get('currentStep');
     var step = this.get('currentStep');
     switch (step) {
     switch (step) {
-      /*case '10':
-       this.get('installerStep9Controller').loadStep();
-       case '9':
-       this.get('installerStep8Controller').loadStep();
-       case '8':
-       this.get('installerStep7Controller').loadStep();
-       case '7':
-       this.get('installerStep6Controller').loadStep();
-       case '6':
-       this.get('installerStep5Controller').loadStep();
-       case '5':
-       this.get('installerStep4Controller').loadStep();*/
+      case '7':
+        //current
+      case '6':
+        //Sasha
+      case '5':
+        this.loadMasterComponentHosts();
       case '4':
       case '4':
-      //this.get('installerStep3Controller').loadStep();
+        this.loadConfirmedHosts();
       case '3':
       case '3':
         this.loadServices();
         this.loadServices();
       case '2':
       case '2':
       case '1':
       case '1':
-        this.loadHosts();
+        this.loadInstallOptions();
     }
     }
   },
   },
 
 

+ 55 - 78
ambari-web/app/controllers/wizard/step5_controller.js

@@ -19,17 +19,16 @@
 var App = require('app');
 var App = require('app');
 
 
 App.WizardStep5Controller = Em.Controller.extend({
 App.WizardStep5Controller = Em.Controller.extend({
-  //properties
-  name: "wizardStep5Controller",
-  hosts: [],
-  selectedServices: [],
-  selectedServicesMasters: [],
-  zId: 0,
-  components: require('data/service_components'),
 
 
-  /*
-   Below function retrieves host information from local storage
-   */
+  name:"wizardStep5Controller",
+
+  hosts:[],
+
+  selectedServices:[],
+  selectedServicesMasters:[],
+  zId:0,
+
+  components: require('data/service_components'),
 
 
   clearStep: function () {
   clearStep: function () {
     this.set('hosts', []);
     this.set('hosts', []);
@@ -41,56 +40,52 @@ App.WizardStep5Controller = Em.Controller.extend({
   loadStep: function () {
   loadStep: function () {
     console.log("TRACE: Loading step5: Assign Masters");
     console.log("TRACE: Loading step5: Assign Masters");
     this.clearStep();
     this.clearStep();
-    this.renderHostInfo(this.loadHostInfo());
-    this.renderComponents(this.loadComponents(this.loadServices()));
+    this.renderHostInfo();
+    this.renderComponents(this.loadComponents());
   },
   },
 
 
-  loadHostInfo: function () {
-    var hostInfo = [];
-    hostInfo = App.db.getHosts();
-    var hosts = new Ember.Set();
-    for (var index in hostInfo) {
-      hosts.add(hostInfo[index]);
-      console.log("TRACE: host name is: " + hostInfo[index].name);
-    }
-    return hosts.filterProperty('bootStatus', 'success');
-  },
+  /**
+   * Load active host list to <code>hosts</code> variable
+   */
+  renderHostInfo: function () {
 
 
+    var hostInfo = this.get('content.hostsInfo');
 
 
-  renderHostInfo: function (hostsInfo) {
+    for (var index in hostInfo) {
+      var _host = hostInfo[index];
+      if(_host.bootStatus === 'success'){
 
 
-    //wrap the model data into
+        var hostObj = Ember.Object.create({
+          host_name: _host.name,
+          cpu: _host.cpu,
+          memory: _host.memory,
+          host_info: "%@ ( %@GB %@cores )".fmt(_host.name, _host.memory, _host.cpu)
+        });
 
 
-    hostsInfo.forEach(function (_host) {
-      var hostObj = Ember.Object.create({
-        host_name: _host.name,
-        cpu: _host.cpu,
-        memory: _host.memory
-      });
-      console.log('pushing ' + hostObj.host_name);
-      hostObj.set("host_info", "" + hostObj.get("host_name") + " ( " + hostObj.get("memory") + "GB" + " " + hostObj.get("cpu") + "cores )");
-      this.get("hosts").pushObject(hostObj);
-    }, this);
+        this.get("hosts").pushObject(hostObj);
+      }
+    }
+  },
 
 
+  /**
+   * Load services info to appropriate variable and return masterComponentHosts
+   * @return {Ember.Set}
+   */
+  loadComponents: function () {
 
 
-  },
+    var services = this.get('content.services')
+                      .filterProperty('isSelected', true).mapProperty('serviceName');
 
 
-  loadServices: function () {
-    var serviceInfo = App.db.getService();
-    var services = serviceInfo.filterProperty('isSelected', true).mapProperty('serviceName');
     services.forEach(function (item) {
     services.forEach(function (item) {
-      console.log("TRACE: service name is: " + item);
       this.get("selectedServices").pushObject(Ember.Object.create({service_name: item}));
       this.get("selectedServices").pushObject(Ember.Object.create({service_name: item}));
     }, this);
     }, this);
 
 
-    return services;
-
-  },
+    var masterHosts = this.get('content.masterHosts');
 
 
-  loadComponents: function (services) {
     var components = new Ember.Set();
     var components = new Ember.Set();
-    if (App.db.getMasterComponentHosts() === undefined) {
-      var masterComponents = this.components.filterProperty('isMaster', true);
+    if (!masterHosts) {
+
+      var masterComponents = this.get('components').filterProperty('isMaster', true);
       for (var index in services) {
       for (var index in services) {
         var componentInfo = masterComponents.filterProperty('service_name', services[index]);
         var componentInfo = masterComponents.filterProperty('service_name', services[index]);
         componentInfo.forEach(function (_componentInfo) {
         componentInfo.forEach(function (_componentInfo) {
@@ -102,27 +97,28 @@ App.WizardStep5Controller = Em.Controller.extend({
           components.add(componentObj);
           components.add(componentObj);
         }, this);
         }, this);
       }
       }
+
     } else {
     } else {
-      var masterComponentHosts = App.db.getMasterComponentHosts();
-      masterComponentHosts.forEach(function (_masterComponentHost) {
+
+      masterHosts.forEach(function (_masterComponentHost) {
         var componentObj = {};
         var componentObj = {};
-        componentObj.component_name = _masterComponentHost.component;
+        componentObj.component_name =_masterComponentHost.component;
         componentObj.selectedHost = _masterComponentHost.hostName;   // call the method that plays selectNode algorithm or fetches from server
         componentObj.selectedHost = _masterComponentHost.hostName;   // call the method that plays selectNode algorithm or fetches from server
         componentObj.availableHosts = [];
         componentObj.availableHosts = [];
         components.add(componentObj);
         components.add(componentObj);
       }, this);
       }, this);
+
     }
     }
     return components;
     return components;
   },
   },
 
 
-  getMasterComponents: function () {
-    return (this.get('selectedServicesMasters').slice(0));
-  },
-
+  /**
+   * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
+   * @param masterComponents
+   */
   renderComponents: function (masterComponents) {
   renderComponents: function (masterComponents) {
     var zookeeperComponent = null, componentObj = null;
     var zookeeperComponent = null, componentObj = null;
-    var services = [];
-    services = this.getMasterComponents();
+    var services = this.get('selectedServicesMasters').slice(0);
     if (services.length) {
     if (services.length) {
       this.set('selectedServicesMasters', []);
       this.set('selectedServicesMasters', []);
     }
     }
@@ -312,6 +308,11 @@ App.WizardStep5Controller = Em.Controller.extend({
   },
   },
 
 
 
 
+  /**
+   * Return hostName of masterNode for specified service
+   * @param componentName
+   * @return {*}
+   */
   selectHost: function (componentName) {
   selectHost: function (componentName) {
     var noOfHosts = this.get('hosts').length;
     var noOfHosts = this.get('hosts').length;
     if (componentName === 'KERBEROS_SERVER') {
     if (componentName === 'KERBEROS_SERVER') {
@@ -561,31 +562,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     else {
     else {
       return -1;
       return -1;
     }
     }
-  },
-
-  saveComponentHostsToDb: function () {
-    var obj = this.get('selectedServicesMasters');
-    var masterComponentHosts = [];
-    var inc = 0;
-    var array = [];
-    obj.forEach(function (_component) {
-      var hostArr = [];
-      masterComponentHosts.push({
-        component: _component.component_name,
-        hostName: _component.selectedHost
-      });
-    });
-
-    App.db.setMasterComponentHosts(masterComponentHosts);
-
-  },
-
-  submit: function () {
-    this.saveComponentHostsToDb();
-    App.router.send('next');
   }
   }
-
-
 });
 });
 
 
 
 

+ 210 - 0
ambari-web/app/controllers/wizard/step6_controller.js

@@ -0,0 +1,210 @@
+/**
+ * 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');
+
+/**
+ * By Step 6, we have the following information stored in App.db and set on this
+ * controller by the router:
+ *
+ *   hosts: App.db.hosts (list of all hosts the user selected in Step 3)
+ *   selectedServiceNames: App.db.selectedServiceNames (the services that the user selected in Step 4)
+ *   masterComponentHosts: App.db.masterComponentHosts (master-components-to-hosts mapping the user selected in Step 5)
+ *
+ * Step 6 will set the following information in App.db:
+ *   hostSlaveComponents: App.db.hostSlaveComponents (hosts-to-slave-components mapping the user selected in Steo 6)
+ *   slaveComponentHosts: App.db.slaveComponentHosts (slave-components-to-hosts mapping the user selected in Step 6)
+ *
+ */
+App.WizardStep6Controller = Em.Controller.extend({
+
+  hosts: [],
+  // TODO: hook up with user host selection
+  rawHosts: [],
+  masterComponentHosts: require('data/mock/master_component_hosts'),
+
+  isAllDataNodes: function () {
+    return this.get('hosts').everyProperty('isDataNode', true);
+  }.property('hosts.@each.isDataNode'),
+
+  isAllTaskTrackers: function () {
+    return this.get('hosts').everyProperty('isTaskTracker', true);
+  }.property('hosts.@each.isTaskTracker'),
+
+  isAllRegionServers: function () {
+    return this.get('hosts').everyProperty('isRegionServer', true);
+  }.property('hosts.@each.isRegionServer'),
+
+  isNoDataNodes: function () {
+    return this.get('hosts').everyProperty('isDataNode', false);
+  }.property('hosts.@each.isDataNode'),
+
+  isNoTaskTrackers: function () {
+    return this.get('hosts').everyProperty('isTaskTracker', false);
+  }.property('hosts.@each.isTaskTracker'),
+
+  isNoRegionServers: function () {
+    return this.get('hosts').everyProperty('isRegionServer', false);
+  }.property('hosts.@each.isRegionServer'),
+
+  /**
+   * Return whether Hbase service was selected or not.
+   * Calculate this information on <code>content.services</code> variable
+   * @return Boolean
+   */
+  isHbSelected: function () {
+    return this.get('content.services').findProperty('serviceName', 'HBASE').get('isSelected');
+  }.property('content'),
+
+  /**
+   * Return whether MapReduce service was selected or not.
+   * Calculate this information on <code>content.services</code> variable
+   * @return Boolean
+   */
+	isMrSelected: function () {
+    return this.get('content.services').findProperty('serviceName', 'MAPREDUCE').get('isSelected');
+	}.property('content'),
+
+  /**
+   * Check whether current host is currently selected as master
+   * @param hostname
+   * @return {Boolean}
+   */
+  hasMasterComponents: function (hostname) {
+    return this.get('content.masterComponentHosts').someProperty('hostName', hostname);
+  },
+
+  selectAllDataNodes: function () {
+    this.get('hosts').setEach('isDataNode', true);
+  },
+
+  selectAllTaskTrackers: function () {
+    this.get('hosts').setEach('isTaskTracker', true);
+  },
+
+  selectAllRegionServers: function () {
+    this.get('hosts').setEach('isRegionServer', true);
+  },
+
+  deselectAllDataNodes: function () {
+    this.get('hosts').setEach('isDataNode', false);
+  },
+
+  deselectAllTaskTrackers: function () {
+    this.get('hosts').setEach('isTaskTracker', false);
+  },
+
+  deselectAllRegionServers: function () {
+    this.get('hosts').setEach('isRegionServer', false);
+  },
+
+  clearStep: function () {
+    this.set('hosts', []);
+  },
+
+  loadStep: function () {
+    console.log("TRACE: Loading step6: Assign Slaves");
+    this.clearStep();
+    this.renderSlaveHosts();
+  },
+
+  /**
+   * Get active host names
+   * @return {Array}
+   */
+  getHostNames: function () {
+    var hostInfo = this.get('content.hostsInfo');
+    var hostNames = [];
+    for (var index in hostInfo) {
+      if (hostInfo[index].bootStatus === 'success')
+        hostNames.push(hostInfo[index].name);
+    }
+    return hostNames;
+  },
+
+  /**
+   * Load all data needed for this module. Then it automatically renders in template
+   * @return {Ember.Set}
+   */
+  renderSlaveHosts: function () {
+    var hostsObj = Em.Set.create();
+    var allHosts = this.getHostNames();
+    var slaveHosts = this.get('content.slaveComponentHosts');
+
+    allHosts.forEach(function (_hostName) {
+      hostsObj.push(Em.Object.create({
+        hostname: _hostName
+      }));
+    });
+
+    if (!slaveHosts) { // we are at this page for the first time
+
+      hostsObj.forEach(function (host) {
+        host.isDataNode = host.isTaskTracker
+            = host.isRegionServer = !this.hasMasterComponents(host.hostname);
+      }, this);
+
+    } else {
+
+			var dataNodes = slaveHosts.findProperty('componentName', 'DATANODE');
+      dataNodes.hosts.forEach(function (_dataNode) {
+        var dataNode = hostsObj.findProperty('hostname', _dataNode.hostname);
+        if (dataNode) {
+          dataNode.isDataNode = true;
+        }
+      });
+
+			var taskTrackers = slaveHosts.findProperty('componentName', 'TASKTRACKER');
+      taskTrackers.hosts.forEach(function (_taskTracker) {
+        var taskTracker = hostsObj.findProperty('hostname', _taskTracker.hostname);
+        if (taskTracker) {
+          taskTracker.isTaskTracker = true;
+        }
+      });
+
+      if (this.get('isHbSelected')) {
+				var regionServers = slaveHosts.findProperty('componentName', 'HBASE_REGIONSERVER');
+        regionServers.hosts.forEach(function (_regionServer) {
+          var regionServer = hostsObj.findProperty('hostname', _regionServer.hostname);
+          if (regionServer) {
+            regionServer.isRegionServer = true;
+          }
+        });
+      }
+
+    }
+
+    hostsObj.forEach(function(host){
+      this.get('hosts').pushObject(host);
+    }, this);
+  },
+
+  /**
+   * Validate form. Return do we have errors or not
+   * @return {Boolean}
+   */
+  validate: function () {
+    var isOK =  !(this.get('isNoDataNodes') || ( this.get('isMrSelected') && this.get('isNoTaskTrackers')) || ( this.get('isHbSelected') &&this.get('isNoRegionServers')));
+    if(!isOK){
+      this.set('errorMessage', Ember.I18n.t('installer.step6.error.mustSelectOne'));
+    }
+    return isOK;
+  }
+
+});

+ 268 - 251
ambari-web/app/messages.js

@@ -18,269 +18,286 @@
 
 
 Em.I18n.translations = {
 Em.I18n.translations = {
 
 
-  'app.name': 'Ambari',
-
-  'login.header': 'Sign in',
-  'login.username': 'Username',
-  'login.password': 'Password',
-  '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.header': 'Cluster Install Wizard',
-  'installer.step1.header': 'Welcome',
-  'installer.step1.body.header': 'Welcome to Apache Ambari!',
-  'installer.step1.body': 'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
+  'app.name':'Ambari',
+
+  'login.header':'Sign in',
+  'login.username':'Username',
+  'login.password':'Password',
+  '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.header':'Cluster Install Wizard',
+  'installer.step1.header':'Welcome',
+  'installer.step1.body.header':'Welcome to Apache Ambari!',
+  'installer.step1.body':'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
     'We will walk you through the cluster installation process with this step-by-step wizard.',
     'We will walk you through the cluster installation process with this step-by-step wizard.',
-  'installer.step1.clusterName': 'Name your cluster',
-  'installer.step1.clusterName.tooltip.title': 'Cluster Name',
-  'installer.step1.clusterName.tooltip.content': 'Enter a unique cluster name. Cluster name cannot be changed later.',
-  'installer.step1.clusterName.error.required': 'Cluster Name is required',
-  'installer.step1.clusterName.error.whitespaces': 'Cluster Name cannot contain white spaces',
-  'installer.step1.clusterName.error.specialChar': 'Cluster Name cannot contain special characters',
-
-  'installer.step2.header': 'Install Options',
-  'installer.step2.body': 'Enter the list of hosts to be included in the cluster, provide your SSH key, and optionally specify a local repository.',
-  'installer.step2.targetHosts': 'Target Hosts',
-  'installer.step2.targetHosts.info': 'Enter a list of host names, one per line',
-  '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 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': 'Host Connectivity Information',
-  'installer.step2.sshKey.info': 'Provide your SSH Private Key (<b>id_rsa</b> for <b>root</b>)',
-  'installer.step2.sshKey.error.required': 'SSH Private Key is required',
-  'installer.step2.passphrase.error.match': 'Passphrases do not match',
-  'installer.step2.manualInstall.label': 'Do not use SSH to automatically configure hosts ',
-  'installer.step2.manualInstall.info': 'By not using SSH to connect to the target hosts, you must manually install and start the ' +
+  'installer.step1.clusterName':'Name your cluster',
+  'installer.step1.clusterName.tooltip.title':'Cluster Name',
+  'installer.step1.clusterName.tooltip.content':'Enter a unique cluster name. Cluster name cannot be changed later.',
+  'installer.step1.clusterName.error.required':'Cluster Name is required',
+  'installer.step1.clusterName.error.whitespaces':'Cluster Name cannot contain white spaces',
+  'installer.step1.clusterName.error.specialChar':'Cluster Name cannot contain special characters',
+
+  'installer.step2.header':'Install Options',
+  'installer.step2.body':'Enter the list of hosts to be included in the cluster, provide your SSH key, and optionally specify a local repository.',
+  'installer.step2.targetHosts':'Target Hosts',
+  'installer.step2.targetHosts.info':'Enter a list of host names, one per line',
+  '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 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':'Host Connectivity Information',
+  'installer.step2.sshKey.info':'Provide your SSH Private Key (<b>id_rsa</b> for <b>root</b>)',
+  'installer.step2.sshKey.error.required':'SSH Private Key is required',
+  'installer.step2.passphrase.error.match':'Passphrases do not match',
+  'installer.step2.manualInstall.label':'Do not use SSH to automatically configure hosts ',
+  'installer.step2.manualInstall.info':'By not using SSH to connect to the target hosts, you must manually install and start the ' +
     'Ambari Agent on each host in order for the wizard to perform the necessary configurations and software installs.',
     'Ambari Agent on each host in order for the wizard to perform the necessary configurations and software installs.',
-  'installer.step2.advancedOption': 'Advanced Options',
-  'installer.step2.repoConf': 'Software Repository Configuration File Path',
-  'installer.step2.localRepo.header': 'Software repository',
-  'installer.step2.localRepo.label': 'Use a local software repository',
-  'installer.step2.localRepo.error.required': 'Local repository file path is required',
-  'installer.step2.localRepo.info': 'The repository configuration file should be installed on each host in your cluster. ' +
+  'installer.step2.advancedOption':'Advanced Options',
+  'installer.step2.repoConf':'Software Repository Configuration File Path',
+  'installer.step2.localRepo.header':'Software repository',
+  'installer.step2.localRepo.label':'Use a local software repository',
+  'installer.step2.localRepo.error.required':'Local repository file path is required',
+  'installer.step2.localRepo.info':'The repository configuration file should be installed on each host in your cluster. ' +
     'This file instructs the package manager to use your local software repository to retrieve software packages, instead of ' +
     'This file instructs the package manager to use your local software repository to retrieve software packages, instead of ' +
     'downloading them from the internet.',
     'downloading them from the internet.',
-  'installer.step2.localRepo.tooltip.title': 'Local Software Repository',
-  'installer.step2.localRepo.tooltip.content': 'The repository configuration file should be installed on each host ' +
+  'installer.step2.localRepo.tooltip.title':'Local Software Repository',
+  'installer.step2.localRepo.tooltip.content':'The repository configuration file should be installed on each host ' +
     'in your cluster. This file instructs package manager to use your local' +
     'in your cluster. This file instructs package manager to use your local' +
     'software repository to retrieve software packages, instead of using the internet.',
     'software repository to retrieve software packages, instead of using the internet.',
-  'installer.step2.manualInstall.tooltip.title': 'Not Using SSH (Manual Install)',
-  'installer.step2.manualInstall.tooltip.content': 'If you do not wish Ambari to automatically configure the target hosts via SSH,' +
+  'installer.step2.manualInstall.tooltip.title':'Not Using SSH (Manual Install)',
+  'installer.step2.manualInstall.tooltip.content':'If you do not wish Ambari to automatically configure the target hosts via SSH,' +
     ' you have the option of configuring them yourself.  This involves installing and starting Ambari Agent on each of your target hosts.',
     ' you have the option of configuring them yourself.  This involves installing and starting Ambari Agent on each of your target hosts.',
-  'installer.step2.manualInstall.popup.header': 'Before You Proceed',
-  'installer.step2.manualInstall.popup.body': 'You must install Ambari Agents on each host you want to manage before you proceed.  <a href="#" target="_blank">Learn more</a>',
+  'installer.step2.manualInstall.popup.header':'Before You Proceed',
+  'installer.step2.manualInstall.popup.body':'You must install Ambari Agents on each host you want to manage before you proceed.  <a href="#" target="_blank">Learn more</a>',
 
 
-  'installer.step3.header': 'Confirm Hosts',
-  'installer.step3.body': 'Here are the results of the host discovery process.<br>' +
+  'installer.step3.header':'Confirm Hosts',
+  'installer.step3.body':'Here are the results of the host discovery process.<br>' +
     'Please verify and remove the ones that you do not want to be part of the cluster.',
     'Please verify and remove the ones that you do not want to be part of the cluster.',
-  'installer.step3.hostLog.popup.header': 'Log file for the host',
-  'installer.step3.hostLog.popup.body': 'Placeholder for the log file',
-  'installer.step3.hosts.remove.popup.header': 'Remove Hosts',
-  'installer.step3.hosts.remove.popup.body': 'Are you sure you want to remove the selected host(s)?',
-  'installer.step3.hosts.retry.popup.header': 'Retry Host Discovery',
-  'installer.step3.hosts.retry.popup.body': 'Are you sure you want to retry discovery of the selected host(s)?',
-
-  'installer.step4.header': 'Choose Services',
-  'installer.step4.body': 'Choose which services you want to install on your cluster.<br>Note that some services have dependencies (e.g., HBase requires ZooKeeper.)',
-  'installer.step4.mapreduceCheck.popup.header': 'MapReduce Needed',
-  'installer.step4.mapreduceCheck.popup.body': 'You did not select MapReduce, but it is needed by other services you selected.  We will automatically add MapReduce.  Is this OK?',
-  'installer.step4.monitoringCheck.popup.header': 'Limited Functionality Warning',
-  'installer.step4.monitoringCheck.popup.body': 'You did not select Nagios and/or Ganglia.  If both are not selected, monitoring and alerts will not function properly.  Is this OK?',
-
-  'installer.step5.header': 'Assign Masters',
-  'installer.step5.attention': ' hosts not running master services',
-  'installer.step5.body': 'Assign master components to hosts you want to run them on.',
-
-  'installer.step6.header': 'Assign Slaves',
-  'installer.step6.body': 'Assign slave components to hosts you want to run them on.',
-  'installer.step6.error.mustSelectOne': 'You must assign at least one host to each.',
-
-  'installer.step7.header': 'Customize Services',
-  'installer.step7.body': 'We have come up with recommended configurations for the services you selected.  Customize them as you see fit.',
-  'installer.step7.attentionNeeded': '<strong>Attention:</strong> Some configurations need your attention before you can proceed.',
-
-  'installer.step8.header': 'Review',
-  'installer.step8.body': 'Please review the cluster configuration before installation',
-
-  'installer.step9.header': 'Install, Start and Test',
-  'installer.step9.body': 'Wait to complete the cluster installation. Installing, Starting and Testing selected services',
-  'installer.step9.status.success': 'Successfully installed the cluster',
-  'installer.step9.status.failed': 'Failure in installation',
-  'installer.step9.host.status.success': 'success',
-  'installer.step9.host.status.warning': 'tolerable failures encountered',
-  'installer.step9.host.status.failed': 'failures encountered',
-
-  'installer.step10.header': 'Summary',
-
-  'form.create': 'Create',
-  'form.save': 'Save',
-  'form.cancel': 'Cancel',
-  'form.password': 'Password',
-  'form.passwordRetype': 'Retype Password',
-  'form.saveSuccess': 'Successfully saved.',
-  'form.saveError': 'Sorry, errors occured.',
-
-  'form.validator.invalidIp': 'Please enter valid ip address',
-
-  'admin.advanced.title': 'Advanced',
-  'admin.advanced.caution': 'This section is for advanced user only.<br/>Proceed with caution.',
-  'admin.advanced.button.uninstallIncludingData': 'Uninstall cluster including all data.',
-  'admin.advanced.button.uninstallKeepData': 'Uninstall cluster but keep data.',
-
-  'admin.advanced.popup.header': 'Uninstall Cluster',
+  'installer.step3.hostLog.popup.header':'Log file for the host',
+  'installer.step3.hostLog.popup.body':'Placeholder for the log file',
+  'installer.step3.hosts.remove.popup.header':'Remove Hosts',
+  'installer.step3.hosts.remove.popup.body':'Are you sure you want to remove the selected host(s)?',
+  'installer.step3.hosts.retry.popup.header':'Retry Host Discovery',
+  'installer.step3.hosts.retry.popup.body':'Are you sure you want to retry discovery of the selected host(s)?',
+
+  'installer.step4.header':'Choose Services',
+  'installer.step4.body':'Choose which services you want to install on your cluster.<br>Note that some services have dependencies (e.g., HBase requires ZooKeeper.)',
+  'installer.step4.mapreduceCheck.popup.header':'MapReduce Needed',
+  'installer.step4.mapreduceCheck.popup.body':'You did not select MapReduce, but it is needed by other services you selected.  We will automatically add MapReduce.  Is this OK?',
+  'installer.step4.monitoringCheck.popup.header':'Limited Functionality Warning',
+  'installer.step4.monitoringCheck.popup.body':'You did not select Nagios and/or Ganglia.  If both are not selected, monitoring and alerts will not function properly.  Is this OK?',
+
+  'installer.step5.header':'Assign Masters',
+  'installer.step5.attention':' hosts not running master services',
+  'installer.step5.body':'Assign master components to hosts you want to run them on.',
+
+  'installer.step6.header':'Assign Slaves',
+  'installer.step6.body':'Assign slave components to hosts you want to run them on.',
+  'installer.step6.error.mustSelectOne':'You must assign at least one host to each.',
+
+  'installer.step7.header':'Customize Services',
+  'installer.step7.body':'We have come up with recommended configurations for the services you selected.  Customize them as you see fit.',
+  'installer.step7.attentionNeeded':'<strong>Attention:</strong> Some configurations need your attention before you can proceed.',
+
+  'installer.step8.header':'Review',
+  'installer.step8.body':'Please review the cluster configuration before installation',
+
+  'installer.step9.header':'Install, Start and Test',
+  'installer.step9.body':'Wait to complete the cluster installation. Installing, Starting and Testing selected services',
+  'installer.step9.status.success':'Successfully installed the cluster',
+  'installer.step9.status.failed':'Failure in installation',
+  'installer.step9.host.status.success':'success',
+  'installer.step9.host.status.warning':'tolerable failures encountered',
+  'installer.step9.host.status.failed':'failures encountered',
+
+  'installer.step10.header':'Summary',
+
+  'form.create':'Create',
+  'form.save':'Save',
+  'form.cancel':'Cancel',
+  'form.password':'Password',
+  'form.passwordRetype':'Retype Password',
+  'form.saveSuccess':'Successfully saved.',
+  'form.saveError':'Sorry, errors occured.',
+
+  'form.validator.invalidIp':'Please enter valid ip address',
+
+  'admin.advanced.title':'Advanced',
+  'admin.advanced.caution':'This section is for advanced user only.<br/>Proceed with caution.',
+  'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including all data.',
+  'admin.advanced.button.uninstallKeepData':'Uninstall cluster but keep data.',
+
+  'admin.advanced.popup.header':'Uninstall Cluster',
   /*'admin.advanced.popup.text':'Uninstall Cluster',*/
   /*'admin.advanced.popup.text':'Uninstall Cluster',*/
 
 
-  'admin.audit.grid.date': "Date/Time",
-  'admin.audit.grid.category': "Category",
-  'admin.audit.grid.operationName': "Operation",
-  'admin.audit.grid.performedBy': "Performed By",
-  'admin.audit.grid.service': "Category",
-
-  'admin.authentication.form.method.database': 'Use Ambari Database to authenticate users',
-  'admin.authentication.form.method.ldap': 'Use LDAP/Active Directory to authenticate',
-  'admin.authentication.form.primaryServer': 'Primary Server',
-  'admin.authentication.form.secondaryServer': 'Secondary Server',
-  'admin.authentication.form.useSsl': 'Use SSL',
-  'admin.authentication.form.bind.anonymously': "Bind Anonymously",
-  'admin.authentication.form.bind.useCrenedtials': "Use Credentials To Bind",
-  'admin.authentication.form.bindUserDN': 'Bind User DN',
-  'admin.authentication.form.searchBaseDN': 'Search Base DN',
-  'admin.authentication.form.usernameAttribute': 'Username Attribute',
-
-  'admin.authentication.form.userDN': 'User DN',
-  'admin.authentication.form.password': 'Password',
-  'admin.authentication.form.configurationTest': 'Configuration Test',
-  'admin.authentication.form.testConfiguration': 'Test Configuration',
-
-  'admin.authentication.form.test.success': 'The configuration passes the test',
-  'admin.authentication.form.test.fail': 'The configuration fails the test',
-
-  'admin.security.title': 'Kerberos Security has not been enabled on this cluster.',
-  'admin.security.button.enable': 'Enable Kerberos Security on this cluster',
-
-  'admin.users.ldapAuthentionUsed': 'LDAP Authentication is being used to authenticate users',
-  'admin.users.deleteYourselfMessage': 'You can\'t delete yourself',
-  'admin.users.addButton': 'Add User',
-  'admin.users.delete': 'delete',
-  'admin.users.edit': 'edit',
-  'admin.users.privileges': 'Admin',
-  'admin.users.password': 'Password',
-  'admin.users.passwordRetype': 'Retype Password',
-  'admin.users.username': 'Username',
-
-  'question.sure': 'Are you sure?',
-
-  'services.service.start': 'Start',
-  'services.service.stop': 'Stop',
-  'services.service.start.popup.header': 'Confirmation',
-  'services.service.stop.popup.header': 'Confirmation',
-  'services.service.start.popup.body': 'Are you sure?',
-  'services.service.stop.popup.body': 'Are you sure?',
-  'services.service.summary.version': 'Version',
-  'services.service.summary.nameNode': 'NameNode Web UI',
-  'services.service.summary.nameNodeUptime': 'NameNode Uptime',
-  'services.service.summary.nameNodeHeap': 'NameNode Heap',
-  'services.service.summary.pendingUpgradeStatus': 'HDFS Pending Upgrade Status',
-  'services.service.summary.safeModeStatus': 'HDFS Safe Mode Status',
-  'services.service.summary.dataNodes': 'DataNodes (live/dead/decom)',
-  'services.service.summary.diskCapacity': 'HDFS Disk Capacity',
-  'services.service.summary.blocksTotal': 'Blocks (total)',
-  'services.service.summary.blockErrors': 'Block Errors (corr./miss./underrep.)',
-  'services.service.summary.totalFiles': 'Total Files + Directory Count',
-  'services.service.summary.jobTracker': 'Job Tracker Web UI',
-  'services.service.summary.jobTrackerUptime': 'Job Tracker Uptime',
-  'services.service.summary.trackersLiveTotal': 'Trackers (live/total)',
-  'services.service.summary.trackersBlacklistGraylist': 'Trackers (blacklist/graylist/excl.)',
-  'services.service.summary.jobTrackerHeap': 'Job Tracker Heap (used/max)',
-  'services.service.summary.totalSlotsCapacity': 'Total Slots Capacity (maps/reduces/avg per node)',
-  'services.service.summary.totalJobs': 'Total Jobs (submitted/completed)',
-  'services.service.summary.currentSlotUtiliMaps': 'Current Slot Utilization: Maps (occupied/reserved)',
-  'services.service.summary.currentSlotUtiliReduces': 'Current Slot Utilization: Reduces (occupied/reserved)',
-  'services.service.summary.tasksMaps': 'Tasks: Maps (running/waiting)',
-  'services.service.summary.tasksReduces': 'Tasks: Reduces (running/waiting)',
-  'services.service.summary.hbaseMaster': 'HBase Master Web UI',
-  'services.service.summary.regionServerCount': 'Region Server Count (live/dead)',
-  'services.service.summary.regionInTransition': 'Region In Transition',
-  'services.service.summary.masterStarted': 'Master Started',
-  'services.service.summary.masterActivated': 'Master Activated',
-  'services.service.summary.averageLoad': 'Average Load (regions per regionServer)',
-  'services.service.summary.masterHeap': 'Master Heap (used/max)',
-  'services.service.summary.moreStats': 'more stats here',
-
-  'hosts.host.start.popup.header': 'Confirmation',
-  'hosts.host.stop.popup.header': 'Confirmation',
-  'hosts.host.start.popup.body': 'Are you sure?',
-  'hosts.host.stop.popup.body': 'Are you sure?',
-  'hosts.assignedToRack.popup.body': 'Are you sure?',
-  'hosts.assignedToRack.popup.header': 'Confirmation',
-  'hosts.decommission.popup.body': 'Are you sure?',
-  'hosts.decommission.popup.header': 'Confirmation',
-  'hosts.delete.popup.body': 'Are you sure?',
-  'hosts.delete.popup.header': 'Confirmation',
-
-  'charts.horizon.chart.showText': 'show',
-  'charts.horizon.chart.hideText': 'hide',
-  'charts.horizon.chart.attributes.cpu': 'CPU',
-  'charts.horizon.chart.attributes.memory': 'Memory',
-  'charts.horizon.chart.attributes.network': 'Network',
-  'charts.horizon.chart.attributes.io': 'I/O',
-
-  'metric.default': 'default',
-  'metric.cpu': 'cpu',
-  'metric.memory': 'disk used',
-  'metric.network': 'network',
-  'metric.io': 'io',
-
-  'hosts.add.header' : 'Add Host Wizard',
-  'hosts.add.step2.warning' : 'Hosts are already part of the cluster and will be ignored',
-
-  'dashboard.services': 'Services',
-  'dashboard.services.hosts': 'Hosts',
-  'dashboard.services.uptime': '{0} days {1} hrs {2} mins',
-  'dashboard.services.hdfs.summary': '{0} of {1} nodes live, {2}% capacity free',
-  'dashboard.services.hdfs.capacity': 'HDFS Capacity',
-  'dashboard.services.hdfs.capacityUsed': '{0} of {1} ({2}% used)',
-  'dashboard.services.hdfs.totalFilesAndDirs': 'Total Files + Directories',
-  'dashboard.services.hdfs.nodes': 'Data Nodes',
+  'admin.audit.grid.date':"Date/Time",
+  'admin.audit.grid.category':"Category",
+  'admin.audit.grid.operationName':"Operation",
+  'admin.audit.grid.performedBy':"Performed By",
+  'admin.audit.grid.service':"Category",
+
+  'admin.authentication.form.method.database':'Use Ambari Database to authenticate users',
+  'admin.authentication.form.method.ldap':'Use LDAP/Active Directory to authenticate',
+  'admin.authentication.form.primaryServer':'Primary Server',
+  'admin.authentication.form.secondaryServer':'Secondary Server',
+  'admin.authentication.form.useSsl':'Use SSL',
+  'admin.authentication.form.bind.anonymously':"Bind Anonymously",
+  'admin.authentication.form.bind.useCrenedtials':"Use Credentials To Bind",
+  'admin.authentication.form.bindUserDN':'Bind User DN',
+  'admin.authentication.form.searchBaseDN':'Search Base DN',
+  'admin.authentication.form.usernameAttribute':'Username Attribute',
+
+  'admin.authentication.form.userDN':'User DN',
+  'admin.authentication.form.password':'Password',
+  'admin.authentication.form.configurationTest':'Configuration Test',
+  'admin.authentication.form.testConfiguration':'Test Configuration',
+
+  'admin.authentication.form.test.success':'The configuration passes the test',
+  'admin.authentication.form.test.fail':'The configuration fails the test',
+
+  'admin.security.title':'Kerberos Security has not been enabled on this cluster.',
+  'admin.security.button.enable':'Enable Kerberos Security on this cluster',
+
+  'admin.users.ldapAuthentionUsed':'LDAP Authentication is being used to authenticate users',
+  'admin.users.deleteYourselfMessage':'You can\'t delete yourself',
+  'admin.users.addButton':'Add User',
+  'admin.users.delete':'delete',
+  'admin.users.edit':'edit',
+  'admin.users.privileges':'Admin',
+  'admin.users.password':'Password',
+  'admin.users.passwordRetype':'Retype Password',
+  'admin.users.username':'Username',
+
+  'question.sure':'Are you sure?',
+
+  'services.service.start':'Start',
+  'services.service.stop':'Stop',
+  'services.service.start.popup.header':'Confirmation',
+  'services.service.stop.popup.header':'Confirmation',
+  'services.service.start.popup.body':'Are you sure?',
+  'services.service.stop.popup.body':'Are you sure?',
+  'services.service.summary.version':'Version',
+  'services.service.summary.nameNode':'NameNode Web UI',
+  'services.service.summary.nameNodeUptime':'NameNode Uptime',
+  'services.service.summary.nameNodeHeap':'NameNode Heap',
+  'services.service.summary.pendingUpgradeStatus':'Upgrade Status',
+  'services.service.summary.safeModeStatus':'Safe Mode Status',
+  'services.service.summary.dataNodes':'DataNodes',
+  'services.service.summary.diskCapacity':'HDFS Disk Capacity',
+  'services.service.summary.blocksTotal':'Blocks (total)',
+  'services.service.summary.blockErrors':'Block Errors',
+  'services.service.summary.totalFiles':'Total Files + Dirs',
+  'services.service.summary.jobTracker':'JobTracker Web UI',
+  'services.service.summary.jobTrackerUptime':'JobTracker Uptime',
+  'services.service.summary.trackersLiveTotal':'Trackers',
+  'services.service.summary.trackersBlacklistGraylist':'Trackers',
+  'services.service.summary.jobTrackerHeap':'JobTracker Heap',
+  'services.service.summary.totalSlotsCapacity':'Total Slots Capacity',
+  'services.service.summary.totalJobs':'Total Jobs',
+  'services.service.summary.currentSlotUtiliMaps':'Map Slots',
+  'services.service.summary.currentSlotUtiliReduces':'Reduce Slots',
+  'services.service.summary.tasksMaps':'Tasks: Maps',
+  'services.service.summary.tasksReduces':'Tasks: Reduces',
+  'services.service.summary.hbaseMaster':'HBase Master Web UI',
+  'services.service.summary.regionServerCount':'Region Server Count',
+  'services.service.summary.regionInTransition':'Region In Transition',
+  'services.service.summary.masterStarted':'Master Started',
+  'services.service.summary.masterActivated':'Master Activated',
+  'services.service.summary.averageLoad':'Average Load',
+  'services.service.summary.masterHeap':'Master Heap',
+  'services.service.summary.moreStats':'more stats here',
+
+  'hosts.host.start.popup.header':'Confirmation',
+  'hosts.host.stop.popup.header':'Confirmation',
+  'hosts.host.start.popup.body':'Are you sure?',
+  'hosts.host.stop.popup.body':'Are you sure?',
+  'hosts.assignedToRack.popup.body':'Are you sure?',
+  'hosts.assignedToRack.popup.header':'Confirmation',
+  'hosts.decommission.popup.body':'Are you sure?',
+  'hosts.decommission.popup.header':'Confirmation',
+  'hosts.delete.popup.body':'Are you sure?',
+  'hosts.delete.popup.header':'Confirmation',
+
+  'charts.horizon.chart.showText':'show',
+  'charts.horizon.chart.hideText':'hide',
+  'charts.horizon.chart.attributes.cpu':'CPU',
+  'charts.horizon.chart.attributes.memory':'Memory',
+  'charts.horizon.chart.attributes.network':'Network',
+  'charts.horizon.chart.attributes.io':'I/O',
+
+  'metric.default':'combined',
+  'metric.cpu':'cpu',
+  'metric.memory':'disk used',
+  'metric.network':'network',
+  'metric.io':'io',
+  'metric.more':'more',
+  'metric.more.cpu':'Cpu',
+
+  'metric.more.memory':'Memory',
+  'metric.more.memory.swapFree': 'swap_free',
+  'metric.more.memory.memCached': 'mem_cached',
+
+  'hosts.add.header':'Add Host Wizard',
+  'hosts.add.step2.warning':'Hosts are already part of the cluster and will be ignored',
+
+  'dashboard.services':'Services',
+  'dashboard.services.hosts':'Hosts',
+  'dashboard.services.uptime':'{0} days {1} hrs {2} mins',
+  'dashboard.services.hdfs.summary':'{0} of {1} nodes live, {2}% capacity free',
+  'dashboard.services.hdfs.capacity':'HDFS Capacity',
+  'dashboard.services.hdfs.capacityUsed':'{0} of {1} ({2}% used)',
+  'dashboard.services.hdfs.totalFilesAndDirs':'Total Files + Directories',
+  'dashboard.services.hdfs.nodes':'Data Nodes',
   'dashboard.services.hdfs.nodes.live':'live',
   'dashboard.services.hdfs.nodes.live':'live',
   'dashboard.services.hdfs.nodes.dead':'dead',
   'dashboard.services.hdfs.nodes.dead':'dead',
   'dashboard.services.hdfs.nodes.decom':'decom',
   'dashboard.services.hdfs.nodes.decom':'decom',
-  'dashboard.services.hdfs.nodes.uptime': 'NameNode Uptime',
-  'dashboard.services.hdfs.nodes.heap': 'NameNode Heap',
-  'dashboard.services.hdfs.nodes.heapUsed': '{0} of {1}, {2}% used',
-  'dashboard.services.hdfs.chart.label': 'Capacity (Free/Used)',
-
-  'dashboard.services.mapreduce.summary': '{0} of {1} trackers live, {2} jobs running',
-  'dashboard.services.mapreduce.trackers': 'Trackers',
-  'dashboard.services.mapreduce.trackersSummary': '{0} live / {1} total',
-  'dashboard.services.mapreduce.jobs': 'Jobs',
-  'dashboard.services.mapreduce.jobsSummary': '{0} running / {1} completed / {2} failed',
-  'dashboard.services.mapreduce.mapSlots': 'Map Slots',
-  'dashboard.services.mapreduce.reduceSlots': 'Reduce Slots',
-  'dashboard.services.mapreduce.jobTrackerHeap': 'JobTracker Heap',
-  'dashboard.services.mapreduce.jobTrackerUptime': 'Job Trackers Uptime',
-  'dashboard.services.mapreduce.chart.label': 'Jobs Running',
-
-  'dashboard.services.hbase.summary': '{0} of {1} region servers up, {2} average load',
-  'dashboard.services.hbase.masterServerHeap': 'Master Server Heap',
-  'dashboard.services.hbase.masterServerUptime': 'Master Server Uptime',
-  'dashboard.services.hbase.averageLoad': 'Average Load',
-  'dashboard.services.hbase.regionServers': 'Region Servers',
-  'dashboard.services.hbase.regionServersSummary': '{0} live / {1} total',
-  'dashboard.services.hbase.chart.label': 'Request Count'
+  'dashboard.services.hdfs.nodes.uptime':'NameNode Uptime',
+  'dashboard.services.hdfs.nodes.heap':'NameNode Heap',
+  'dashboard.services.hdfs.nodes.heapUsed':'{0} of {1}, {2}% used',
+  'dashboard.services.hdfs.chart.label':'Capacity (Free/Used)',
+
+  'dashboard.services.mapreduce.summary':'{0} of {1} trackers live, {2} jobs running',
+  'dashboard.services.mapreduce.trackers':'Trackers',
+  'dashboard.services.mapreduce.trackersSummary':'{0} live / {1} total',
+  'dashboard.services.mapreduce.jobs':'Jobs',
+  'dashboard.services.mapreduce.jobsSummary':'{0} running / {1} completed / {2} failed',
+  'dashboard.services.mapreduce.mapSlots':'Map Slots',
+  'dashboard.services.mapreduce.mapSlotsSummary':'{0} occuped / {1} reserved / {2} total',
+  'dashboard.services.mapreduce.reduceSlots':'Reduce Slots',
+  'dashboard.services.mapreduce.reduceSlotsSummary':'{0} occuped / {1} reserved / {2} total',
+  'dashboard.services.mapreduce.jobTrackerHeap':'JobTracker Heap',
+  'dashboard.services.mapreduce.jobTrackerHeapSummary':'{0} of {1} ({2}% used)',
+  'dashboard.services.mapreduce.jobTrackerUptime':'Job Trackers Uptime',
+  'dashboard.services.mapreduce.chart.label':'Jobs Running',
+
+  'dashboard.services.hbase.summary':'{0} of {1} region servers up, {2} average load',
+  'dashboard.services.hbase.masterServerHeap':'Master Server Heap',
+  'dashboard.services.hbase.masterServerHeap.summary':'{0} of {1} ({2}% used)',
+  'dashboard.services.hbase.masterServerUptime':'Master Server Uptime',
+  'dashboard.services.hbase.averageLoad':'Average Load',
+  'dashboard.services.hbase.regionServers':'Region Servers',
+  'dashboard.services.hbase.regionServersSummary':'{0} live / {1} total',
+  'dashboard.services.hbase.chart.label':'Request Count',
+
+  'timeRange.presets.1hour':'1h',
+  'timeRange.presets.12hour':'12h',
+  'timeRange.presets.1day':'1d',
+  'timeRange.presets.1week':'1wk',
+  'timeRange.presets.1month':'1mo',
+  'timeRange.presets.1year':'1yr'
 };
 };

+ 26 - 13
ambari-web/app/routes/add_host_routes.js

@@ -122,26 +122,39 @@ module.exports = Em.Route.extend({
       var controller = router.get('addHostController');
       var controller = router.get('addHostController');
       controller.setCurrentStep('4', false);
       controller.setCurrentStep('4', false);
       controller.loadAllPriorSteps();
       controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep5' /*, controller.get('content.services')*/);
+      controller.connectOutlet('wizardStep5', controller.get('content'));
 
 
     },
     },
     back: Em.Router.transitionTo('step3'),
     back: Em.Router.transitionTo('step3'),
     next: function (router, context) {
     next: function (router, context) {
-      console.warn('next is not ready now');
+      var addHostController = router.get('addHostController');
+      var wizardStep5Controller = router.get('wizardStep5Controller');
+      addHostController.saveMasterComponentHosts( wizardStep5Controller );
+      router.transitionTo('step5');
+    }
+  }),
+
+  step5: Em.Route.extend({
+    route: '/step5',
+    connectOutlets: function (router, context) {
+      console.log('in addHost.step5:connectOutlets');
+      var controller = router.get('addHostController');
+      controller.setCurrentStep('5', false);
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep6', controller.get('content'));
+    },
+    back: Em.Router.transitionTo('step4'),
+    next: function(router){
+      var addHostController = router.get('addHostController');
+      var wizardStep6Controller = router.get('wizardStep6Controller');
+
+      if(wizardStep6Controller.validate()){
+        addHostController.saveSlaveComponentHosts(wizardStep6Controller);
+        router.transitionTo('step7');
+      }
     }
     }
   }),
   }),
 
 
-//  step5: Em.Route.extend({
-//    route: '/step5',
-//    connectOutlets: function (router, context) {
-//      router.setNavigationFlow('step5');
-//      router.setInstallerCurrentStep('5', false);
-//      router.get('installerController').connectOutlet('installerStep5');
-//    },
-//    back: Em.Router.transitionTo('step4'),
-//    next: Em.Router.transitionTo('step6')
-//  }),
-//
 //  step6: Em.Route.extend({
 //  step6: Em.Route.extend({
 //    route: '/step6',
 //    route: '/step6',
 //    connectOutlets: function (router, context) {
 //    connectOutlets: function (router, context) {

+ 2 - 1
ambari-web/app/styles/app.css

@@ -163,7 +163,8 @@
 }
 }
 /*End Carousel*//*End Hosts*/
 /*End Carousel*//*End Hosts*/
 
 
-#add-host .back{
+#add-host .back,
+#add-service .back{
     display: block;
     display: block;
     width: 105px;
     width: 105px;
     margin-bottom: 10px;
     margin-bottom: 10px;

+ 117 - 23
ambari-web/app/styles/application.less

@@ -167,13 +167,13 @@ h1 {
 /***************
 /***************
  * Ambari wide icon colors
  * Ambari wide icon colors
  ***************/
  ***************/
-.icon-ok{
-    color: #5AB400;
+.icon-ok {
+  color: #5AB400;
 }
 }
-.icon-remove{
-    color: #FF4B4B;
+
+.icon-remove {
+  color: #FF4B4B;
 }
 }
- 
 
 
 #installer, #add-host {
 #installer, #add-host {
   h2 {
   h2 {
@@ -242,12 +242,12 @@ h1 {
     }
     }
     .slave-component-group-menu {
     .slave-component-group-menu {
       float: left;
       float: left;
-      .nav{
+      .nav {
         margin-bottom: 10px;
         margin-bottom: 10px;
       }
       }
-      .nav li{
+      .nav li {
         position: relative;
         position: relative;
-        a{
+        a {
           padding-right: 24px;
           padding-right: 24px;
         }
         }
         i {
         i {
@@ -256,13 +256,14 @@ h1 {
           right: 7px;
           right: 7px;
           top: 10px;
           top: 10px;
           z-index: 2;
           z-index: 2;
+          cursor: default;
         }
         }
         i:hover {
         i:hover {
           border: 1px solid grey;
           border: 1px solid grey;
         }
         }
       }
       }
     }
     }
-    .remove-group-error{
+    .remove-group-error {
       display: none;
       display: none;
     }
     }
     .add-slave-component-group {
     .add-slave-component-group {
@@ -404,11 +405,11 @@ a:focus {
   }
   }
 
 
   .service {
   .service {
-    position:relative;
+    position: relative;
     margin-top: 10px;
     margin-top: 10px;
     border-bottom: 1px solid #b8b8b8;
     border-bottom: 1px solid #b8b8b8;
     padding-left: 10px;
     padding-left: 10px;
-    width: 540px;
+    margin-right: 20px;
 
 
     .name {
     .name {
       line-height: 21px;
       line-height: 21px;
@@ -426,15 +427,19 @@ a:focus {
       margin-top: 14px;
       margin-top: 14px;
       color: #7b7b7b;
       color: #7b7b7b;
       font-size: 13px;
       font-size: 13px;
-      width: 410px;
+      width: 80%;
+      tr > td:first-child {
+        padding-right: 10px;
+        text-align: right !important;
+      }
       th, td {
       th, td {
-        line-height: 8px !important;
+        padding: 4px;
       }
       }
     }
     }
 
 
     .chart {
     .chart {
       right: 0;
       right: 0;
-      top: 5px;
+      top: 27px;
       position: absolute;
       position: absolute;
       overflow: visible; // for quick links
       overflow: visible; // for quick links
       text-align: center;
       text-align: center;
@@ -446,14 +451,19 @@ a:focus {
     }
     }
   }
   }
 }
 }
+
 #summary-info {
 #summary-info {
-  margin: 10px 0;
-/*
+  border-top: none;
+  border-collapse: collapse;
+
+  td.summary-label {
+    text-align: right;
+  }
+
   tr td:first-child {
   tr td:first-child {
-    font-weight: bold;
-    background: #d0d0d0;
+    text-align: right;
   }
   }
-*/
+
   a {
   a {
     text-decoration: underline;
     text-decoration: underline;
     &:hover {
     &:hover {
@@ -461,6 +471,7 @@ a:focus {
     }
     }
   }
   }
 }
 }
+
 .more-stats {
 .more-stats {
   display: block;
   display: block;
   width: 100%;
   width: 100%;
@@ -478,6 +489,7 @@ a:focus {
 
 
 /*start alerts summary*/
 /*start alerts summary*/
 .alerts {
 .alerts {
+  border: 1px solid #ddd;
   margin: 0px;
   margin: 0px;
   max-height: 500px;
   max-height: 500px;
   overflow-y: auto;
   overflow-y: auto;
@@ -518,7 +530,7 @@ a:focus {
       padding-left: 7px;
       padding-left: 7px;
     }
     }
   }
   }
-  
+
 }
 }
 
 
 .go-to {
 .go-to {
@@ -565,6 +577,12 @@ a:focus {
   margin-top: -48px;
   margin-top: -48px;
 }
 }
 
 
+.service-content {
+  #summary-info {
+    margin-bottom: 0;
+  }
+}
+
 /*End Services*/
 /*End Services*/
 
 
 /*Hosts*/
 /*Hosts*/
@@ -594,7 +612,7 @@ a:focus {
     margin-bottom: 5px;
     margin-bottom: 5px;
   }
   }
   .box-header .host-title {
   .box-header .host-title {
-    margin:0;
+    margin: 0;
     padding-left: 17px;
     padding-left: 17px;
   }
   }
   .box-header .button-section {
   .box-header .button-section {
@@ -623,7 +641,7 @@ a:focus {
     background: #F5F5F5;
     background: #F5F5F5;
   }
   }
   .host-components .btn-group {
   .host-components .btn-group {
-    margin:0 5px 10px 0;
+    margin: 0 5px 10px 0;
   }
   }
 }
 }
 
 
@@ -948,7 +966,7 @@ ul.filter {
   display: block !important;
   display: block !important;
 }
 }
 
 
-.displayInline {
+.display-inline-block {
   display: inline-block !important;
   display: inline-block !important;
 }
 }
 
 
@@ -1074,4 +1092,80 @@ ul.inline li {
 .table.no-borders th, .table.no-borders td {
 .table.no-borders th, .table.no-borders td {
   border-top: none;
   border-top: none;
 }
 }
+
 /* UNIVERSAL STYLES END */
 /* UNIVERSAL STYLES END */
+
+/* METRIC FILTERING WIDGET */
+.metric-filtering-widget {
+
+  .title {
+    padding-top: 4px;
+  }
+
+  .accordion {
+    background: none repeat scroll 0 0 #FFFFFF;
+    border: 1px solid;
+    font-size: 12px;
+    padding: 5px 0;
+    position: absolute;
+
+    .accordion-group {
+      border: none;
+
+      .accordion-heading {
+        .accordion-toggle {
+          padding: 0 5px;
+        }
+        i {
+          text-decoration: none;
+        }
+      }
+
+      .accordion-body {
+        .accordion-inner {
+          border: none;
+          padding: 0 8px;
+          width: 160px;
+
+          ul.items {
+            border: 1px solid #000;
+            list-style: none;
+            border-bottom: none;
+
+            li {
+              border-bottom: 1px solid;
+              line-height: 12px;
+              padding: 2px 5px;
+              padding: 2px 5px;
+            }
+
+            li.disabled {
+              a {
+                color: #999999;
+              }
+            }
+
+          }
+        }
+      }
+    }
+  }
+}
+
+/* METRIC FILTERING WIDGET END */
+
+/* TIME RANGE WIDGET */
+
+/* css for timepicker */
+.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+.ui-timepicker-div dl { text-align: left; }
+.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
+.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
+.ui-timepicker-div td { font-size: 90%; }
+.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
+
+.ui-timepicker-rtl{ direction: rtl; }
+.ui-timepicker-rtl dl { text-align: right; }
+.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; }
+
+/* TIME RANGE WIDGET END */

+ 40 - 3
ambari-web/app/templates/common/metric.hbs

@@ -15,9 +15,46 @@
 * See the License for the specific language governing permissions and
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * limitations under the License.
 -->
 -->
-Metrics <i class="icon-question-sign"></i>
-<ul class="displayInline nav nav-pills">
+<div class="span title">
+  Metrics <i class="icon-question-sign"></i>
+</div>
+<ul class="display-inline-block nav nav-pills">
   {{#each metric in view.metrics}}
   {{#each metric in view.metrics}}
-    {{view view.itemView metricBinding="metric"}}
+  {{view view.itemView metricBinding="metric" widgetBinding="view"}}
   {{/each}}
   {{/each}}
+
+  <li class="dropdown">
+    <a {{action toggleMore target="view"}} href="#">
+      {{t metric.more}}
+      <b class="caret"></b>
+    </a>
+
+    {{#if view.showMore}}
+    <div class="accordion" id="metricAccordion">
+      {{#each view.moreMetrics}}
+      <div class="accordion-group">
+        <div class="accordion-heading">
+          <a class="accordion-toggle" data-toggle="collapse" data-parent="#metricAccordion"
+             href="#{{unbound code}}Collapse">
+            <i class="icon-play"></i>{{unbound label}}
+          </a>
+        </div>
+        <div id="{{unbound code}}Collapse" class="accordion-body collapse">
+          <div class="accordion-inner">
+            {{#if items.length }}
+            <ul class="items">
+              {{#each metric in items}}
+              {{view view.moreItemView metricBinding="metric" widgetBinding="view"}}
+              {{/each}}
+            </ul>
+            {{else}}
+            no items found
+            {{/if}}
+          </div>
+        </div>
+      </div>
+      {{/each}}
+    </div>
+    {{/if}}
+  </li>
 </ul>
 </ul>

+ 27 - 0
ambari-web/app/templates/common/time_range.hbs

@@ -0,0 +1,27 @@
+<!--
+* 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.
+-->
+<div class="span title">
+  Time Range <i class="icon-question-sign"></i>
+</div>
+<ul class="display-inline-block nav nav-pills">
+  {{#each preset in view.presets}}
+    {{view view.presetView presetBinding="preset" widgetBinding="view"}}
+  {{/each}}
+  From {{view view.dateFromView}}
+  To {{view view.dateToView}}
+</ul>

+ 15 - 0
ambari-web/app/templates/installer/slave_component_hosts.hbs

@@ -0,0 +1,15 @@
+{{#if view.hasNoHosts}}
+  none -
+  <a href="#" {{action showEditSlaveComponentGroups view.serviceConfig.category target="App.router.slaveComponentGroupsController"}}>
+    select hosts for this group
+  </a>
+{{else}}
+<a href="#" {{action showEditSlaveComponentGroups view.serviceConfig.category target="App.router.slaveComponentGroupsController"}}>
+  {{#if view.hasOneHost}}
+  {{view.hosts.firstObject.hostname}}
+  {{/if}}
+  {{#if view.hasMultipleHosts}}
+  {{view.hosts.firstObject.hostname}} and {{view.otherLength}}
+  {{/if}}
+</a>
+{{/if}}

+ 31 - 0
ambari-web/app/templates/installer/slave_component_hosts_popup.hbs

@@ -0,0 +1,31 @@
+<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 App.SlaveComponentDropDownGroupView contentBinding="host"}}
+        <select>
+          {{#each groupName in controller.getGroupsForDropDown}}
+            {{#view view.optionTag contentBinding="groupName"}}
+              <option value="{{unbound groupName}}" {{bindAttr selected="view.selected"}} {{action changeGroup target="view"}}>
+                {{unbound groupName}}
+              </option>
+            {{/view}}
+          {{/each}}
+        </select>
+        {{/view}}
+      </td>
+    </tr>
+  {{/each}}
+  </tbody>
+</table>

+ 7 - 18
ambari-web/app/templates/installer/step7.hbs

@@ -57,24 +57,13 @@
           {{/if}}
           {{/if}}
           <form class="form-horizontal">
           <form class="form-horizontal">
             {{#if category.isForSlaveComponent}}
             {{#if category.isForSlaveComponent}}
-              {{#each group in App.router.slaveComponentGroupsController.componentGroups}}
-                {{#if group.active}}
-                  {{#view App.SlaveComponentGroupView contentBinding="group" }}
-                    <div class="control-group">
-                      <label class="control-label">Group name</label>
-                      <div class="controls">
-                        <input class="span6" type="text" value="{{unbound group.name}}">
-                        <span class="help-inline"></span>
-                      </div>
-                    </div>
-                    <!--<div class="control-group">-->
-                      <!--<label class="control-label">DataNode hosts</label>-->
-                      <!--<div class="controls">-->
-                      <!--</div>-->
-                    <!--</div>-->
-                  {{/view}}
-                {{/if}}
-              {{/each}}
+            <div class="control-group">
+              <label class="control-label">Group name</label>
+              <div class="controls">
+                <input class="span6" type="text" {{bindAttr value="App.router.slaveComponentGroupsController.activeGroup.name"}}>
+                <span class="help-inline"></span>
+              </div>
+            </div>
             {{/if}}
             {{/if}}
             {{#each view.categoryConfigs}}
             {{#each view.categoryConfigs}}
             <div {{bindAttr class="errorMessage:error: :control-group"}}>
             <div {{bindAttr class="errorMessage:error: :control-group"}}>

+ 2 - 1
ambari-web/app/templates/main/charts.hbs

@@ -17,5 +17,6 @@
 -->
 -->
 
 
 {{view App.MetricFilteringWidget controllerBinding="App.router.mainChartsController"}}
 {{view App.MetricFilteringWidget controllerBinding="App.router.mainChartsController"}}
-  {{view App.MainChartsMenuView}}
+{{view App.TimeRangeWidget controllerBinding="App.router.mainChartsController"}}
+{{view App.MainChartsMenuView}}
 {{outlet}}
 {{outlet}}

+ 6 - 6
ambari-web/app/templates/main/dashboard.hbs

@@ -25,12 +25,12 @@
             <h4>{{t dashboard.services}}</h4>
             <h4>{{t dashboard.services}}</h4>
           </div>
           </div>
           <dl class="dl-horizontal services">
           <dl class="dl-horizontal services">
-            {{view App.MainDashboardServiceHdfsView servicesBinding="controller.services"}}
-            {{view App.MainDashboardServiceMapreduceView servicesBinding="controller.services"}}
-            {{view App.MainDashboardServiceHbaseView servicesBinding="controller.services"}}
-            {{view App.MainDashboardServiceHiveView servicesBinding="controller.services"}}
-            {{view App.MainDashboardServiceZookeperView servicesBinding="controller.services"}}
-            {{view App.MainDashboardServiceOozieView servicesBinding="controller.services"}}
+            {{view App.MainDashboardServiceHdfsView controllerBinding="controller"}}
+            {{view App.MainDashboardServiceMapreduceView controllerBinding="controller"}}
+            {{view App.MainDashboardServiceHbaseView controllerBinding="controller"}}
+            {{view App.MainDashboardServiceHiveView controllerBinding="controller"}}
+            {{view App.MainDashboardServiceZookeperView controllerBinding="controller"}}
+            {{view App.MainDashboardServiceOozieView controllerBinding="controller"}}
           </dl>
           </dl>
         </div>
         </div>
       </div>
       </div>

+ 2 - 2
ambari-web/app/templates/main/dashboard/service/hbase.hbs

@@ -34,13 +34,13 @@
   <!-- Master Server Heap -->
   <!-- Master Server Heap -->
   <tr>
   <tr>
     <td>{{t dashboard.services.hbase.masterServerHeap}}</td>
     <td>{{t dashboard.services.hbase.masterServerHeap}}</td>
-    <td>?</td>
+    <td>{{view.masterServerHeapSummary}}</td>
   </tr>
   </tr>
 
 
   <!-- Average load -->
   <!-- Average load -->
   <tr>
   <tr>
     <td>{{t dashboard.services.hbase.averageLoad}}</td>
     <td>{{t dashboard.services.hbase.averageLoad}}</td>
-    <td>?</td>
+    <td>{{view.data.average_load}}</td>
   </tr>
   </tr>
 
 
   <!-- Region Servers -->
   <!-- Region Servers -->

+ 1 - 1
ambari-web/app/templates/main/dashboard/service/hdfs.hbs

@@ -50,7 +50,7 @@
   <!-- Total Files And Directories -->
   <!-- Total Files And Directories -->
   <tr>
   <tr>
     <td>{{t dashboard.services.hdfs.totalFilesAndDirs}}</td>
     <td>{{t dashboard.services.hdfs.totalFilesAndDirs}}</td>
-    <td>?</td>
+    <td>{{view.data.total_files_and_dirs}}</td>
   </tr>
   </tr>
 
 
   <!-- NameNode Uptime -->
   <!-- NameNode Uptime -->

+ 3 - 3
ambari-web/app/templates/main/dashboard/service/mapreduce.hbs

@@ -45,13 +45,13 @@
   <!-- Map Slots -->
   <!-- Map Slots -->
   <tr>
   <tr>
     <td>{{t dashboard.services.mapreduce.mapSlots}}</td>
     <td>{{t dashboard.services.mapreduce.mapSlots}}</td>
-    <td>?</td>
+    <td>{{view.mapSlotsSummary}}</td>
   </tr>
   </tr>
 
 
   <!-- Reduce Slots -->
   <!-- Reduce Slots -->
   <tr>
   <tr>
     <td>{{t dashboard.services.mapreduce.reduceSlots}}</td>
     <td>{{t dashboard.services.mapreduce.reduceSlots}}</td>
-    <td>?</td>
+    <td>{{view.reduceSlotsSummary}}</td>
   </tr>
   </tr>
 
 
   <!-- Job Tracker Uptime -->
   <!-- Job Tracker Uptime -->
@@ -63,7 +63,7 @@
   <!-- JobTracker Heap -->
   <!-- JobTracker Heap -->
   <tr>
   <tr>
     <td>{{t dashboard.services.mapreduce.jobTrackerHeap}}</td>
     <td>{{t dashboard.services.mapreduce.jobTrackerHeap}}</td>
-    <td>?</td>
+    <td>{{view.trackersHeapSummary}}</td>
   </tr>
   </tr>
 
 
   <!-- Hosts -->
   <!-- Hosts -->

+ 177 - 166
ambari-web/app/templates/main/service/info/summary.hbs

@@ -27,173 +27,184 @@
         </ul>
         </ul>
       </li>
       </li>
     </ul>
     </ul>
-		<h4>{{controller.content.label}} Summary</h4>
-		<div class="service-content">
-      <table id="summary-info" class="table table-bordered table-striped table-condensed">
-        <tbody>
-        {{#each component in controller.content.components}}
-          <tr>
-          {{#if component.type}}
-            <td>{{component.componentName}}</td><td><a {{action selectHost component.host}} href="javascript:void(null)">{{component.host.hostName}}</a></td>
-          {{else}}
-            <td>{{component.componentName}}s</td><td><a {{action filterHosts component}} href="javascript:void(null)">{{component.componentName}}s</a></td>
+
+    <div class="box">
+      <div class="box-header">
+        <h4>{{controller.content.label}} Summary</h4>
+      </div>
+      <div class="service-content">
+        <table id="summary-info" class="table table-bordered table-striped table-condensed">
+          <tbody>
+          {{#each component in controller.content.components}}
+            <tr>
+            {{#if component.type}}
+              <td class="summary-label">{{component.componentName}}</td>
+              <td><a {{action selectHost component.host}} href="javascript:void(null)">{{component.host.hostName}}</a></td>
+            {{else}}
+              <td class="summary-label">{{component.componentName}}s</td>
+              <td><a {{action filterHosts component}} href="javascript:void(null)">{{component.componentName}}s</a></td>
+            {{/if}}
+            </tr>
+          {{/each}}
+          {{#if view.serviceStatus.hdfs}}
+            <tr>
+              <td>{{t services.service.summary.version}}</td>
+              <td>{{view.attributes.version}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.nameNode}}</td>
+              <td><a {{bindAttr href="view.attributes.namenode_addr"}}>{{view.attributes.namenode_addr}}</a></td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.nameNodeUptime}}</td>
+              <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.nameNodeHeap}}</td>
+              <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.dataNodes}}</td>
+              <td>
+                <a href="javascript:void(null)">{{view.attributes.live_nodes}} live</a> / <a href="javascript:void(null)">{{view.attributes.dead_nodes}} dead</a> / <a href="javascript:void(null)">{{view.attributes.decommissioning_nodes}} decom</a>
+              </td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.diskCapacity}}</td>
+              <td>{{view.attributes.used_bytes}} / {{view.attributes.dfs_total_bytes}} ({{view.attributes.dfs_percent_disk_used}} used)</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.blocksTotal}}</td>
+              <td>{{view.attributes.dfs_blocks_total}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.blockErrors}}</td>
+              <td>{{view.attributes.dfs_blocks_corrupt}} corr / {{view.attributes.dfs_blocks_missing}} miss / {{view.attributes.dfs_blocks_underreplicated}} underrep</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.totalFiles}}</td>
+              <td>{{view.attributes.dfs_dirfiles_count}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.pendingUpgradeStatus}}</td>
+              <td>
+                {{#if view.attributes.pending_upgrades}}
+                Pending upgrade
+                {{else}}
+                No pending upgrade
+                {{/if}}
+              </td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.safeModeStatus}}</td>
+              <td>
+                {{#if view.attributes.safemode}}
+                In safe mode
+                {{else}}
+                Not in safe mode
+                {{/if}}
+              </td>
+            </tr>
           {{/if}}
           {{/if}}
-          </tr>
-        {{/each}}
-        {{#if view.serviceStatus.hdfs}}
-          <tr>
-            <td>{{t services.service.summary.version}}</td>
-            <td>{{view.attributes.version}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.nameNode}}</td>
-            <td><a {{bindAttr href="view.attributes.namenode_addr"}}>{{view.attributes.namenode_addr}}</a></td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.nameNodeUptime}}</td>
-            <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.nameNodeHeap}}</td>
-            <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.dataNodes}}</td>
-            <td>
-              <a href="javascript:void(null)">{{view.attributes.live_nodes}}</a> / <a href="javascript:void(null)">{{view.attributes.dead_nodes}}</a> / <a href="javascript:void(null)">{{view.attributes.decommissioning_nodes}}</a>
-            </td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.diskCapacity}}</td>
-            <td>{{view.attributes.used_bytes}} / {{view.attributes.dfs_total_bytes}} ({{view.attributes.dfs_percent_disk_used}} used)</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.blocksTotal}}</td>
-            <td>{{view.attributes.dfs_blocks_total}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.blockErrors}}</td>
-            <td>{{view.attributes.dfs_blocks_corrupt}} / {{view.attributes.dfs_blocks_missing}} / ({{view.attributes.dfs_blocks_underreplicated}})</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.totalFiles}}</td>
-            <td>{{view.attributes.dfs_dirfiles_count}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.pendingUpgradeStatus}}</td>
-            <td>
-              {{#if view.attributes.pending_upgrades}}
-              Pending upgrade
-              {{else}}
-              No pending upgrade
-              {{/if}}
-            </td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.safeModeStatus}}</td>
-            <td>
-              {{#if view.attributes.safemode}}
-              In safe mode
-              {{else}}
-              Not in safe mode
-              {{/if}}
-            </td>
-          </tr>
-        {{/if}}
-        {{#if view.serviceStatus.mapreduce}}
-          <tr>
-            <td>{{t services.service.summary.version}}</td>
-            <td>{{view.attributes.version}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.jobTracker}}</td>
-            <td><a {{bindAttr href="view.attributes.jobtracker_addr"}}>{{view.attributes.jobtracker_addr}}</a></td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.jobTrackerUptime}}</td>
-            <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.trackersLiveTotal}}</td>
-            <td>
-              <a href="javascript:void(null)">{{view.attributes.trackers_live}}</a> / {{view.attributes.trackers_total}}
-            </td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.trackersBlacklistGraylist}}</td>
-            <td>
-              <a href="javascript:void(null)">{{view.attributes.trackers_blacklisted}}</a> / <a href="javascript:void(null)">{{view.attributes.trackers_graylisted}}</a> / <a href="javascript:void(null)">{{view.attributes.trackers_excluded}}</a>
-            </td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.jobTrackerHeap}}</td>
-            <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.totalSlotsCapacity}}</td>
-            <td>{{view.attributes.map_task_capacity}} / {{view.attributes.reduce_task_capacity}} / {{view.attributes.average_node_capacity}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.totalJobs}}</td>
-            <td>{{view.attributes.job_total_submissions}} / {{view.attributes.job_total_completions}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.currentSlotUtiliMaps}}</td>
-            <td>{{view.attributes.occupied_map_slots}} / {{view.attributes.reserved_map_slots}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.currentSlotUtiliReduces}}</td>
-            <td>{{view.attributes.occupied_reduce_slots}} / {{view.attributes.reserved_reduce_slots}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.tasksMaps}}</td>
-            <td>{{view.attributes.running_map_tasks}} / {{view.attributes.waiting_maps}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.tasksReduces}}</td>
-            <td>{{view.attributes.running_reduce_tasks}} / {{view.attributes.waiting_reduces}}</td>
-          </tr>
-        {{/if}}
-        {{#if view.serviceStatus.hbase}}
-          <tr>
-            <td>{{t services.service.summary.version}}</td>
-            <td>{{view.attributes.version}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.hbaseMaster}}</td>
-            <td><a {{bindAttr href="view.attributes.hbasemaster_addr"}}>{{view.attributes.hbasemaster_addr}}</a></td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.regionServerCount}}</td>
-            <td>
-              <a href="javascript:void(null)">{{view.attributes.live_regionservers}}</a> / <a href="javascript:void(null)">{{view.attributes.dead_regionservers}}</a>
-            </td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.regionInTransition}}</td>
-            <td>{{view.attributes.regions_in_transition_count}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.masterStarted}}</td>
-            <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.masterActivated}}</td>
-            <td>{{view.attributes.active_time.d}}day {{view.attributes.active_time.h}}hr {{view.attributes.active_time.m}}min</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.averageLoad}}</td>
-            <td>{{view.attributes.average_load}}</td>
-          </tr>
-          <tr>
-            <td>{{t services.service.summary.masterHeap}}</td>
-            <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
-          </tr>
-        {{/if}}
-        </tbody>
-      </table>
-      {{!view view.moreStatsView}}
-		</div>
+          {{#if view.serviceStatus.mapreduce}}
+            <tr>
+              <td>{{t services.service.summary.version}}</td>
+              <td>{{view.attributes.version}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.jobTracker}}</td>
+              <td><a {{bindAttr href="view.attributes.jobtracker_addr"}}>{{view.attributes.jobtracker_addr}}</a></td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.jobTrackerUptime}}</td>
+              <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.trackersLiveTotal}}</td>
+              <td>
+                <a href="javascript:void(null)">{{view.attributes.trackers_live}} live</a> / {{view.attributes.trackers_total}} total
+              </td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.trackersBlacklistGraylist}}</td>
+              <td>
+                <a href="javascript:void(null)">{{view.attributes.trackers_blacklisted}} blacklist</a> / <a href="javascript:void(null)">{{view.attributes.trackers_graylisted}} graylist</a> / <a href="javascript:void(null)">{{view.attributes.trackers_excluded}} excl.</a>
+              </td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.jobTrackerHeap}}</td>
+              <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.totalSlotsCapacity}}</td>
+              <td>{{view.attributes.map_task_capacity}} maps / {{view.attributes.reduce_task_capacity}} reduces / {{view.attributes.average_node_capacity}} avg per node</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.totalJobs}}</td>
+              <td>{{view.attributes.job_total_submissions}} submitted / {{view.attributes.job_total_completions}} completed</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.currentSlotUtiliMaps}}</td>
+              <td>{{view.attributes.occupied_map_slots}} occupied / {{view.attributes.reserved_map_slots}} reserved</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.currentSlotUtiliReduces}}</td>
+              <td>{{view.attributes.occupied_reduce_slots}} occupied / {{view.attributes.reserved_reduce_slots}} reserved</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.tasksMaps}}</td>
+              <td>{{view.attributes.running_map_tasks}} running / {{view.attributes.waiting_maps}} waiting</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.tasksReduces}}</td>
+              <td>{{view.attributes.running_reduce_tasks}} running / {{view.attributes.waiting_reduces}} waiting</td>
+            </tr>
+          {{/if}}
+          {{#if view.serviceStatus.hbase}}
+            <tr>
+              <td>{{t services.service.summary.version}}</td>
+              <td>{{view.attributes.version}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.hbaseMaster}}</td>
+              <td><a {{bindAttr href="view.attributes.hbasemaster_addr"}}>{{view.attributes.hbasemaster_addr}}</a></td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.regionServerCount}}</td>
+              <td>
+                <a href="javascript:void(null)">{{view.attributes.live_regionservers}} live</a> / <a href="javascript:void(null)">{{view.attributes.dead_regionservers}} dead</a>
+              </td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.regionInTransition}}</td>
+              <td>{{view.attributes.regions_in_transition_count}}</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.masterStarted}}</td>
+              <td>{{view.attributes.start_time.d}}day {{view.attributes.start_time.h}}hr {{view.attributes.start_time.m}}min</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.masterActivated}}</td>
+              <td>{{view.attributes.active_time.d}}day {{view.attributes.active_time.h}}hr {{view.attributes.active_time.m}}min</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.averageLoad}}</td>
+              <td>{{view.attributes.average_load}} regions per RegionServer</td>
+            </tr>
+            <tr>
+              <td>{{t services.service.summary.masterHeap}}</td>
+              <td>{{view.attributes.memory_heap_used}} / {{view.attributes.memory_heap_max}} ({{view.attributes.memory_heap_percent_used}} used)</td>
+            </tr>
+          {{/if}}
+          </tbody>
+        </table>
+        {{!view view.moreStatsView}}
+      </div>
+      {{!
+      <div class="box-footer">
+      </div>
+      }}
+    </div>
 	</div>
 	</div>
 	<div class="span6">
 	<div class="span6">
 		<div class="box">
 		<div class="box">

+ 3 - 3
ambari-web/app/templates/wizard/step5.hbs

@@ -71,6 +71,6 @@
   <div style="clear: both;"></div>
   <div style="clear: both;"></div>
 </div>
 </div>
 <div class="btn-area">
 <div class="btn-area">
-  <a class="btn pull-left" {{action back}}>&larr; Back</a>
-  <a class="btn btn-success pull-right" {{action submit target="controller"}}>Next &rarr;</a>
-</div>
+  <a class="btn pull-left" {{action back href="true"}}>&larr; Back</a>
+  <a class="btn btn-success pull-right" {{action next}}>Next &rarr;</a>
+</div>

+ 77 - 0
ambari-web/app/templates/wizard/step6.hbs

@@ -0,0 +1,77 @@
+<!--
+* 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.
+-->
+
+<div id="step6">
+  <h2>{{t installer.step6.header}}</h2>
+
+  <div class="alert alert-info">{{t installer.step6.body}}</div>
+  {{#if errorMessage}}
+  <div class="alert alert-error">{{errorMessage}}</div>
+  {{/if}}
+  <table class="table table-striped">
+    <thead>
+      <tr>
+        <th>Host</th>
+        <th>
+          <a href="#" {{bindAttr class="isAllDataNodes:selected:deselected"}}
+            {{action selectAllDataNodes target="controller"}}>all</a> |
+
+          <a href="#" {{bindAttr class="isNoDataNodes:selected:deselected"}}
+            {{action deselectAllDataNodes target="controller"}}>none</a>
+        </th>
+      {{#if controller.isMrSelected}}
+        <th>
+          <a href="#" {{bindAttr class="isAllTaskTrackers:selected:deselected"}}
+            {{action selectAllTaskTrackers target="controller"}}>all</a> |
+
+          <a href="#" {{bindAttr class="isNoTaskTrackers:selected:deselected"}}
+            {{action deselectAllTaskTrackers target="controller"}}>none</a>
+        </th>
+      {{/if}}
+      {{#if controller.isHbSelected}}
+        <th>
+          <a href="#" {{bindAttr class="isAllRegionServers:selected:deselected"}}
+            {{action selectAllRegionServers target="controller"}}>all</a> |
+
+          <a href="#" {{bindAttr class="isNoRegionServers:selected:deselected"}}
+            {{action deselectAllRegionServers target="controller"}}>none</a>
+        </th>
+      {{/if}}
+      </tr>
+    </thead>
+    <tbody>
+
+    {{#each hosts}}
+    <tr>
+      <td>{{hostname}}</td>
+      <td><label class="checkbox">{{view Ember.Checkbox checkedBinding="isDataNode"}}DataNode</label></td>
+      {{#if controller.isMrSelected}}
+      <td><label class="checkbox">{{view Ember.Checkbox checkedBinding="isTaskTracker"}}TaskTracker</label></td>
+      {{/if}}
+      {{#if controller.isHbSelected}}
+      <td><label class="checkbox">{{view Ember.Checkbox checkedBinding="isRegionServer"}}RegionServer</label></td>
+      {{/if}}
+    </tr>
+    {{/each}}
+    </tbody>
+  </table>
+  <div class="btn-area">
+    <a class="btn" {{action back href="true"}}>&larr; Back</a>
+    <a class="btn btn-success pull-right" {{action next}}>Next &rarr;</a>
+  </div>
+</div>

+ 1 - 1
ambari-web/app/utils/db.js

@@ -398,7 +398,7 @@ App.db.getBootStatus = function() {
   return App.db.data[user].Installer.bootStatus;
   return App.db.data[user].Installer.bootStatus;
 }
 }
 
 
-App.db.getService = function(serviceInfo) {
+App.db.getService = function() {
   console.log('TRACE: Entering db:getService function');
   console.log('TRACE: Entering db:getService 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;

+ 17 - 23
ambari-web/app/utils/helper.js

@@ -15,28 +15,27 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-String.prototype.capitalize = function()
-{
+String.prototype.capitalize = function () {
   return this.charAt(0).toUpperCase() + this.slice(1);
   return this.charAt(0).toUpperCase() + this.slice(1);
 }
 }
 
 
 Em.CoreObject.reopen({
 Em.CoreObject.reopen({
-  t: function(key, attrs){
+  t:function (key, attrs) {
     return Em.I18n.t(key, attrs)
     return Em.I18n.t(key, attrs)
   }
   }
 });
 });
 
 
-Handlebars.registerHelper('log', function(variable) {
+Handlebars.registerHelper('log', function (variable) {
   console.log(variable);
   console.log(variable);
 });
 });
 
 
-Handlebars.registerHelper('warn', function(variable) {
+Handlebars.registerHelper('warn', function (variable) {
   console.warn(variable);
   console.warn(variable);
 });
 });
 
 
-String.prototype.format = function() {
+String.prototype.format = function () {
   var args = arguments;
   var args = arguments;
-  return this.replace(/{(\d+)}/g, function(match, number) {
+  return this.replace(/{(\d+)}/g, function (match, number) {
     return typeof args[number] != 'undefined' ? args[number] : match;
     return typeof args[number] != 'undefined' ? args[number] : match;
   });
   });
 };
 };
@@ -48,48 +47,43 @@ String.prototype.format = function() {
  * @remarks The parseType argument can be "parseInt" or "parseFloat".
  * @remarks The parseType argument can be "parseInt" or "parseFloat".
  * @return {String) Returns converted value with abbreviation.
  * @return {String) Returns converted value with abbreviation.
  */
  */
-Number.prototype.bytesToSize = function(precision, parseType/* = 'parseInt' */) {
+Number.prototype.bytesToSize = function (precision, parseType/* = 'parseInt' */) {
   if (arguments[1] === undefined) {
   if (arguments[1] === undefined) {
     parseType = 'parseInt';
     parseType = 'parseInt';
   }
   }
+
   var value = this;
   var value = this;
   var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
   var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
   var posttxt = 0;
   var posttxt = 0;
   if (this == 0) return 'n/a';
   if (this == 0) return 'n/a';
-  while( value >= 1024 ) {
+  while (value >= 1024) {
     posttxt++;
     posttxt++;
     value = value / 1024;
     value = value / 1024;
   }
   }
-  if (parseType == 'parseInt') {
-    var parsedValue = parseInt(value);
-  } else if (parseType == 'parseFloat') {
-    var parsedValue = parseFloat(value);
-  } else {
-    console.warn('Parameter parseType incorrect');
-  }
+  var parsedValue = window[parseType](value);
 
 
   return parsedValue.toFixed(precision) + " " + sizes[posttxt];
   return parsedValue.toFixed(precision) + " " + sizes[posttxt];
 }
 }
 
 
-Number.prototype.toDaysHoursMinutes = function() {
+Number.prototype.toDaysHoursMinutes = function () {
   var formatted = {},
   var formatted = {},
     dateDiff = this,
     dateDiff = this,
     minK = 60, // sec
     minK = 60, // sec
     hourK = 60 * minK, // sec
     hourK = 60 * minK, // sec
     dayK = 24 * hourK;
     dayK = 24 * hourK;
 
 
-  dateDiff = parseInt(dateDiff/1000);
-  formatted.d = Math.floor(dateDiff/dayK);
+  dateDiff = parseInt(dateDiff / 1000);
+  formatted.d = Math.floor(dateDiff / dayK);
   dateDiff -= formatted.d * dayK;
   dateDiff -= formatted.d * dayK;
-  formatted.h = Math.floor(dateDiff/hourK);
+  formatted.h = Math.floor(dateDiff / hourK);
   dateDiff -= formatted.h * hourK;
   dateDiff -= formatted.h * hourK;
-  formatted.m = Math.floor(dateDiff/minK);
+  formatted.m = Math.floor(dateDiff / minK);
   dateDiff -= formatted.m * minK;
   dateDiff -= formatted.m * minK;
 
 
   return formatted;
   return formatted;
 }
 }
 
 
-Number.prototype.countPercentageRatio = function(maxValue) {
+Number.prototype.countPercentageRatio = function (maxValue) {
   var usedValue = this;
   var usedValue = this;
-  return Math.round((usedValue/maxValue) * 100) + "%";
+  return Math.round((usedValue / maxValue) * 100) + "%";
 }
 }

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

@@ -26,6 +26,7 @@ require('views/common/chart/pie');
 require('views/common/chart/linear');
 require('views/common/chart/linear');
 require('views/common/modal_popup');
 require('views/common/modal_popup');
 require('views/common/metric');
 require('views/common/metric');
+require('views/common/time_range');
 require('views/common/form/field');
 require('views/common/form/field');
 require('views/login');
 require('views/login');
 require('views/main');
 require('views/main');
@@ -86,3 +87,4 @@ require('views/wizard/step2_view');
 require('views/wizard/step3_view');
 require('views/wizard/step3_view');
 require('views/wizard/step4_view');
 require('views/wizard/step4_view');
 require('views/wizard/step5_view');
 require('views/wizard/step5_view');
+require('views/wizard/step6_view');

+ 62 - 27
ambari-web/app/views/common/metric.js

@@ -25,7 +25,35 @@ var App = require('app');
  * @type {*}
  * @type {*}
  */
  */
 App.MetricFilteringWidget = Em.View.extend({
 App.MetricFilteringWidget = Em.View.extend({
-  metrics:[],
+  classNames:['metric-filtering-widget'],
+  /**
+   * metrics
+   */
+  metrics:[
+    Em.Object.create({ label:Em.I18n.t('metric.default'), value:null}),
+    Em.Object.create({ label:Em.I18n.t('metric.cpu'), value:'cpu'}),
+    Em.Object.create({ label:Em.I18n.t('metric.memory'), value:'memory'}),
+    Em.Object.create({ label:Em.I18n.t('metric.network'), value:'network'}),
+    Em.Object.create({ label:Em.I18n.t('metric.io'), value:'io'})
+  ],
+  /**
+   * chosen metric value
+   */
+  chosenMetric:null,
+  chosenMoreMetric: null,
+
+  showMore:0, // toggle more metrics indicator
+
+  /**
+   * return array of chosen metrics
+   */
+  chosenMetrics:function () {
+    return this.get('chosenMetric') ? [this.get('chosenMetric')] : this.get('defaultMetrics');
+  }.property('chosenMetric'),
+
+  /**
+   * metric item view
+   */
   itemView:Em.View.extend({
   itemView:Em.View.extend({
     tagName:'li',
     tagName:'li',
     classNameBindings:['disabled'],
     classNameBindings:['disabled'],
@@ -33,22 +61,31 @@ App.MetricFilteringWidget = Em.View.extend({
       return this.get('isActive') ? "disabled" : false;
       return this.get('isActive') ? "disabled" : false;
     }.property('isActive'),
     }.property('isActive'),
     isActive:function () {
     isActive:function () {
-      return this.get('metric.value') == this.get('widget.controller.metric');
-    }.property('widget.controller.metric'),
-    template:Em.Handlebars.compile('<a {{action activate view.metric.value target="view.widget" }}>{{view.metric.label}}</a>')
+      return this.get('metric.value') == this.get('widget.chosenMetric');
+    }.property('widget.chosenMetric'),
+    template:Em.Handlebars.compile('<a {{action activate view.metric.value target="view.widget" href="true" }}>{{unbound view.metric.label}}</a>')
   }),
   }),
 
 
-  metricsConfig:[
-    { label:Em.I18n.t('metric.default'), value:null},
-    { label:Em.I18n.t('metric.cpu'), value:'cpu'},
-    { label:Em.I18n.t('metric.memory'), value:'memory'},
-    { label:Em.I18n.t('metric.network'), value:'network'},
-    { label:Em.I18n.t('metric.io'), value:'io'}
+  moreItemView: function(){
+    return this.get('itemView').extend({});
+  }.property(),
+
+  moreMetrics:[
+    Em.Object.create({ label:Em.I18n.t('metric.more.cpu'), code:'cpu', items:[] }),
+    Em.Object.create({ label:Em.I18n.t('metric.more.memory'), code:'memory',
+      items:[
+        Em.Object.create({label:Em.I18n.t('metric.more.memory.swapFree'), value:'swap_free'}),
+        Em.Object.create({label:Em.I18n.t('metric.more.memory.memCached'), value:'cpu'})
+      ]
+    })
   ],
   ],
 
 
-  allMetrics:function () {
+  /**
+   * return default selected metrics (currently - all)
+   */
+  defaultMetrics:function () {
     var values = [];
     var values = [];
-    $.each(this.get('metricsConfig'), function () {
+    $.each(this.get('metrics'), function () {
       if (this.value) {
       if (this.value) {
         values.push(this.value);
         values.push(this.value);
       }
       }
@@ -56,32 +93,30 @@ App.MetricFilteringWidget = Em.View.extend({
     return values;
     return values;
   }.property(),
   }.property(),
 
 
-  init:function () {
-    this._super();
+  bindToController:function () {
     var thisW = this;
     var thisW = this;
     var controller = this.get('controller');
     var controller = this.get('controller');
     controller.set('metricWidget', thisW);
     controller.set('metricWidget', thisW);
+  },
 
 
-    this.get('itemView').reopen({
-      widget:thisW
-    });
-
-    // preparing metric objects
-    this.get('metricsConfig').forEach(function (config) {
-      config['widget'] = thisW;
-      thisW.get('metrics').push(Em.Object.create(config))
-    });
+  toggleMore:function () {
+    this.set('showMore', 1 - this.get('showMore'));
+  },
 
 
+  /**
+   * assign this widget to controller, prepare items by metricsConfig
+   */
+  init:function () {
+    this._super();
+    this.bindToController();
   },
   },
 
 
   /**
   /**
-   * write active metric to binded controller
+   * write active metric to widget
    * @param event
    * @param event
    */
    */
   activate:function (event) {
   activate:function (event) {
-    var selected = event.context;
-    var controller = this.get('controller');
-    controller.set('metric', selected);
+    this.set('chosenMetric', event.context);
   },
   },
 
 
   templateName:require('templates/common/metric')
   templateName:require('templates/common/metric')

+ 169 - 0
ambari-web/app/views/common/time_range.js

@@ -0,0 +1,169 @@
+/**
+ * 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');
+
+/**
+ * use: {{view App.TimeRangeWidget controllerBinding="App.router.mainChartsController"}}
+ * set controller.preset field with preset value
+ * widget assign itself to controller like presetWidget (controller.get('presetWidget'))
+ * @type {*}
+ */
+App.TimeRangeWidget = Em.View.extend({
+  classNames:['time-range-widget'],
+  /**
+   * presets
+   */
+  presets:[
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.1hour'), value:'1h'}),
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.12hour'), value:'12h'}),
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.1day'), value:'1d'}),
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.1week'), value:'1wk'}),
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.1month'), value:'1mo'}),
+    Em.Object.create({ label:Em.I18n.t('timeRange.presets.1year'), value:'1yr'})
+  ],
+  /**
+   * chosen preset value
+   */
+  chosenPreset:'1h',
+
+  /**
+   * return array of chosen presets
+   */
+  chosenPresets:function () {
+    return this.get('chosenPreset') ? [this.get('chosenPreset')] : this.get('defaultPresets');
+  }.property('chosenPreset'),
+
+  /**
+   * preset item view
+   */
+  presetView:Em.View.extend({
+    tagName:'li',
+    classNameBindings:['disabled'],
+    disabled:function () {
+      return this.get('isActive') ? "disabled" : false;
+    }.property('isActive'),
+    isActive:function () {
+      return this.get('preset.value') == this.get('widget.chosenPreset');
+    }.property('widget.chosenPreset'),
+    template:Em.Handlebars.compile('<a {{action activate view.preset.value target="view.widget" href="true" }}>{{unbound view.preset.label}}</a>')
+  }),
+
+  /**
+   * return default selected presets (currently - all)
+   */
+  defaultPresets:function () {
+    var values = [];
+    $.each(this.get('presets'), function () {
+      if (this.value) {
+        values.push(this.value);
+      }
+    });
+    return values;
+  }.property(),
+
+  bindToController:function () {
+    var thisW = this;
+    var controller = this.get('controller');
+    controller.set('presetWidget', thisW);
+  },
+
+  /**
+   * assign this widget to controller, prepare items by presetsConfig
+   */
+  init:function () {
+    this._super();
+    this.bindToController();
+  },
+
+  /**
+   * write active preset to widget
+   * @param event
+   */
+  activate:function (event) {
+    this.set('chosenPreset', event.context);
+  },
+
+  templateName:require('templates/common/time_range'),
+
+  dateFromView: Ember.TextField.extend({
+    elementId: 'timeRangeFrom',
+    classNames: 'timeRangeFrom',
+    attributeBindings:['readonly'],
+    readonly: true,
+    didInsertElement: function() {
+      var self = this;
+      $('#timeRangeFrom').datetimepicker({
+        dateFormat: 'dd/mm/yy',
+        timeFormat: 'hh:mm',
+        maxDate: new Date(),
+        onClose:function (dateText, inst) {
+          var endDateTextBox = $('#timeRangeTo');
+          if (endDateTextBox.val() != '') {
+            var testStartDate = new Date(dateText);
+            var testEndDate = new Date(endDateTextBox.val());
+            if (testStartDate > testEndDate)
+              endDateTextBox.val(dateText);
+          }
+          else {
+            endDateTextBox.val(dateText);
+          }
+          self.set('dateFrom', dateText);
+        },
+        onSelect:function (selectedDateTime) {
+          var start = $(this).datetimepicker('getDate');
+          $('#timeRangeTo').datetimepicker('option', 'minDate', new Date(start.getTime()));
+        }
+      });
+      self.set('dateFrom', this.get('value'));
+    }
+  }),
+
+  dateToView: Ember.TextField.extend({
+    elementId: 'timeRangeTo',
+    classNames: 'timeRangeTo',
+    attributeBindings:['readonly'],
+    readonly: true,
+    didInsertElement: function() {
+      var self = this;
+      $('#timeRangeTo').datetimepicker({
+        maxDate: new Date(),
+        dateFormat: 'dd/mm/yy',
+        timeFormat: 'hh:mm',
+        onClose:function (dateText, inst) {
+          var startDateTextBox = $('#timeRangeFrom');
+          if (startDateTextBox.val() != '') {
+            var testStartDate = new Date(startDateTextBox.val());
+            var testEndDate = new Date(dateText);
+            if (testStartDate > testEndDate)
+              startDateTextBox.val(dateText);
+          }
+          else {
+            startDateTextBox.val(dateText);
+          }
+          self.set('dateTo', dateText);
+        },
+        onSelect:function (selectedDateTime) {
+          var end = $(this).datetimepicker('getDate');
+          $('#timeRangeFrom').datetimepicker('option', 'maxDate', new Date(end.getTime()));
+        }
+      });
+      self.set('dateTo', this.get('value'));
+    }
+  })
+})

+ 44 - 19
ambari-web/app/views/installer/step7_view.js

@@ -204,19 +204,19 @@ App.ServiceConfigMasterHostsView = Ember.View.extend(App.ServiceConfigMultipleHo
 
 
 });
 });
 
 
-App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
-
-  classNames: ['slave-hosts', 'span6'],
-  templateName: require('templates/installer/slave_hosts'),
-
-  controllerBinding: 'App.router.slaveComponentGroupsController',
-  valueBinding: 'App.router.slaveComponentGroupsController.hosts',
-
-  disabled: function () {
-    return !this.get('serviceConfig.isEditable');
-  }.property('serviceConfig.isEditable')
-
-});
+//App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
+//
+//  classNames: ['slave-hosts', 'span6'],
+//  templateName: require('templates/installer/slave_hosts'),
+//
+//  controllerBinding: 'App.router.slaveComponentGroupsController',
+//  valueBinding: 'App.router.slaveComponentGroupsController.hosts',
+//
+//  disabled: function () {
+//    return !this.get('serviceConfig.isEditable');
+//  }.property('serviceConfig.isEditable')
+//
+//});
 
 
 App.AddSlaveComponentGroupButton = Ember.View.extend({
 App.AddSlaveComponentGroupButton = Ember.View.extend({
 
 
@@ -249,10 +249,35 @@ App.SlaveComponentGroupsMenu = Em.CollectionView.extend({
     template:Ember.Handlebars.compile('<a {{action showSlaveComponentGroup view.content target="App.router.slaveComponentGroupsController"}} href="#"> {{unbound view.content.name}}</a><i {{action removeSlaveComponentGroup view.content target="App.router.slaveComponentGroupsController"}} class="icon-remove"></i>')  })
     template:Ember.Handlebars.compile('<a {{action showSlaveComponentGroup view.content target="App.router.slaveComponentGroupsController"}} href="#"> {{unbound view.content.name}}</a><i {{action removeSlaveComponentGroup view.content target="App.router.slaveComponentGroupsController"}} class="icon-remove"></i>')  })
 });
 });
 
 
-App.SlaveComponentGroupView = Ember.View.extend({
-//  contentBinding: 'App.router.slaveComponentGroupsController.activeGroup',
-  classNames: ['slave-group']
-//  elementId: function(){
-//    return 'slave-group' + this.get('content.index');
-//  }.property('content.index')
+App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
+  classNames: ['slave-hosts', 'span6'],
+  controllerBinding: 'App.router.slaveComponentGroupsController',
+  valueBinding: 'hosts',
+  group: function(){
+    return this.get('controller.activeGroup');
+  }.property('controller.activeGroup'),
+  hosts: function(){
+    if (this.get('group') !== undefined)
+      return this.get('controller').getHostsByGroup(this.get('group'))
+  }.property('controller.hosts.@each.group', 'group'),
+  templateName: require('templates/installer/slave_component_hosts'),
+  disabled: function () {
+    return !this.get('serviceConfig.isEditable');
+  }.property('serviceConfig.isEditable')
+});
+
+App.SlaveComponentDropDownGroupView = Ember.View.extend({
+  controllerBinding: 'App.router.slaveComponentGroupsController',
+  optionTag: Ember.View.extend({
+    selected: function(){
+      var parent = this._parentView.templateData.view;
+      return parent.get('content.group') === this.get('content');
+    }.property('content'),
+    changeGroup: function(event) {
+      var parent = this._parentView.templateData.view;
+      var groupName = this.get('content');
+      var host = parent.get('content');
+      parent.get('controller').changeHostGroup(host, groupName);
+    }
+  })
 });
 });

+ 9 - 12
ambari-web/app/views/main/charts/horizon/chart.js

@@ -73,18 +73,15 @@ App.MainChartsHorizonChartView = App.ChartView.extend({
   }.property('nodeAttributes'),
   }.property('nodeAttributes'),
 
 
   nodeAttributes: function(){
   nodeAttributes: function(){
-    var controller = App.get('router.mainChartsController');
-    var metric = controller.get('metric');
-    if(!metric) {
-      return controller.get('metricWidget.allMetrics');
-    }
-    return [metric];
-  }.property('App.router.mainChartsController.metric'),
+
+    console.warn("node attributes:", App.router.get('mainChartsController.metricWidget.chosenMetrics'));
+
+    return App.router.get('mainChartsController.metricWidget.chosenMetrics');
+  }.property('App.router.mainChartsController.metricWidget.chosenMetrics'),
 
 
   toggleChart:function () {
   toggleChart:function () {
     var thisChart = this;
     var thisChart = this;
     var host = this.get('host');
     var host = this.get('host');
-    var controller = App.router.get('mainChartsController');
     if (!this.get('chartOpened')) { // if chart will be opened
     if (!this.get('chartOpened')) { // if chart will be opened
       if (!this.get('chartDrawn')) {
       if (!this.get('chartDrawn')) {
         this.drawPlot(); // parent method
         this.drawPlot(); // parent method
@@ -92,11 +89,11 @@ App.MainChartsHorizonChartView = App.ChartView.extend({
       }
       }
 
 
       this.loadHorizonInfo();
       this.loadHorizonInfo();
-      controller.addObserver('metric', thisChart, 'drawPlot');
-      controller.addObserver('metric', thisChart, 'loadHorizonInfo');
+      this.addObserver('nodeAttributes', thisChart, 'drawPlot');
+      this.addObserver('nodeAttributes', thisChart, 'loadHorizonInfo');
     } else { // if chart will be closed
     } else { // if chart will be closed
-      controller.removeObserver('metric', thisChart, 'drawPlot');
-      controller.removeObserver('metric', thisChart, 'loadHorizonInfo');
+      this.removeObserver('nodeAttributes', thisChart, 'drawPlot');
+      this.removeObserver('nodeAttributes', thisChart, 'loadHorizonInfo');
     }
     }
 
 
     this.set('chartOpened', 1 - this.get('chartOpened'));
     this.set('chartOpened', 1 - this.get('chartOpened'));

+ 17 - 17
ambari-web/app/views/main/dashboard/service.js

@@ -30,11 +30,11 @@ App.MainDashboardServiceHealthView = Em.View.extend({
   }.property('parent.service.healthStatus'),
   }.property('parent.service.healthStatus'),
 
 
   startBlink:function () {
   startBlink:function () {
-      this.set('blink', true);
+    this.set('blink', true);
   },
   },
 
 
   doBlink:function () {
   doBlink:function () {
-    if (this.get('blink') && (this.get("state") == "inDOM")){
+    if (this.get('blink') && (this.get("state") == "inDOM")) {
       this.$().effect("pulsate", { times:1 }, "slow", function () {
       this.$().effect("pulsate", { times:1 }, "slow", function () {
         var view = Em.View.views[$(this).attr('id')];
         var view = Em.View.views[$(this).attr('id')];
         view.doBlink();
         view.doBlink();
@@ -73,25 +73,25 @@ App.MainDashboardServiceHealthView = Em.View.extend({
 
 
 App.MainDashboardServiceView = Em.View.extend({
 App.MainDashboardServiceView = Em.View.extend({
   classNames:['service', 'clearfix'],
   classNames:['service', 'clearfix'],
+  data:function () {
+    return this.get('controller.data.' + this.get('serviceName'));
+  }.property('controller.data'),
   service:function () {
   service:function () {
-    var services = this.get('services');
-    thisView = this;
-    var serviceProperty = false;
-    services.forEach(function (service) {
-      if (service.get('serviceName') == thisView.get('serviceName')) {
-        return serviceProperty = service;
-      }
-    })
+    var services = this.get('controller.services');
+    if (services) {
+      thisView = this;
+      var serviceProperty = false;
+      services.forEach(function (service) {
+        if (service.get('serviceName') == thisView.get('serviceName')) {
+          return serviceProperty = service;
+        }
+      })
+    }
 
 
     return serviceProperty;
     return serviceProperty;
-  }.property('services'),
-
-  init:function () {
-    this._super();
-    var thisView = this;
-  },
+  }.property('controller.services'),
 
 
-  criticalAlertsCount: function(){
+  criticalAlertsCount:function () {
     var alerts = this.get('service.alerts');
     var alerts = this.get('service.alerts');
     var count = 0;
     var count = 0;
 
 

+ 21 - 18
ambari-web/app/views/main/dashboard/service/hbase.js

@@ -19,40 +19,43 @@
 var App = require('app');
 var App = require('app');
 
 
 App.MainDashboardServiceHbaseView = App.MainDashboardServiceView.extend({
 App.MainDashboardServiceHbaseView = App.MainDashboardServiceView.extend({
-  templateName: require('templates/main/dashboard/service/hbase'),
-  serviceName: 'hbase',
-  data: {
-    "hbasemaster_addr": "hbasemaster:60010",
-    "total_regionservers": "1",
-    "hbasemaster_starttime": 1348935496,
-    "live_regionservers": 1,
-    "dead_regionservers": 0,
-    "regions_in_transition_count": 0,
-    "chart": [3,7,7,5,5,3,5,3,7]
-  },
-
-  Chart: App.ChartLinearView.extend({
-    data: function(){
+  templateName:require('templates/main/dashboard/service/hbase'),
+  serviceName:'hbase',
+
+  Chart:App.ChartLinearView.extend({
+    data:function () {
       return this.get('_parentView.data.chart');
       return this.get('_parentView.data.chart');
     }.property('_parentView.data.chart')
     }.property('_parentView.data.chart')
   }),
   }),
 
 
-  summaryHeader: function(){
+  masterServerHeapSummary:function () {
+    var percent = this.get('data.master_server_heap_total') > 0
+      ? 100 * this.get('data.master_server_heap_used') / this.get('data.master_server_heap_total')
+      : 0;
+
+    return this.t('dashboard.services.hbase.masterServerHeap.summary').format(
+      this.get('data.master_server_heap_used').bytesToSize(1, 'parseFloat'),
+      this.get('data.master_server_heap_total').bytesToSize(1, 'parseFloat'),
+      percent.toFixed(1)
+    );
+  }.property('data'),
+
+  summaryHeader:function () {
     return this.t("dashboard.services.hbase.summary").format(
     return this.t("dashboard.services.hbase.summary").format(
       this.get('data.live_regionservers'),
       this.get('data.live_regionservers'),
       this.get('data.total_regionservers'),
       this.get('data.total_regionservers'),
-      "?"
+      this.get('data.average_load')
     );
     );
   }.property('data'),
   }.property('data'),
 
 
-  regionServers: function(){
+  regionServers:function () {
     return this.t('dashboard.services.hbase.regionServersSummary').format(
     return this.t('dashboard.services.hbase.regionServersSummary').format(
       this.get('data.live_regionservers'), this.get('data.total_regionservers')
       this.get('data.live_regionservers'), this.get('data.total_regionservers')
     );
     );
 
 
   }.property('data'),
   }.property('data'),
 
 
-  masterServerUptime: function(){
+  masterServerUptime:function () {
     var uptime = this.get('data.hbasemaster_starttime');
     var uptime = this.get('data.hbasemaster_starttime');
     var formatted = uptime.toDaysHoursMinutes();
     var formatted = uptime.toDaysHoursMinutes();
     return this.t('dashboard.services.uptime').format(formatted.d, formatted.h, formatted.m);
     return this.t('dashboard.services.uptime').format(formatted.d, formatted.h, formatted.m);

+ 9 - 21
ambari-web/app/views/main/dashboard/service/hdfs.js

@@ -21,25 +21,6 @@ var App = require('app');
 App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
 App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
   templateName:require('templates/main/dashboard/service/hdfs'),
   templateName:require('templates/main/dashboard/service/hdfs'),
   serviceName:'hdfs',
   serviceName:'hdfs',
-  data:{
-    "namenode_addr":"namenode:50070",
-    "secondary_namenode_addr":"snamenode:50090",
-    "namenode_starttime":1348935028,
-    "total_nodes":"1",
-    "live_nodes":1,
-    "dead_nodes":0,
-    "decommissioning_nodes":0,
-    "dfs_blocks_underreplicated":145,
-    "safemode":false,
-    "pending_upgrades":false,
-    "dfs_configured_capacity":885570207744,
-    "dfs_percent_used":0.01,
-    "dfs_percent_remaining":95.09,
-    "dfs_total_bytes":885570207744,
-    "dfs_used_bytes":104898560,
-    "nondfs_used_bytes":43365113856,
-    "dfs_free_bytes":842100195328
-  },
 
 
   Chart:App.ChartPieView.extend({
   Chart:App.ChartPieView.extend({
     data:function () {
     data:function () {
@@ -54,7 +35,14 @@ App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
   }.property("data"),
   }.property("data"),
 
 
   nodeHeap:function () {
   nodeHeap:function () {
-    return this.t('dashboard.services.hdfs.nodes.heapUsed').format("?", "?", "?");
+
+    var percent = this.get('data.namenode_heap_total') > 0 ? 100 * this.get('data.namenode_heap_used') / this.get('data.namenode_heap_total') : 0;
+
+    return this.t('dashboard.services.hdfs.nodes.heapUsed').format(
+      this.get('data.namenode_heap_used').bytesToSize(1, 'parseFloat'),
+      this.get('data.namenode_heap_total').bytesToSize(1, 'parseFloat')
+      , percent.toFixed(1));
+
   }.property('data'),
   }.property('data'),
 
 
   summaryHeader:function () {
   summaryHeader:function () {
@@ -67,6 +55,6 @@ App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
     var total = this.get('data.dfs_total_bytes') + 0;
     var total = this.get('data.dfs_total_bytes') + 0;
     var used = this.get('data.dfs_used_bytes') + this.get('data.nondfs_used_bytes');
     var used = this.get('data.dfs_used_bytes') + this.get('data.nondfs_used_bytes');
 
 
-    return text.format(used.bytesToSize(2), total.bytesToSize(2), this.get('data.dfs_percent_used'));
+    return text.format(used.bytesToSize(1), total.bytesToSize(1), this.get('data.dfs_percent_used'));
   }.property('data')
   }.property('data')
 });
 });

+ 39 - 21
ambari-web/app/views/main/dashboard/service/mapreduce.js

@@ -19,44 +19,62 @@
 var App = require('app');
 var App = require('app');
 
 
 App.MainDashboardServiceMapreduceView = App.MainDashboardServiceView.extend({
 App.MainDashboardServiceMapreduceView = App.MainDashboardServiceView.extend({
-  templateName: require('templates/main/dashboard/service/mapreduce'),
-  serviceName: 'mapreduce',
-  data: {
-    "jobtracker_addr": "jobtracker:50030",
-    "jobtracker_starttime": 1348935243,
-    "running_jobs": 1,
-    "waiting_jobs": 0,
-    "trackers_total": "1",
-    "trackers_live": 1,
-    "trackers_graylisted": 0,
-    "trackers_blacklisted": 0,
-    "chart": [4,8,7,2,1,4,3,3,3]
-  },
-
-  Chart: App.ChartLinearView.extend({
-    data: function(){
+  templateName:require('templates/main/dashboard/service/mapreduce'),
+  serviceName:'mapreduce',
+
+  Chart:App.ChartLinearView.extend({
+    data:function () {
       return this.get('_parentView.data.chart');
       return this.get('_parentView.data.chart');
     }.property('_parentView.data.chart')
     }.property('_parentView.data.chart')
   }),
   }),
 
 
-  jobTrackerUptime: function(){
+  jobTrackerUptime:function () {
     var uptime = this.get('data.jobtracker_starttime') + 0;
     var uptime = this.get('data.jobtracker_starttime') + 0;
     var formatted = uptime.toDaysHoursMinutes();
     var formatted = uptime.toDaysHoursMinutes();
     return this.t('dashboard.services.uptime').format(formatted.d, formatted.h, formatted.m);
     return this.t('dashboard.services.uptime').format(formatted.d, formatted.h, formatted.m);
   }.property("data"),
   }.property("data"),
 
 
-  summaryHeader: function(){
+  summaryHeader:function () {
     var template = this.t('dashboard.services.mapreduce.summary');
     var template = this.t('dashboard.services.mapreduce.summary');
     return template.format(this.get('data.trackers_live'), this.get('data.trackers_total'), this.get('data.running_jobs'));
     return template.format(this.get('data.trackers_live'), this.get('data.trackers_total'), this.get('data.running_jobs'));
   }.property('data'),
   }.property('data'),
 
 
-  trackersSummary: function (){
+  trackersSummary:function () {
     var template = this.t('dashboard.services.mapreduce.trackersSummary');
     var template = this.t('dashboard.services.mapreduce.trackersSummary');
     return template.format(this.get('data.trackers_live'), this.get('data.trackers_total'));
     return template.format(this.get('data.trackers_live'), this.get('data.trackers_total'));
   }.property('data'),
   }.property('data'),
 
 
-  jobsSummary: function (){
+  trackersHeapSummary:function () {
+    var percent =
+      this.get('data.trackers_heap_total') > 0
+        ? 100 * this.get('data.trackers_heap_used') / this.get('data.trackers_heap_total')
+        : 0;
+
+    return this.t('dashboard.services.mapreduce.jobTrackerHeapSummary').format(
+      this.get('data.trackers_heap_used').bytesToSize(1, "parseFloat"),
+      this.get('data.trackers_heap_total').bytesToSize(1, "parseFloat"),
+      percent.toFixed(1)
+    );
+  }.property('data'),
+
+  jobsSummary:function () {
     var template = this.t('dashboard.services.mapreduce.jobsSummary');
     var template = this.t('dashboard.services.mapreduce.jobsSummary');
-    return template.format(this.get('data.running_jobs'), "?", "?");
+    return template.format(this.get('data.running_jobs'), this.get('data.completed_jobs'), this.get('data.failed_jobs'));
+  }.property('data'),
+
+  mapSlotsSummary:function () {
+    return this.t('dashboard.services.mapreduce.mapSlotsSummary').format(
+      this.get('data.map_slots_occuped'),
+      this.get('data.map_slots_reserved'),
+      this.get('data.map_slots_total')
+    );
+  }.property('data'),
+
+  reduceSlotsSummary:function () {
+    return this.t('dashboard.services.mapreduce.reduceSlotsSummary').format(
+      this.get('data.reduce_slots_occuped'),
+      this.get('data.reduce_slots_reserved'),
+      this.get('data.reduce_slots_total')
+    );
   }.property('data')
   }.property('data')
 });
 });

+ 33 - 9
ambari-web/app/views/main/service/menu.js

@@ -19,25 +19,49 @@
 var App = require('app');
 var App = require('app');
 
 
 App.MainServiceMenuView = Em.CollectionView.extend({
 App.MainServiceMenuView = Em.CollectionView.extend({
-  content:function(){
+  content: function () {
     return App.router.get('mainServiceController.content');
     return App.router.get('mainServiceController.content');
   }.property('App.router.mainServiceController.content'),
   }.property('App.router.mainServiceController.content'),
 
 
-  init: function(){ this._super(); this.activateView(); },
+  init: function () {
+    this._super();
+    this.renderOnRoute();
+  },
+  didInsertElement: function () {
+    App.router.location.addObserver('lastSetURL', this, 'renderOnRoute');
+    this.renderOnRoute();
+  },
 
 
-  tagName:'ul',
-  classNames:["nav", "nav-list", "nav-services"],
+  /**
+   *    Syncs navigation menu with requested URL
+   */
+  renderOnRoute: function () {
+    var last_url = App.router.location.lastSetURL || location.href.replace(/^[^#]*#/, '');
+    if (last_url.substr(1, 4) !== 'main' || !this._childViews) {
+      return;
+    }
+    var reg = /^\/main\/services\/(\d+)/g;
+    var sub_url = reg.exec(last_url);
+    var service_id = (null != sub_url) ? sub_url[1] : 1;
+    $.each(this._childViews, function () {
+      this.set('active', this.get('content.id') == service_id ? "active" : "");
+    });
+  },
+
+  tagName: 'ul',
+  classNames: ["nav", "nav-list", "nav-services"],
 
 
-  activateView:function () {
+  activateView: function () {
     var service = App.router.get('mainServiceItemController.content');
     var service = App.router.get('mainServiceItemController.content');
     $.each(this._childViews, function () {
     $.each(this._childViews, function () {
       this.set('active', (this.get('content.serviceName') == service.get('serviceName') ? "active" : ""));
       this.set('active', (this.get('content.serviceName') == service.get('serviceName') ? "active" : ""));
     });
     });
   }.observes("App.router.mainServiceItemController.content"),
   }.observes("App.router.mainServiceItemController.content"),
 
 
-  itemViewClass:Em.View.extend({
-    classNameBindings:["active"],
-    active:"",
-    template:Ember.Handlebars.compile('<a {{action showService view.content}} href="#" class="health-status-{{unbound view.content.healthStatus}}"> {{unbound view.content.label}}</a>')
+  itemViewClass: Em.View.extend({
+    classNameBindings: ["active"],
+    active: "",
+    // {{action showService view.content}}
+    template: Ember.Handlebars.compile('<a href="#/main/services/{{unbound view.content.id}}/summary" class="health-status-{{unbound view.content.healthStatus}}"> {{unbound view.content.label}}</a>')
   })
   })
 });
 });

+ 6 - 4
ambari-web/app/views/wizard/step5_view.js

@@ -26,8 +26,10 @@ App.WizardStep5View = Em.View.extend({
   didInsertElement: function () {
   didInsertElement: function () {
     var controller = this.get('controller');
     var controller = this.get('controller');
     controller.loadStep();
     controller.loadStep();
-    if (controller.lastZooKeeper()) {
-      controller.lastZooKeeper().set('showAddControl', true);
+
+    var ZooKeeper = controller.lastZooKeeper();
+    if (ZooKeeper) {
+      ZooKeeper.set('showAddControl', true);
     }
     }
   }
   }
 
 
@@ -54,7 +56,7 @@ App.AddControlView = Em.View.extend({
   classNames: ["badge", "badge-important"],
   classNames: ["badge", "badge-important"],
   template: Ember.Handlebars.compile('+'),
   template: Ember.Handlebars.compile('+'),
 
 
-  click: function (event) {
+  click: function () {
     this.get('controller').addZookeepers();
     this.get('controller').addZookeepers();
   }
   }
 });
 });
@@ -65,7 +67,7 @@ App.RemoveControlView = Em.View.extend({
   classNames: ["badge", "badge-important"],
   classNames: ["badge", "badge-important"],
   template: Ember.Handlebars.compile('-'),
   template: Ember.Handlebars.compile('-'),
 
 
-  click: function (event) {
+  click: function () {
     this.get('controller').removeZookeepers(this.get("zId"));
     this.get('controller').removeZookeepers(this.get("zId"));
   }
   }
 });
 });

+ 31 - 0
ambari-web/app/views/wizard/step6_view.js

@@ -0,0 +1,31 @@
+/**
+ * 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');
+
+App.WizardStep6View = Em.View.extend({
+
+  templateName: require('templates/wizard/step6'),
+
+  didInsertElement: function () {
+    var controller = this.get('controller');
+    controller.loadStep();
+  }
+
+});

+ 1530 - 0
ambari-web/vendor/scripts/jquery-ui-timepicker-addon.js

@@ -0,0 +1,1530 @@
+/*
+* jQuery timepicker addon
+* By: Trent Richardson [http://trentrichardson.com]
+* Version 1.0.1
+* Last Modified: 07/01/2012
+*
+* Copyright 2012 Trent Richardson
+* You may use this project under MIT or GPL licenses.
+* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
+* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
+*
+* HERES THE CSS:
+* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+* .ui-timepicker-div dl { text-align: left; }
+* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
+* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
+* .ui-timepicker-div td { font-size: 90%; }
+* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
+*/
+
+/*jslint evil: true, maxlen: 300, white: false, undef: false, nomen: false, onevar: false */
+
+(function($) {
+
+// Prevent "Uncaught RangeError: Maximum call stack size exceeded"
+$.ui.timepicker = $.ui.timepicker || {};
+if ($.ui.timepicker.version) {
+	return;
+}
+
+$.extend($.ui, { timepicker: { version: "1.0.1" } });
+
+/* Time picker manager.
+   Use the singleton instance of this class, $.timepicker, to interact with the time picker.
+   Settings for (groups of) time pickers are maintained in an instance object,
+   allowing multiple different settings on the same page. */
+
+function Timepicker() {
+	this.regional = []; // Available regional settings, indexed by language code
+	this.regional[''] = { // Default regional settings
+		currentText: 'Now',
+		closeText: 'Done',
+		ampm: false,
+		amNames: ['AM', 'A'],
+		pmNames: ['PM', 'P'],
+		timeFormat: 'hh:mm tt',
+		timeSuffix: '',
+		timeOnlyTitle: 'Choose Time',
+		timeText: 'Time',
+		hourText: 'Hour',
+		minuteText: 'Minute',
+		secondText: 'Second',
+		millisecText: 'Millisecond',
+		timezoneText: 'Time Zone'
+	};
+	this._defaults = { // Global defaults for all the datetime picker instances
+		showButtonPanel: true,
+		timeOnly: false,
+		showHour: true,
+		showMinute: true,
+		showSecond: false,
+		showMillisec: false,
+		showTimezone: false,
+		showTime: true,
+		stepHour: 1,
+		stepMinute: 1,
+		stepSecond: 1,
+		stepMillisec: 1,
+		hour: 0,
+		minute: 0,
+		second: 0,
+		millisec: 0,
+		timezone: null,
+		useLocalTimezone: false,
+		defaultTimezone: "+0000",
+		hourMin: 0,
+		minuteMin: 0,
+		secondMin: 0,
+		millisecMin: 0,
+		hourMax: 23,
+		minuteMax: 59,
+		secondMax: 59,
+		millisecMax: 999,
+		minDateTime: null,
+		maxDateTime: null,
+		onSelect: null,
+		hourGrid: 0,
+		minuteGrid: 0,
+		secondGrid: 0,
+		millisecGrid: 0,
+		alwaysSetTime: true,
+		separator: ' ',
+		altFieldTimeOnly: true,
+		showTimepicker: true,
+		timezoneIso8601: false,
+		timezoneList: null,
+		addSliderAccess: false,
+		sliderAccessArgs: null
+	};
+	$.extend(this._defaults, this.regional['']);
+}
+
+$.extend(Timepicker.prototype, {
+	$input: null,
+	$altInput: null,
+	$timeObj: null,
+	inst: null,
+	hour_slider: null,
+	minute_slider: null,
+	second_slider: null,
+	millisec_slider: null,
+	timezone_select: null,
+	hour: 0,
+	minute: 0,
+	second: 0,
+	millisec: 0,
+	timezone: null,
+	defaultTimezone: "+0000",
+	hourMinOriginal: null,
+	minuteMinOriginal: null,
+	secondMinOriginal: null,
+	millisecMinOriginal: null,
+	hourMaxOriginal: null,
+	minuteMaxOriginal: null,
+	secondMaxOriginal: null,
+	millisecMaxOriginal: null,
+	ampm: '',
+	formattedDate: '',
+	formattedTime: '',
+	formattedDateTime: '',
+	timezoneList: null,
+
+	/* Override the default settings for all instances of the time picker.
+	   @param  settings  object - the new settings to use as defaults (anonymous object)
+	   @return the manager object */
+	setDefaults: function(settings) {
+		extendRemove(this._defaults, settings || {});
+		return this;
+	},
+
+	//########################################################################
+	// Create a new Timepicker instance
+	//########################################################################
+	_newInst: function($input, o) {
+		var tp_inst = new Timepicker(),
+			inlineSettings = {};
+
+		for (var attrName in this._defaults) {
+			var attrValue = $input.attr('time:' + attrName);
+			if (attrValue) {
+				try {
+					inlineSettings[attrName] = eval(attrValue);
+				} catch (err) {
+					inlineSettings[attrName] = attrValue;
+				}
+			}
+		}
+		tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, {
+			beforeShow: function(input, dp_inst) {
+				if ($.isFunction(o.beforeShow)) {
+					return o.beforeShow(input, dp_inst, tp_inst);
+                }
+			},
+			onChangeMonthYear: function(year, month, dp_inst) {
+				// Update the time as well : this prevents the time from disappearing from the $input field.
+				tp_inst._updateDateTime(dp_inst);
+				if ($.isFunction(o.onChangeMonthYear)) {
+					o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
+                }
+			},
+			onClose: function(dateText, dp_inst) {
+				if (tp_inst.timeDefined === true && $input.val() !== '') {
+					tp_inst._updateDateTime(dp_inst);
+                }
+				if ($.isFunction(o.onClose)) {
+					o.onClose.call($input[0], dateText, dp_inst, tp_inst);
+                }
+			},
+			timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
+		});
+		tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); });
+		tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); });
+
+		if (tp_inst._defaults.timezoneList === null) {
+			var timezoneList = [];
+			for (var i = -11; i <= 12; i++) {
+				timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00');
+            }
+			if (tp_inst._defaults.timezoneIso8601) {
+				timezoneList = $.map(timezoneList, function(val) {
+					return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3));
+				});
+            }
+			tp_inst._defaults.timezoneList = timezoneList;
+		}
+
+		tp_inst.timezone = tp_inst._defaults.timezone;
+		tp_inst.hour = tp_inst._defaults.hour;
+		tp_inst.minute = tp_inst._defaults.minute;
+		tp_inst.second = tp_inst._defaults.second;
+		tp_inst.millisec = tp_inst._defaults.millisec;
+		tp_inst.ampm = '';
+		tp_inst.$input = $input;
+
+		if (o.altField) {
+			tp_inst.$altInput = $(o.altField)
+				.css({ cursor: 'pointer' })
+				.focus(function(){ $input.trigger("focus"); });
+        }
+
+		if(tp_inst._defaults.minDate===0 || tp_inst._defaults.minDateTime===0)
+		{
+			tp_inst._defaults.minDate=new Date();
+		}
+		if(tp_inst._defaults.maxDate===0 || tp_inst._defaults.maxDateTime===0)
+		{
+			tp_inst._defaults.maxDate=new Date();
+		}
+
+		// datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
+		if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
+			tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
+        }
+		if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
+			tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
+        }
+		if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
+			tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
+        }
+		if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
+			tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
+        }
+		return tp_inst;
+	},
+
+	//########################################################################
+	// add our sliders to the calendar
+	//########################################################################
+	_addTimePicker: function(dp_inst) {
+		var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ?
+				this.$input.val() + ' ' + this.$altInput.val() :
+				this.$input.val();
+
+		this.timeDefined = this._parseTime(currDT);
+		this._limitMinMaxDateTime(dp_inst, false);
+		this._injectTimePicker();
+	},
+
+	//########################################################################
+	// parse the time string from input value or _setTime
+	//########################################################################
+	_parseTime: function(timeString, withDate) {
+		if (!this.inst) {
+			this.inst = $.datepicker._getInst(this.$input[0]);
+		}
+		
+		if (withDate || !this._defaults.timeOnly) 
+		{
+			var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
+			try {
+				var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
+				if (!parseRes.timeObj) { return false; }
+				$.extend(this, parseRes.timeObj);
+			} catch (err)
+			{
+				return false;
+			}
+			return true;
+		}
+		else
+		{
+			var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
+			if(!timeObj) { return false; }
+			$.extend(this, timeObj);
+			return true;
+		}
+	},
+	
+	//########################################################################
+	// generate and inject html for timepicker into ui datepicker
+	//########################################################################
+	_injectTimePicker: function() {
+		var $dp = this.inst.dpDiv,
+			o = this._defaults,
+			tp_inst = this,
+			// Added by Peter Medeiros:
+			// - Figure out what the hour/minute/second max should be based on the step values.
+			// - Example: if stepMinute is 15, then minMax is 45.
+			hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10),
+			minMax  = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10),
+			secMax  = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10),
+			millisecMax  = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10),
+			dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, '');
+
+		// Prevent displaying twice
+		//if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) {
+		if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) {
+			var noDisplay = ' style="display:none;"',
+				html =	'<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' +
+						'<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' +
+						((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
+						'<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' +
+						((o.showTime) ? '' : noDisplay) + '></dd>' +
+						'<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' +
+						((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>',
+				hourGridSize = 0,
+				minuteGridSize = 0,
+				secondGridSize = 0,
+				millisecGridSize = 0,
+				size = null;
+
+            // Hours
+			html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' +
+						((o.showHour) ? '' : noDisplay) + '></div>';
+			if (o.showHour && o.hourGrid > 0) {
+				html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+				for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) {
+					hourGridSize++;
+					var tmph = (o.ampm && h > 12) ? h-12 : h;
+					if (tmph < 10) { tmph = '0' + tmph; }
+					if (o.ampm) {
+						if (h === 0) {
+                            tmph = 12 +'a';
+                        } else {
+                            if (h < 12) { tmph += 'a'; }
+						    else { tmph += 'p'; }
+                        }
+					}
+					html += '<td>' + tmph + '</td>';
+				}
+
+				html += '</tr></table></div>';
+			}
+			html += '</dd>';
+
+			// Minutes
+			html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' +
+					((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+
+					'<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' +
+							((o.showMinute) ? '' : noDisplay) + '></div>';
+
+			if (o.showMinute && o.minuteGrid > 0) {
+				html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+				for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) {
+					minuteGridSize++;
+					html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>';
+				}
+
+				html += '</tr></table></div>';
+			}
+			html += '</dd>';
+
+			// Seconds
+			html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' +
+					((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+
+					'<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+
+							((o.showSecond) ? '' : noDisplay) + '></div>';
+
+			if (o.showSecond && o.secondGrid > 0) {
+				html += '<div style="padding-left: 1px"><table><tr>';
+
+				for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) {
+					secondGridSize++;
+					html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>';
+				}
+
+				html += '</tr></table></div>';
+			}
+			html += '</dd>';
+
+			// Milliseconds
+			html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' +
+					((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+
+					'<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+
+							((o.showMillisec) ? '' : noDisplay) + '></div>';
+
+			if (o.showMillisec && o.millisecGrid > 0) {
+				html += '<div style="padding-left: 1px"><table><tr>';
+
+				for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) {
+					millisecGridSize++;
+					html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>';
+				}
+
+				html += '</tr></table></div>';
+			}
+			html += '</dd>';
+
+			// Timezone
+			html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' +
+					((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
+			html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"'	+
+							((o.showTimezone) ? '' : noDisplay) + '></dd>';
+
+			html += '</dl></div>';
+			var $tp = $(html);
+
+				// if we only want time picker...
+			if (o.timeOnly === true) {
+				$tp.prepend(
+					'<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' +
+						'<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' +
+					'</div>');
+				$dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
+			}
+
+			this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({
+				orientation: "horizontal",
+				value: this.hour,
+				min: o.hourMin,
+				max: hourMax,
+				step: o.stepHour,
+				slide: function(event, ui) {
+					tp_inst.hour_slider.slider( "option", "value", ui.value);
+					tp_inst._onTimeChange();
+				}
+			});
+
+
+			// Updated by Peter Medeiros:
+			// - Pass in Event and UI instance into slide function
+			this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({
+				orientation: "horizontal",
+				value: this.minute,
+				min: o.minuteMin,
+				max: minMax,
+				step: o.stepMinute,
+				slide: function(event, ui) {
+					tp_inst.minute_slider.slider( "option", "value", ui.value);
+					tp_inst._onTimeChange();
+				}
+			});
+
+			this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({
+				orientation: "horizontal",
+				value: this.second,
+				min: o.secondMin,
+				max: secMax,
+				step: o.stepSecond,
+				slide: function(event, ui) {
+					tp_inst.second_slider.slider( "option", "value", ui.value);
+					tp_inst._onTimeChange();
+				}
+			});
+
+			this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({
+				orientation: "horizontal",
+				value: this.millisec,
+				min: o.millisecMin,
+				max: millisecMax,
+				step: o.stepMillisec,
+				slide: function(event, ui) {
+					tp_inst.millisec_slider.slider( "option", "value", ui.value);
+					tp_inst._onTimeChange();
+				}
+			});
+
+			this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select");
+			$.fn.append.apply(this.timezone_select,
+				$.map(o.timezoneList, function(val, idx) {
+					return $("<option />")
+						.val(typeof val == "object" ? val.value : val)
+						.text(typeof val == "object" ? val.label : val);
+				})
+			);
+			if (typeof(this.timezone) != "undefined" && this.timezone !== null && this.timezone !== "") {
+				var local_date = new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12);
+				var local_timezone = timeZoneString(local_date);
+				if (local_timezone == this.timezone) {
+					selectLocalTimeZone(tp_inst);
+				} else {
+					this.timezone_select.val(this.timezone);
+				}
+			} else {
+				if (typeof(this.hour) != "undefined" && this.hour !== null && this.hour !== "") {
+					this.timezone_select.val(o.defaultTimezone);
+				} else {
+					selectLocalTimeZone(tp_inst);
+				}
+			}
+			this.timezone_select.change(function() {
+				tp_inst._defaults.useLocalTimezone = false;
+				tp_inst._onTimeChange();
+			});
+
+			// Add grid functionality
+			if (o.showHour && o.hourGrid > 0) {
+				size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin);
+
+				$tp.find(".ui_tpicker_hour table").css({
+					width: size + "%",
+					marginLeft: (size / (-2 * hourGridSize)) + "%",
+					borderCollapse: 'collapse'
+				}).find("td").each( function(index) {
+					$(this).click(function() {
+						var h = $(this).html();
+						if(o.ampm)	{
+							var ap = h.substring(2).toLowerCase(),
+								aph = parseInt(h.substring(0,2), 10);
+							if (ap == 'a') {
+								if (aph == 12) { h = 0; }
+								else { h = aph; }
+							} else if (aph == 12) { h = 12; }
+							else { h = aph + 12; }
+						}
+						tp_inst.hour_slider.slider("option", "value", h);
+						tp_inst._onTimeChange();
+						tp_inst._onSelectHandler();
+					}).css({
+						cursor: 'pointer',
+						width: (100 / hourGridSize) + '%',
+						textAlign: 'center',
+						overflow: 'hidden'
+					});
+				});
+			}
+
+			if (o.showMinute && o.minuteGrid > 0) {
+				size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin);
+				$tp.find(".ui_tpicker_minute table").css({
+					width: size + "%",
+					marginLeft: (size / (-2 * minuteGridSize)) + "%",
+					borderCollapse: 'collapse'
+				}).find("td").each(function(index) {
+					$(this).click(function() {
+						tp_inst.minute_slider.slider("option", "value", $(this).html());
+						tp_inst._onTimeChange();
+						tp_inst._onSelectHandler();
+					}).css({
+						cursor: 'pointer',
+						width: (100 / minuteGridSize) + '%',
+						textAlign: 'center',
+						overflow: 'hidden'
+					});
+				});
+			}
+
+			if (o.showSecond && o.secondGrid > 0) {
+				$tp.find(".ui_tpicker_second table").css({
+					width: size + "%",
+					marginLeft: (size / (-2 * secondGridSize)) + "%",
+					borderCollapse: 'collapse'
+				}).find("td").each(function(index) {
+					$(this).click(function() {
+						tp_inst.second_slider.slider("option", "value", $(this).html());
+						tp_inst._onTimeChange();
+						tp_inst._onSelectHandler();
+					}).css({
+						cursor: 'pointer',
+						width: (100 / secondGridSize) + '%',
+						textAlign: 'center',
+						overflow: 'hidden'
+					});
+				});
+			}
+
+			if (o.showMillisec && o.millisecGrid > 0) {
+				$tp.find(".ui_tpicker_millisec table").css({
+					width: size + "%",
+					marginLeft: (size / (-2 * millisecGridSize)) + "%",
+					borderCollapse: 'collapse'
+				}).find("td").each(function(index) {
+					$(this).click(function() {
+						tp_inst.millisec_slider.slider("option", "value", $(this).html());
+						tp_inst._onTimeChange();
+						tp_inst._onSelectHandler();
+					}).css({
+						cursor: 'pointer',
+						width: (100 / millisecGridSize) + '%',
+						textAlign: 'center',
+						overflow: 'hidden'
+					});
+				});
+			}
+
+			var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
+			if ($buttonPanel.length) { $buttonPanel.before($tp); }
+			else { $dp.append($tp); }
+
+			this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id);
+
+			if (this.inst !== null) {
+				var timeDefined = this.timeDefined;
+				this._onTimeChange();
+				this.timeDefined = timeDefined;
+			}
+
+			//Emulate datepicker onSelect behavior. Call on slidestop.
+			var onSelectDelegate = function() {
+				tp_inst._onSelectHandler();
+			};
+			this.hour_slider.bind('slidestop',onSelectDelegate);
+			this.minute_slider.bind('slidestop',onSelectDelegate);
+			this.second_slider.bind('slidestop',onSelectDelegate);
+			this.millisec_slider.bind('slidestop',onSelectDelegate);
+
+			// slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
+			if (this._defaults.addSliderAccess){
+				var sliderAccessArgs = this._defaults.sliderAccessArgs;
+				setTimeout(function(){ // fix for inline mode
+					if($tp.find('.ui-slider-access').length === 0){
+						$tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);
+
+						// fix any grids since sliders are shorter
+						var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
+						if(sliderAccessWidth){
+							$tp.find('table:visible').each(function(){
+								var $g = $(this),
+									oldWidth = $g.outerWidth(),
+									oldMarginLeft = $g.css('marginLeft').toString().replace('%',''),
+									newWidth = oldWidth - sliderAccessWidth,
+									newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%';
+
+								$g.css({ width: newWidth, marginLeft: newMarginLeft });
+							});
+						}
+					}
+				},0);
+			}
+			// end slideAccess integration
+
+		}
+	},
+
+	//########################################################################
+	// This function tries to limit the ability to go outside the
+	// min/max date range
+	//########################################################################
+	_limitMinMaxDateTime: function(dp_inst, adjustSliders){
+		var o = this._defaults,
+			dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
+
+		if(!this._defaults.showTimepicker) { return; } // No time so nothing to check here
+
+		if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){
+			var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
+				minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
+
+			if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){
+				this.hourMinOriginal = o.hourMin;
+				this.minuteMinOriginal = o.minuteMin;
+				this.secondMinOriginal = o.secondMin;
+				this.millisecMinOriginal = o.millisecMin;
+			}
+
+			if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
+				this._defaults.hourMin = minDateTime.getHours();
+				if (this.hour <= this._defaults.hourMin) {
+					this.hour = this._defaults.hourMin;
+					this._defaults.minuteMin = minDateTime.getMinutes();
+					if (this.minute <= this._defaults.minuteMin) {
+						this.minute = this._defaults.minuteMin;
+						this._defaults.secondMin = minDateTime.getSeconds();
+					} else if (this.second <= this._defaults.secondMin){
+						this.second = this._defaults.secondMin;
+						this._defaults.millisecMin = minDateTime.getMilliseconds();
+					} else {
+						if(this.millisec < this._defaults.millisecMin) {
+							this.millisec = this._defaults.millisecMin;
+                        }
+						this._defaults.millisecMin = this.millisecMinOriginal;
+					}
+				} else {
+					this._defaults.minuteMin = this.minuteMinOriginal;
+					this._defaults.secondMin = this.secondMinOriginal;
+					this._defaults.millisecMin = this.millisecMinOriginal;
+				}
+			}else{
+				this._defaults.hourMin = this.hourMinOriginal;
+				this._defaults.minuteMin = this.minuteMinOriginal;
+				this._defaults.secondMin = this.secondMinOriginal;
+				this._defaults.millisecMin = this.millisecMinOriginal;
+			}
+		}
+
+		if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){
+			var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
+				maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
+
+			if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){
+				this.hourMaxOriginal = o.hourMax;
+				this.minuteMaxOriginal = o.minuteMax;
+				this.secondMaxOriginal = o.secondMax;
+				this.millisecMaxOriginal = o.millisecMax;
+			}
+
+			if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){
+				this._defaults.hourMax = maxDateTime.getHours();
+				if (this.hour >= this._defaults.hourMax) {
+					this.hour = this._defaults.hourMax;
+					this._defaults.minuteMax = maxDateTime.getMinutes();
+					if (this.minute >= this._defaults.minuteMax) {
+						this.minute = this._defaults.minuteMax;
+						this._defaults.secondMax = maxDateTime.getSeconds();
+					} else if (this.second >= this._defaults.secondMax) {
+						this.second = this._defaults.secondMax;
+						this._defaults.millisecMax = maxDateTime.getMilliseconds();
+					} else {
+						if(this.millisec > this._defaults.millisecMax) { this.millisec = this._defaults.millisecMax; }
+						this._defaults.millisecMax = this.millisecMaxOriginal;
+					}
+				} else {
+					this._defaults.minuteMax = this.minuteMaxOriginal;
+					this._defaults.secondMax = this.secondMaxOriginal;
+					this._defaults.millisecMax = this.millisecMaxOriginal;
+				}
+			}else{
+				this._defaults.hourMax = this.hourMaxOriginal;
+				this._defaults.minuteMax = this.minuteMaxOriginal;
+				this._defaults.secondMax = this.secondMaxOriginal;
+				this._defaults.millisecMax = this.millisecMaxOriginal;
+			}
+		}
+
+		if(adjustSliders !== undefined && adjustSliders === true){
+			var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10),
+                minMax  = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10),
+                secMax  = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10),
+				millisecMax  = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10);
+
+			if(this.hour_slider) {
+				this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour);
+            }
+			if(this.minute_slider) {
+				this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute);
+            }
+			if(this.second_slider){
+				this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second);
+            }
+			if(this.millisec_slider) {
+				this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec);
+            }
+		}
+
+	},
+
+
+	//########################################################################
+	// when a slider moves, set the internal time...
+	// on time change is also called when the time is updated in the text field
+	//########################################################################
+	_onTimeChange: function() {
+		var hour   = (this.hour_slider) ? this.hour_slider.slider('value') : false,
+			minute = (this.minute_slider) ? this.minute_slider.slider('value') : false,
+			second = (this.second_slider) ? this.second_slider.slider('value') : false,
+			millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false,
+			timezone = (this.timezone_select) ? this.timezone_select.val() : false,
+			o = this._defaults;
+
+		if (typeof(hour) == 'object') { hour = false; }
+		if (typeof(minute) == 'object') { minute = false; }
+		if (typeof(second) == 'object') { second = false; }
+		if (typeof(millisec) == 'object') { millisec = false; }
+		if (typeof(timezone) == 'object') { timezone = false; }
+
+		if (hour !== false) { hour = parseInt(hour,10); }
+		if (minute !== false) { minute = parseInt(minute,10); }
+		if (second !== false) { second = parseInt(second,10); }
+		if (millisec !== false) { millisec = parseInt(millisec,10); }
+
+		var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
+
+		// If the update was done in the input field, the input field should not be updated.
+		// If the update was done using the sliders, update the input field.
+		var hasChanged = (hour != this.hour || minute != this.minute ||
+				second != this.second || millisec != this.millisec ||
+				(this.ampm.length > 0 &&
+				    (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) ||
+				timezone != this.timezone);
+
+		if (hasChanged) {
+
+			if (hour !== false) { this.hour = hour; }
+			if (minute !== false) { this.minute = minute; }
+			if (second !== false) { this.second = second; }
+			if (millisec !== false) { this.millisec = millisec; }
+			if (timezone !== false) { this.timezone = timezone; }
+
+			if (!this.inst) { this.inst = $.datepicker._getInst(this.$input[0]); }
+
+			this._limitMinMaxDateTime(this.inst, true);
+		}
+		if (o.ampm) { this.ampm = ampm; }
+
+		//this._formatTime();
+		this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults);
+		if (this.$timeObj) { this.$timeObj.text(this.formattedTime + o.timeSuffix); }
+		this.timeDefined = true;
+		if (hasChanged) { this._updateDateTime(); }
+	},
+
+	//########################################################################
+	// call custom onSelect.
+	// bind to sliders slidestop, and grid click.
+	//########################################################################
+	_onSelectHandler: function() {
+		var onSelect = this._defaults.onSelect;
+		var inputEl = this.$input ? this.$input[0] : null;
+		if (onSelect && inputEl) {
+			onSelect.apply(inputEl, [this.formattedDateTime, this]);
+		}
+	},
+
+	//########################################################################
+	// left for any backwards compatibility
+	//########################################################################
+	_formatTime: function(time, format) {
+		time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone };
+		var tmptime = (format || this._defaults.timeFormat).toString();
+
+		tmptime = $.datepicker.formatTime(tmptime, time, this._defaults);
+
+		if (arguments.length) { return tmptime; }
+		else { this.formattedTime = tmptime; }
+	},
+
+	//########################################################################
+	// update our input with the new date time..
+	//########################################################################
+	_updateDateTime: function(dp_inst) {
+		dp_inst = this.inst || dp_inst;
+		var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
+			dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
+			formatCfg = $.datepicker._getFormatConfig(dp_inst),
+			timeAvailable = dt !== null && this.timeDefined;
+		this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
+		var formattedDateTime = this.formattedDate;
+		// remove following lines to force every changes in date picker to change the input value
+		// Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. 
+		// If the user manually empty the value in the input field, the date picker will never change selected value.
+		//if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
+		//	return;
+        //}
+
+		if (this._defaults.timeOnly === true) {
+			formattedDateTime = this.formattedTime;
+		} else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
+			formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
+		}
+
+		this.formattedDateTime = formattedDateTime;
+
+		if(!this._defaults.showTimepicker) {
+			this.$input.val(this.formattedDate);
+		} else if (this.$altInput && this._defaults.altFieldTimeOnly === true) {
+			this.$altInput.val(this.formattedTime);
+			this.$input.val(this.formattedDate);
+		} else if(this.$altInput) {
+			this.$altInput.val(formattedDateTime);
+			this.$input.val(formattedDateTime);
+		} else {
+			this.$input.val(formattedDateTime);
+		}
+
+		this.$input.trigger("change");
+	}
+
+});
+
+$.fn.extend({
+	//########################################################################
+	// shorthand just to use timepicker..
+	//########################################################################
+	timepicker: function(o) {
+		o = o || {};
+		var tmp_args = arguments;
+
+		if (typeof o == 'object') { tmp_args[0] = $.extend(o, { timeOnly: true }); }
+
+		return $(this).each(function() {
+			$.fn.datetimepicker.apply($(this), tmp_args);
+		});
+	},
+
+	//########################################################################
+	// extend timepicker to datepicker
+	//########################################################################
+	datetimepicker: function(o) {
+		o = o || {};
+		var tmp_args = arguments;
+
+		if (typeof(o) == 'string'){
+			if(o == 'getDate') {
+				return $.fn.datepicker.apply($(this[0]), tmp_args);
+            }
+			else {
+				return this.each(function() {
+					var $t = $(this);
+					$t.datepicker.apply($t, tmp_args);
+				});
+            }
+		}
+		else {
+			return this.each(function() {
+				var $t = $(this);
+				$t.datepicker($.timepicker._newInst($t, o)._defaults);
+			});
+        }
+	}
+});
+
+$.datepicker.parseDateTime = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
+	var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
+	if (parseRes.timeObj)
+	{
+		var t = parseRes.timeObj;
+		parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
+	}
+
+	return parseRes.date;
+};
+
+$.datepicker.parseTime = function(timeFormat, timeString, options) {
+	
+	//########################################################################
+	// pattern for standard and localized AM/PM markers
+	//########################################################################
+	var getPatternAmpm = function(amNames, pmNames) {
+		var markers = [];
+		if (amNames) {
+			$.merge(markers, amNames);
+        }
+		if (pmNames) {
+			$.merge(markers, pmNames);
+        }
+		markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); });
+		return '(' + markers.join('|') + ')?';
+	};
+   
+	//########################################################################
+	// figure out position of time elements.. cause js cant do named captures
+	//########################################################################
+	var getFormatPositions = function( timeFormat ) {
+		var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g),
+			orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 };
+
+		if (finds) {
+			for (var i = 0; i < finds.length; i++) {
+				if (orders[finds[i].toString().charAt(0)] == -1) {
+					orders[finds[i].toString().charAt(0)] = i + 1;
+                }
+            }
+        }
+		return orders;
+	};
+    
+	var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {});
+    
+	var regstr = '^' + timeFormat.toString()
+			.replace(/h{1,2}/ig, '(\\d?\\d)')
+			.replace(/m{1,2}/ig, '(\\d?\\d)')
+			.replace(/s{1,2}/ig, '(\\d?\\d)')
+			.replace(/l{1}/ig, '(\\d?\\d?\\d)')
+			.replace(/t{1,2}/ig, getPatternAmpm(o.amNames, o.pmNames))
+			.replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?')
+			.replace(/\s/g, '\\s?') + o.timeSuffix + '$',
+		order = getFormatPositions(timeFormat),
+		ampm = '',
+		treg;
+
+	treg = timeString.match(new RegExp(regstr, 'i'));
+
+	var resTime = {hour: 0, minute: 0, second: 0, millisec: 0};
+    
+	if (treg) {
+		if (order.t !== -1) {
+			if (treg[order.t] === undefined || treg[order.t].length === 0) {
+				ampm = '';
+				resTime.ampm = '';
+			} else {
+				ampm = $.inArray(treg[order.t], o.amNames) !== -1 ? 'AM' : 'PM';
+				resTime.ampm = o[ampm == 'AM' ? 'amNames' : 'pmNames'][0];
+			}
+		}
+
+		if (order.h !== -1) {
+			if (ampm == 'AM' && treg[order.h] == '12') {
+				resTime.hour = 0; // 12am = 0 hour
+			} else {
+                if (ampm == 'PM' && treg[order.h] != '12') {
+                    resTime.hour = parseInt(treg[order.h],10) + 12; // 12pm = 12 hour, any other pm = hour + 12
+                }
+                else { resTime.hour = Number(treg[order.h]); }
+            }
+		}
+
+		if (order.m !== -1) { resTime.minute = Number(treg[order.m]); }
+		if (order.s !== -1) { resTime.second = Number(treg[order.s]); }
+		if (order.l !== -1) { resTime.millisec = Number(treg[order.l]); }
+		if (order.z !== -1 && treg[order.z] !== undefined) {
+			var tz = treg[order.z].toUpperCase();
+			switch (tz.length) {
+				case 1:	// Z
+					tz = o.timezoneIso8601 ? 'Z' : '+0000';
+					break;
+				case 5:	// +hhmm
+					if (o.timezoneIso8601) {
+						tz = tz.substring(1) == '0000' ?
+							'Z' :
+							tz.substring(0, 3) + ':' + tz.substring(3);
+                    }
+					break;
+				case 6:	// +hh:mm
+					if (!o.timezoneIso8601) {
+						tz = tz == 'Z' || tz.substring(1) == '00:00' ?
+							'+0000' :
+							tz.replace(/:/, '');
+					} else {
+                        if (tz.substring(1) == '00:00') {
+                            tz = 'Z';
+                        }
+                    }
+					break;
+			}
+			resTime.timezone = tz;
+		}
+
+
+		return resTime;
+	}
+
+	return false;
+};
+
+//########################################################################
+// format the time all pretty...
+// format = string format of the time
+// time = a {}, not a Date() for timezones
+// options = essentially the regional[].. amNames, pmNames, ampm
+//########################################################################
+$.datepicker.formatTime = function(format, time, options) {
+	options = options || {};
+	options = $.extend($.timepicker._defaults, options);
+	time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time);
+
+	var tmptime = format;
+	var ampmName = options.amNames[0];
+
+	var hour = parseInt(time.hour, 10);
+	if (options.ampm) {
+		if (hour > 11){
+			ampmName = options.pmNames[0];
+			if(hour > 12) {
+				hour = hour % 12;
+            }
+		}
+		if (hour === 0) {
+			hour = 12;
+        }
+	}
+	tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) {
+		switch (match.toLowerCase()) {
+			case 'hh': return ('0' + hour).slice(-2);
+			case 'h':  return hour;
+			case 'mm': return ('0' + time.minute).slice(-2);
+			case 'm':  return time.minute;
+			case 'ss': return ('0' + time.second).slice(-2);
+			case 's':  return time.second;
+			case 'l':  return ('00' + time.millisec).slice(-3);
+			case 'z':  return time.timezone;
+			case 't': case 'tt':
+				if (options.ampm) {
+					if (match.length == 1) {
+						ampmName = ampmName.charAt(0);
+                    }
+					return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase();
+				}
+				return '';
+		}
+	});
+
+	tmptime = $.trim(tmptime);
+	return tmptime;
+};
+
+//########################################################################
+// the bad hack :/ override datepicker so it doesnt close on select
+// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
+//########################################################################
+$.datepicker._base_selectDate = $.datepicker._selectDate;
+$.datepicker._selectDate = function (id, dateStr) {
+	var inst = this._getInst($(id)[0]),
+		tp_inst = this._get(inst, 'timepicker');
+
+	if (tp_inst) {
+		tp_inst._limitMinMaxDateTime(inst, true);
+		inst.inline = inst.stay_open = true;
+		//This way the onSelect handler called from calendarpicker get the full dateTime
+		this._base_selectDate(id, dateStr);
+		inst.inline = inst.stay_open = false;
+		this._notifyChange(inst);
+		this._updateDatepicker(inst);
+	}
+	else { this._base_selectDate(id, dateStr); }
+};
+
+//#############################################################################################
+// second bad hack :/ override datepicker so it triggers an event when changing the input field
+// and does not redraw the datepicker on every selectDate event
+//#############################################################################################
+$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
+$.datepicker._updateDatepicker = function(inst) {
+
+	// don't popup the datepicker if there is another instance already opened
+	var input = inst.input[0];
+	if($.datepicker._curInst &&
+	   $.datepicker._curInst != inst &&
+	   $.datepicker._datepickerShowing &&
+	   $.datepicker._lastInput != input) {
+		return;
+	}
+
+	if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
+
+		this._base_updateDatepicker(inst);
+
+		// Reload the time control when changing something in the input text field.
+		var tp_inst = this._get(inst, 'timepicker');
+		if(tp_inst) {
+			tp_inst._addTimePicker(inst);
+
+			if (tp_inst._defaults.useLocalTimezone) { //checks daylight saving with the new date.
+				var date = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, 12);
+				selectLocalTimeZone(tp_inst, date);
+				tp_inst._onTimeChange();
+			}
+		}
+	}
+};
+
+//#######################################################################################
+// third bad hack :/ override datepicker so it allows spaces and colon in the input field
+//#######################################################################################
+$.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
+$.datepicker._doKeyPress = function(event) {
+	var inst = $.datepicker._getInst(event.target),
+		tp_inst = $.datepicker._get(inst, 'timepicker');
+
+	if (tp_inst) {
+		if ($.datepicker._get(inst, 'constrainInput')) {
+			var ampm = tp_inst._defaults.ampm,
+				dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
+				datetimeChars = tp_inst._defaults.timeFormat.toString()
+								.replace(/[hms]/g, '')
+								.replace(/TT/g, ampm ? 'APM' : '')
+								.replace(/Tt/g, ampm ? 'AaPpMm' : '')
+								.replace(/tT/g, ampm ? 'AaPpMm' : '')
+								.replace(/T/g, ampm ? 'AP' : '')
+								.replace(/tt/g, ampm ? 'apm' : '')
+								.replace(/t/g, ampm ? 'ap' : '') +
+								" " +
+								tp_inst._defaults.separator +
+								tp_inst._defaults.timeSuffix +
+								(tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') +
+								(tp_inst._defaults.amNames.join('')) +
+								(tp_inst._defaults.pmNames.join('')) +
+								dateChars,
+				chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
+			return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
+		}
+	}
+
+	return $.datepicker._base_doKeyPress(event);
+};
+
+//#######################################################################################
+// Override key up event to sync manual input changes.
+//#######################################################################################
+$.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
+$.datepicker._doKeyUp = function (event) {
+	var inst = $.datepicker._getInst(event.target),
+		tp_inst = $.datepicker._get(inst, 'timepicker');
+
+	if (tp_inst) {
+		if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
+			try {
+				$.datepicker._updateDatepicker(inst);
+			}
+			catch (err) {
+				$.datepicker.log(err);
+			}
+		}
+	}
+
+	return $.datepicker._base_doKeyUp(event);
+};
+
+//#######################################################################################
+// override "Today" button to also grab the time.
+//#######################################################################################
+$.datepicker._base_gotoToday = $.datepicker._gotoToday;
+$.datepicker._gotoToday = function(id) {
+	var inst = this._getInst($(id)[0]),
+		$dp = inst.dpDiv;
+	this._base_gotoToday(id);
+	var tp_inst = this._get(inst, 'timepicker');
+	selectLocalTimeZone(tp_inst);
+	var now = new Date();
+	this._setTime(inst, now);
+	$( '.ui-datepicker-today', $dp).click();
+};
+
+//#######################################################################################
+// Disable & enable the Time in the datetimepicker
+//#######################################################################################
+$.datepicker._disableTimepickerDatepicker = function(target) {
+	var inst = this._getInst(target);
+    if (!inst) { return; }
+    
+	var tp_inst = this._get(inst, 'timepicker');
+	$(target).datepicker('getDate'); // Init selected[Year|Month|Day]
+	if (tp_inst) {
+		tp_inst._defaults.showTimepicker = false;
+		tp_inst._updateDateTime(inst);
+	}
+};
+
+$.datepicker._enableTimepickerDatepicker = function(target) {
+	var inst = this._getInst(target);
+    if (!inst) { return; }
+    
+	var tp_inst = this._get(inst, 'timepicker');
+	$(target).datepicker('getDate'); // Init selected[Year|Month|Day]
+	if (tp_inst) {
+		tp_inst._defaults.showTimepicker = true;
+		tp_inst._addTimePicker(inst); // Could be disabled on page load
+		tp_inst._updateDateTime(inst);
+	}
+};
+
+//#######################################################################################
+// Create our own set time function
+//#######################################################################################
+$.datepicker._setTime = function(inst, date) {
+	var tp_inst = this._get(inst, 'timepicker');
+	if (tp_inst) {
+		var defaults = tp_inst._defaults,
+			// calling _setTime with no date sets time to defaults
+			hour = date ? date.getHours() : defaults.hour,
+			minute = date ? date.getMinutes() : defaults.minute,
+			second = date ? date.getSeconds() : defaults.second,
+			millisec = date ? date.getMilliseconds() : defaults.millisec;
+		//check if within min/max times..
+		// correct check if within min/max times. 	
+		// Rewritten by Scott A. Woodward
+		var hourEq = hour === defaults.hourMin,
+			minuteEq = minute === defaults.minuteMin,
+			secondEq = second === defaults.secondMin;
+		var reset = false;
+		if(hour < defaults.hourMin || hour > defaults.hourMax)  
+			reset = true;
+		else if( (minute < defaults.minuteMin || minute > defaults.minuteMax) && hourEq)
+			reset = true;
+		else if( (second < defaults.secondMin || second > defaults.secondMax ) && hourEq && minuteEq)
+			reset = true;
+		else if( (millisec < defaults.millisecMin || millisec > defaults.millisecMax) && hourEq && minuteEq && secondEq)
+			reset = true;
+		if(reset) {
+			hour = defaults.hourMin;
+			minute = defaults.minuteMin;
+			second = defaults.secondMin;
+			millisec = defaults.millisecMin;
+		}
+		tp_inst.hour = hour;
+		tp_inst.minute = minute;
+		tp_inst.second = second;
+		tp_inst.millisec = millisec;
+		if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour);
+		if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute);
+		if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second);
+		if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec);
+
+		tp_inst._onTimeChange();
+		tp_inst._updateDateTime(inst);
+	}
+};
+
+//#######################################################################################
+// Create new public method to set only time, callable as $().datepicker('setTime', date)
+//#######################################################################################
+$.datepicker._setTimeDatepicker = function(target, date, withDate) {
+	var inst = this._getInst(target);
+    if (!inst) { return; }
+    
+	var tp_inst = this._get(inst, 'timepicker');
+    
+	if (tp_inst) {
+		this._setDateFromField(inst);
+		var tp_date;
+		if (date) {
+			if (typeof date == "string") {
+				tp_inst._parseTime(date, withDate);
+				tp_date = new Date();
+				tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
+			}
+			else { tp_date = new Date(date.getTime()); }
+			if (tp_date.toString() == 'Invalid Date') { tp_date = undefined; }
+			this._setTime(inst, tp_date);
+		}
+	}
+
+};
+
+//#######################################################################################
+// override setDate() to allow setting time too within Date object
+//#######################################################################################
+$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
+$.datepicker._setDateDatepicker = function(target, date) {
+	var inst = this._getInst(target);
+    if (!inst) { return; }
+    
+	var tp_date = (date instanceof Date) ? new Date(date.getTime()) : date;
+
+	this._updateDatepicker(inst);
+	this._base_setDateDatepicker.apply(this, arguments);
+	this._setTimeDatepicker(target, tp_date, true);
+};
+
+//#######################################################################################
+// override getDate() to allow getting time too within Date object
+//#######################################################################################
+$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
+$.datepicker._getDateDatepicker = function(target, noDefault) {
+	var inst = this._getInst(target);
+    if (!inst) { return; }
+    
+    var tp_inst = this._get(inst, 'timepicker');
+
+	if (tp_inst) {
+		this._setDateFromField(inst, noDefault);
+		var date = this._getDate(inst);
+		if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); }
+		return date;
+	}
+	return this._base_getDateDatepicker(target, noDefault);
+};
+
+//#######################################################################################
+// override parseDate() because UI 1.8.14 throws an error about "Extra characters"
+// An option in datapicker to ignore extra format characters would be nicer.
+//#######################################################################################
+$.datepicker._base_parseDate = $.datepicker.parseDate;
+$.datepicker.parseDate = function(format, value, settings) {
+    var splitRes = splitDateTime(format, value, settings);
+	return $.datepicker._base_parseDate(format, splitRes[0], settings);
+};
+
+//#######################################################################################
+// override formatDate to set date with time to the input
+//#######################################################################################
+$.datepicker._base_formatDate = $.datepicker._formatDate;
+$.datepicker._formatDate = function(inst, day, month, year){
+	var tp_inst = this._get(inst, 'timepicker');
+	if(tp_inst) {
+		tp_inst._updateDateTime(inst);
+		return tp_inst.$input.val();
+	}
+	return this._base_formatDate(inst);
+};
+
+//#######################################################################################
+// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
+//#######################################################################################
+$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
+$.datepicker._optionDatepicker = function(target, name, value) {
+	var inst = this._getInst(target);
+    if (!inst) { return null; }
+    
+	var tp_inst = this._get(inst, 'timepicker');
+	if (tp_inst) {
+		var min = null, max = null, onselect = null;
+		if (typeof name == 'string') { // if min/max was set with the string
+			if (name === 'minDate' || name === 'minDateTime' ) {
+				min = value;
+            }
+			else {
+                if (name === 'maxDate' || name === 'maxDateTime') {
+                    max = value;
+                }
+                else {
+                    if (name === 'onSelect') {
+                        onselect = value;
+                    }
+                }
+            }
+		} else {
+            if (typeof name == 'object') { //if min/max was set with the JSON
+                if (name.minDate) {
+                    min = name.minDate;
+                } else {
+                    if (name.minDateTime) {
+                        min = name.minDateTime;
+                    } else {
+                        if (name.maxDate) {
+                            max = name.maxDate;
+                        } else {
+                            if (name.maxDateTime) {
+                                max = name.maxDateTime;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+		if(min) { //if min was set
+			if (min === 0) {
+				min = new Date();
+            } else {
+				min = new Date(min);
+            }
+
+			tp_inst._defaults.minDate = min;
+			tp_inst._defaults.minDateTime = min;
+		} else if (max) { //if max was set
+			if(max===0) {
+				max=new Date();
+            } else {
+				max= new Date(max);
+            }
+			tp_inst._defaults.maxDate = max;
+			tp_inst._defaults.maxDateTime = max;
+		} else if (onselect) {
+			tp_inst._defaults.onSelect = onselect;
+        }
+	}
+	if (value === undefined) {
+		return this._base_optionDatepicker(target, name);
+    }
+	return this._base_optionDatepicker(target, name, value);
+};
+
+//#######################################################################################
+// jQuery extend now ignores nulls!
+//#######################################################################################
+function extendRemove(target, props) {
+	$.extend(target, props);
+	for (var name in props) {
+		if (props[name] === null || props[name] === undefined) {
+			target[name] = props[name];
+        }
+    }
+	return target;
+}
+
+//#######################################################################################
+// Splits datetime string into date ans time substrings.
+// Throws exception when date can't be parsed
+// If only date is present, time substring eill be '' 
+//#######################################################################################
+var splitDateTime = function(dateFormat, dateTimeString, dateSettings)
+{
+	try {
+		var date = $.datepicker._base_parseDate(dateFormat, dateTimeString, dateSettings);
+	} catch (err) {
+		if (err.indexOf(":") >= 0) {
+			// Hack!  The error message ends with a colon, a space, and
+			// the "extra" characters.  We rely on that instead of
+			// attempting to perfectly reproduce the parsing algorithm.
+            var dateStringLength = dateTimeString.length-(err.length-err.indexOf(':')-2);
+            var timeString = dateTimeString.substring(dateStringLength);
+
+            return [dateTimeString.substring(0, dateStringLength), dateTimeString.substring(dateStringLength)];
+            
+		} else {
+			throw err;
+		}
+	}
+	return [dateTimeString, ''];
+};
+
+//#######################################################################################
+// Internal function to parse datetime interval
+// Returns: {date: Date, timeObj: Object}, where
+//   date - parsed date without time (type Date)
+//   timeObj = {hour: , minute: , second: , millisec: } - parsed time. Optional
+//#######################################################################################
+var parseDateTimeInternal = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings)
+{
+    var date;
+    var splitRes = splitDateTime(dateFormat, dateTimeString, dateSettings);
+	date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
+    if (splitRes[1] !== '')
+    {
+        var timeString = splitRes[1];
+        var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator;            
+        if ( timeString.indexOf(separator) !== 0) {
+            throw 'Missing time separator';
+        }
+        timeString = timeString.substring(separator.length);
+        var parsedTime = $.datepicker.parseTime(timeFormat, timeString, timeSettings);
+        if (parsedTime === null) {
+            throw 'Wrong time format';
+        }
+        return {date: date, timeObj: parsedTime};
+    } else {
+        return {date: date};
+    }
+};
+
+//#######################################################################################
+// Internal function to set timezone_select to the local timezone
+//#######################################################################################
+var selectLocalTimeZone = function(tp_inst, date)
+{
+	if (tp_inst && tp_inst.timezone_select) {
+		tp_inst._defaults.useLocalTimezone = true;
+		var now = typeof date !== 'undefined' ? date : new Date();
+		var tzoffset = timeZoneString(now);
+		if (tp_inst._defaults.timezoneIso8601) {
+			tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3);
+        }
+		tp_inst.timezone_select.val(tzoffset);
+	}
+};
+
+// Input: Date Object
+// Output: String with timezone offset, e.g. '+0100'
+var timeZoneString = function(date)
+{
+	var off = date.getTimezoneOffset() * -10100 / 60;
+	var timezone = (off >= 0 ? '+' : '-') + Math.abs(off).toString().substr(1);
+	return timezone;
+};
+
+$.timepicker = new Timepicker(); // singleton instance
+$.timepicker.version = "1.0.1";
+
+})(jQuery);