Ver código fonte

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 anos atrás
pai
commit
7ae7f580a2
39 arquivos alterados com 3304 adições e 774 exclusões
  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
 
+  AMBARI-891. Initial work to refactor the Wizards in Ambari Web. (yusaku)
+
   AMBARI-883. Improve user interactions on Confirm Hosts page of the
   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/step3_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'),
 
   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'),
 
   showAddSlaveComponentGroup: function (event) {
     var componentName = event.context;
+    var component = this.get('selectedSlaveComponent');
     App.ModalPopup.show({
       header: componentName + ' Groups',
       bodyClass: Ember.View.extend({
         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) {
-    var componentName = event.context;
-    var component = this.findProperty('componentName', componentName);
+    var component = this.get('selectedSlaveComponent');
     var newGroupName;
     component.groups.setEach('active', false);
     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};
     component.groups.pushObject(newGroup);
+    $('.remove-group-error').hide();
   },
 
   showEditSlaveComponentGroups: function (event) {
@@ -260,7 +291,7 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
 
   componentGroups: function () {
     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.groups === undefined){
           component.groups = [];
@@ -272,20 +303,35 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
     }
   }.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){
-    var component = this.findProperty('componentName', this.get('selectedComponentName'));
+    var component = this.get('selectedSlaveComponent');
     component.groups.setEach('active', false);
     var group = component.groups.filterProperty('name', event.context.name);
     group.setEach('active', true);
+    var assignedHosts = component.hosts.filterProperty('group', event.context.name);
+    if (assignedHosts.length === 0){
+      $('.remove-group-error').hide();
+    }
   },
 
   removeSlaveComponentGroup: function(event){
     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);
     if (assignedHosts.length !== 0){
       $('.remove-group-error').show();
@@ -293,10 +339,13 @@ App.SlaveComponentGroupsController = Ember.ArrayController.extend({
       $('.remove-group-error').hide();
       var key = component.groups.indexOf(group);
       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){
         var lastGroup;

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

@@ -21,6 +21,70 @@ var App = require('app');
 App.MainDashboardController = Em.Controller.extend({
   name:'mainDashboardController',
   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(){
     return App.router.get('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: ''
    * }
    */
-  loadHosts: function () {
+  loadInstallOptions: function () {
 
     if (!this.content.hosts) {
       this.content.hosts = Em.Object.create();
@@ -249,8 +249,7 @@ App.AddHostController = Em.Controller.extend({
       });
 
       hosts.pushObject(hostInfo);
-    }
-    ;
+    };
 
     console.log('TRACE: pushing ' + hosts);
     return hosts;
@@ -281,6 +280,15 @@ App.AddHostController = Em.Controller.extend({
     });
     console.log('addHostController:saveConfirmedHosts: save hosts ', 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);
     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) {
     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'));
     stepController.filterProperty('isSelected', true).forEach(function (item) {
       serviceNames.push(item.serviceName);
@@ -325,31 +336,116 @@ App.AddHostController = Em.Controller.extend({
     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>
    */
   loadAllPriorSteps: function () {
     var step = this.get('currentStep');
     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':
-      //this.get('installerStep3Controller').loadStep();
+        this.loadConfirmedHosts();
       case '3':
         this.loadServices();
       case '2':
       case '1':
-        this.loadHosts();
+        this.loadInstallOptions();
     }
   },
 

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

@@ -19,17 +19,16 @@
 var App = require('app');
 
 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 () {
     this.set('hosts', []);
@@ -41,56 +40,52 @@ App.WizardStep5Controller = Em.Controller.extend({
   loadStep: function () {
     console.log("TRACE: Loading step5: Assign Masters");
     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) {
-      console.log("TRACE: service name is: " + item);
       this.get("selectedServices").pushObject(Ember.Object.create({service_name: item}));
     }, this);
 
-    return services;
-
-  },
+    var masterHosts = this.get('content.masterHosts');
 
-  loadComponents: function (services) {
     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) {
         var componentInfo = masterComponents.filterProperty('service_name', services[index]);
         componentInfo.forEach(function (_componentInfo) {
@@ -102,27 +97,28 @@ App.WizardStep5Controller = Em.Controller.extend({
           components.add(componentObj);
         }, this);
       }
+
     } else {
-      var masterComponentHosts = App.db.getMasterComponentHosts();
-      masterComponentHosts.forEach(function (_masterComponentHost) {
+
+      masterHosts.forEach(function (_masterComponentHost) {
         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.availableHosts = [];
         components.add(componentObj);
       }, this);
+
     }
     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) {
     var zookeeperComponent = null, componentObj = null;
-    var services = [];
-    services = this.getMasterComponents();
+    var services = this.get('selectedServicesMasters').slice(0);
     if (services.length) {
       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) {
     var noOfHosts = this.get('hosts').length;
     if (componentName === 'KERBEROS_SERVER') {
@@ -561,31 +562,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     else {
       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 = {
 
-  '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.',
-  '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.',
-  '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 ' +
     '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' +
     '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.',
-  '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.',
-  '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.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.dead':'dead',
   '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');
       controller.setCurrentStep('4', false);
       controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep5' /*, controller.get('content.services')*/);
+      controller.connectOutlet('wizardStep5', controller.get('content'));
 
     },
     back: Em.Router.transitionTo('step3'),
     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({
 //    route: '/step6',
 //    connectOutlets: function (router, context) {

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

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

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

@@ -167,13 +167,13 @@ h1 {
 /***************
  * Ambari wide icon colors
  ***************/
-.icon-ok{
-    color: #5AB400;
+.icon-ok {
+  color: #5AB400;
 }
-.icon-remove{
-    color: #FF4B4B;
+
+.icon-remove {
+  color: #FF4B4B;
 }
- 
 
 #installer, #add-host {
   h2 {
@@ -242,12 +242,12 @@ h1 {
     }
     .slave-component-group-menu {
       float: left;
-      .nav{
+      .nav {
         margin-bottom: 10px;
       }
-      .nav li{
+      .nav li {
         position: relative;
-        a{
+        a {
           padding-right: 24px;
         }
         i {
@@ -256,13 +256,14 @@ h1 {
           right: 7px;
           top: 10px;
           z-index: 2;
+          cursor: default;
         }
         i:hover {
           border: 1px solid grey;
         }
       }
     }
-    .remove-group-error{
+    .remove-group-error {
       display: none;
     }
     .add-slave-component-group {
@@ -404,11 +405,11 @@ a:focus {
   }
 
   .service {
-    position:relative;
+    position: relative;
     margin-top: 10px;
     border-bottom: 1px solid #b8b8b8;
     padding-left: 10px;
-    width: 540px;
+    margin-right: 20px;
 
     .name {
       line-height: 21px;
@@ -426,15 +427,19 @@ a:focus {
       margin-top: 14px;
       color: #7b7b7b;
       font-size: 13px;
-      width: 410px;
+      width: 80%;
+      tr > td:first-child {
+        padding-right: 10px;
+        text-align: right !important;
+      }
       th, td {
-        line-height: 8px !important;
+        padding: 4px;
       }
     }
 
     .chart {
       right: 0;
-      top: 5px;
+      top: 27px;
       position: absolute;
       overflow: visible; // for quick links
       text-align: center;
@@ -446,14 +451,19 @@ a:focus {
     }
   }
 }
+
 #summary-info {
-  margin: 10px 0;
-/*
+  border-top: none;
+  border-collapse: collapse;
+
+  td.summary-label {
+    text-align: right;
+  }
+
   tr td:first-child {
-    font-weight: bold;
-    background: #d0d0d0;
+    text-align: right;
   }
-*/
+
   a {
     text-decoration: underline;
     &:hover {
@@ -461,6 +471,7 @@ a:focus {
     }
   }
 }
+
 .more-stats {
   display: block;
   width: 100%;
@@ -478,6 +489,7 @@ a:focus {
 
 /*start alerts summary*/
 .alerts {
+  border: 1px solid #ddd;
   margin: 0px;
   max-height: 500px;
   overflow-y: auto;
@@ -518,7 +530,7 @@ a:focus {
       padding-left: 7px;
     }
   }
-  
+
 }
 
 .go-to {
@@ -565,6 +577,12 @@ a:focus {
   margin-top: -48px;
 }
 
+.service-content {
+  #summary-info {
+    margin-bottom: 0;
+  }
+}
+
 /*End Services*/
 
 /*Hosts*/
@@ -594,7 +612,7 @@ a:focus {
     margin-bottom: 5px;
   }
   .box-header .host-title {
-    margin:0;
+    margin: 0;
     padding-left: 17px;
   }
   .box-header .button-section {
@@ -623,7 +641,7 @@ a:focus {
     background: #F5F5F5;
   }
   .host-components .btn-group {
-    margin:0 5px 10px 0;
+    margin: 0 5px 10px 0;
   }
 }
 
@@ -948,7 +966,7 @@ ul.filter {
   display: block !important;
 }
 
-.displayInline {
+.display-inline-block {
   display: inline-block !important;
 }
 
@@ -1074,4 +1092,80 @@ ul.inline li {
 .table.no-borders th, .table.no-borders td {
   border-top: none;
 }
+
 /* 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
 * 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}}
-    {{view view.itemView metricBinding="metric"}}
+  {{view view.itemView metricBinding="metric" widgetBinding="view"}}
   {{/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>

+ 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}}
           <form class="form-horizontal">
             {{#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}}
             {{#each view.categoryConfigs}}
             <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.MainChartsMenuView}}
+{{view App.TimeRangeWidget controllerBinding="App.router.mainChartsController"}}
+{{view App.MainChartsMenuView}}
 {{outlet}}

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

@@ -25,12 +25,12 @@
             <h4>{{t dashboard.services}}</h4>
           </div>
           <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>
         </div>
       </div>

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

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

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

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

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

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

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

@@ -27,173 +27,184 @@
         </ul>
       </li>
     </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}}
-          </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 class="span6">
 		<div class="box">

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

@@ -71,6 +71,6 @@
   <div style="clear: both;"></div>
 </div>
 <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;
 }
 
-App.db.getService = function(serviceInfo) {
+App.db.getService = function() {
   console.log('TRACE: Entering db:getService function');
   App.db.data = localStorage.getObject('ambari');
   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
  * limitations under the License.
  */
-String.prototype.capitalize = function()
-{
+String.prototype.capitalize = function () {
   return this.charAt(0).toUpperCase() + this.slice(1);
 }
 
 Em.CoreObject.reopen({
-  t: function(key, attrs){
+  t:function (key, attrs) {
     return Em.I18n.t(key, attrs)
   }
 });
 
-Handlebars.registerHelper('log', function(variable) {
+Handlebars.registerHelper('log', function (variable) {
   console.log(variable);
 });
 
-Handlebars.registerHelper('warn', function(variable) {
+Handlebars.registerHelper('warn', function (variable) {
   console.warn(variable);
 });
 
-String.prototype.format = function() {
+String.prototype.format = function () {
   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;
   });
 };
@@ -48,48 +47,43 @@ String.prototype.format = function() {
  * @remarks The parseType argument can be "parseInt" or "parseFloat".
  * @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) {
     parseType = 'parseInt';
   }
+
   var value = this;
   var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
   var posttxt = 0;
   if (this == 0) return 'n/a';
-  while( value >= 1024 ) {
+  while (value >= 1024) {
     posttxt++;
     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];
 }
 
-Number.prototype.toDaysHoursMinutes = function() {
+Number.prototype.toDaysHoursMinutes = function () {
   var formatted = {},
     dateDiff = this,
     minK = 60, // sec
     hourK = 60 * minK, // sec
     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;
-  formatted.h = Math.floor(dateDiff/hourK);
+  formatted.h = Math.floor(dateDiff / hourK);
   dateDiff -= formatted.h * hourK;
-  formatted.m = Math.floor(dateDiff/minK);
+  formatted.m = Math.floor(dateDiff / minK);
   dateDiff -= formatted.m * minK;
 
   return formatted;
 }
 
-Number.prototype.countPercentageRatio = function(maxValue) {
+Number.prototype.countPercentageRatio = function (maxValue) {
   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/modal_popup');
 require('views/common/metric');
+require('views/common/time_range');
 require('views/common/form/field');
 require('views/login');
 require('views/main');
@@ -86,3 +87,4 @@ require('views/wizard/step2_view');
 require('views/wizard/step3_view');
 require('views/wizard/step4_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 {*}
  */
 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({
     tagName:'li',
     classNameBindings:['disabled'],
@@ -33,22 +61,31 @@ App.MetricFilteringWidget = Em.View.extend({
       return this.get('isActive') ? "disabled" : false;
     }.property('isActive'),
     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 = [];
-    $.each(this.get('metricsConfig'), function () {
+    $.each(this.get('metrics'), function () {
       if (this.value) {
         values.push(this.value);
       }
@@ -56,32 +93,30 @@ App.MetricFilteringWidget = Em.View.extend({
     return values;
   }.property(),
 
-  init:function () {
-    this._super();
+  bindToController:function () {
     var thisW = this;
     var controller = this.get('controller');
     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
    */
   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')

+ 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({
 
@@ -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>')  })
 });
 
-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'),
 
   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 () {
     var thisChart = this;
     var host = this.get('host');
-    var controller = App.router.get('mainChartsController');
     if (!this.get('chartOpened')) { // if chart will be opened
       if (!this.get('chartDrawn')) {
         this.drawPlot(); // parent method
@@ -92,11 +89,11 @@ App.MainChartsHorizonChartView = App.ChartView.extend({
       }
 
       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
-      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'));

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

@@ -30,11 +30,11 @@ App.MainDashboardServiceHealthView = Em.View.extend({
   }.property('parent.service.healthStatus'),
 
   startBlink:function () {
-      this.set('blink', true);
+    this.set('blink', true);
   },
 
   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 () {
         var view = Em.View.views[$(this).attr('id')];
         view.doBlink();
@@ -73,25 +73,25 @@ App.MainDashboardServiceHealthView = Em.View.extend({
 
 App.MainDashboardServiceView = Em.View.extend({
   classNames:['service', 'clearfix'],
+  data:function () {
+    return this.get('controller.data.' + this.get('serviceName'));
+  }.property('controller.data'),
   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;
-  }.property('services'),
-
-  init:function () {
-    this._super();
-    var thisView = this;
-  },
+  }.property('controller.services'),
 
-  criticalAlertsCount: function(){
+  criticalAlertsCount:function () {
     var alerts = this.get('service.alerts');
     var count = 0;
 

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

@@ -19,40 +19,43 @@
 var App = require('app');
 
 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');
     }.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(
       this.get('data.live_regionservers'),
       this.get('data.total_regionservers'),
-      "?"
+      this.get('data.average_load')
     );
   }.property('data'),
 
-  regionServers: function(){
+  regionServers:function () {
     return this.t('dashboard.services.hbase.regionServersSummary').format(
       this.get('data.live_regionservers'), this.get('data.total_regionservers')
     );
 
   }.property('data'),
 
-  masterServerUptime: function(){
+  masterServerUptime:function () {
     var uptime = this.get('data.hbasemaster_starttime');
     var formatted = uptime.toDaysHoursMinutes();
     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({
   templateName:require('templates/main/dashboard/service/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({
     data:function () {
@@ -54,7 +35,14 @@ App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
   }.property("data"),
 
   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'),
 
   summaryHeader:function () {
@@ -67,6 +55,6 @@ App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
     var total = this.get('data.dfs_total_bytes') + 0;
     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')
 });

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

@@ -19,44 +19,62 @@
 var App = require('app');
 
 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');
     }.property('_parentView.data.chart')
   }),
 
-  jobTrackerUptime: function(){
+  jobTrackerUptime:function () {
     var uptime = this.get('data.jobtracker_starttime') + 0;
     var formatted = uptime.toDaysHoursMinutes();
     return this.t('dashboard.services.uptime').format(formatted.d, formatted.h, formatted.m);
   }.property("data"),
 
-  summaryHeader: function(){
+  summaryHeader:function () {
     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'));
   }.property('data'),
 
-  trackersSummary: function (){
+  trackersSummary:function () {
     var template = this.t('dashboard.services.mapreduce.trackersSummary');
     return template.format(this.get('data.trackers_live'), this.get('data.trackers_total'));
   }.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');
-    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')
 });

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

@@ -19,25 +19,49 @@
 var App = require('app');
 
 App.MainServiceMenuView = Em.CollectionView.extend({
-  content:function(){
+  content: function () {
     return App.router.get('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');
     $.each(this._childViews, function () {
       this.set('active', (this.get('content.serviceName') == service.get('serviceName') ? "active" : ""));
     });
   }.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 () {
     var controller = this.get('controller');
     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"],
   template: Ember.Handlebars.compile('+'),
 
-  click: function (event) {
+  click: function () {
     this.get('controller').addZookeepers();
   }
 });
@@ -65,7 +67,7 @@ App.RemoveControlView = Em.View.extend({
   classNames: ["badge", "badge-important"],
   template: Ember.Handlebars.compile('-'),
 
-  click: function (event) {
+  click: function () {
     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);