Browse Source

AMBARI-1059. Refactor cluster management. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1418969 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 years ago
parent
commit
0eabc1a3c6
60 changed files with 3369 additions and 2825 deletions
  1. 1 0
      ambari-web/app/controllers.js
  2. 49 62
      ambari-web/app/controllers/global/background_operations_controller.js
  3. 79 135
      ambari-web/app/controllers/global/cluster_controller.js
  4. 150 0
      ambari-web/app/controllers/global/update_controller.js
  5. 1 0
      ambari-web/app/controllers/installer.js
  6. 1 0
      ambari-web/app/controllers/login_controller.js
  7. 7 0
      ambari-web/app/controllers/main.js
  8. 3 3
      ambari-web/app/controllers/main/host.js
  9. 61 88
      ambari-web/app/controllers/main/host/add_controller.js
  10. 10 5
      ambari-web/app/controllers/main/host/details.js
  11. 4 1
      ambari-web/app/controllers/main/service.js
  12. 8 7
      ambari-web/app/controllers/main/service/add_controller.js
  13. 9 2
      ambari-web/app/controllers/main/service/info/configs.js
  14. 10 6
      ambari-web/app/controllers/main/service/item.js
  15. 62 50
      ambari-web/app/controllers/wizard/step5_controller.js
  16. 4 0
      ambari-web/app/controllers/wizard/step6_controller.js
  17. 80 29
      ambari-web/app/controllers/wizard/step8_controller.js
  18. 5 1
      ambari-web/app/initialize.js
  19. 2 3
      ambari-web/app/mappers/jobs_mapper.js
  20. 0 307
      ambari-web/app/mappers/services_mapper.js
  21. 12 3
      ambari-web/app/mappers/status_mapper.js
  22. 372 0
      ambari-web/app/mappers/update_mapper.js
  23. 0 1
      ambari-web/app/models/host.js
  24. 6 8
      ambari-web/app/models/hosts.js
  25. 1 3
      ambari-web/app/models/user.js
  26. 70 17
      ambari-web/app/router.js
  27. 52 78
      ambari-web/app/routes/add_host_routes.js
  28. 10 5
      ambari-web/app/routes/add_service_routes.js
  29. 13 8
      ambari-web/app/routes/installer.js
  30. 8 2
      ambari-web/app/routes/main.js
  31. 10 3
      ambari-web/app/styles/application.less
  32. 4 0
      ambari-web/app/styles/apps.less
  33. 2 0
      ambari-web/app/templates/main/charts/linear_time.hbs
  34. 1 1
      ambari-web/app/templates/main/dashboard/service/hdfs.hbs
  35. 1 1
      ambari-web/app/templates/main/dashboard/service/mapreduce.hbs
  36. 2 0
      ambari-web/app/templates/main/host.hbs
  37. 5 7
      ambari-web/app/templates/main/host/add.hbs
  38. 2 0
      ambari-web/app/templates/main/host/details.hbs
  39. 2 0
      ambari-web/app/templates/main/service.hbs
  40. 9 0
      ambari-web/app/templates/main/service/info/configs.hbs
  41. 2 2
      ambari-web/app/templates/main/service/info/summary.hbs
  42. 2 0
      ambari-web/app/templates/main/service/item.hbs
  43. 10 11
      ambari-web/app/utils/graph.js
  44. 54 6
      ambari-web/app/utils/helper.js
  45. 55 0
      ambari-web/app/utils/string_utils.js
  46. 166 80
      ambari-web/app/views/common/chart/linear_time.js
  47. 21 8
      ambari-web/app/views/common/quick_view_link_view.js
  48. 6 4
      ambari-web/app/views/main/apps/item/bar_view.js
  49. 1 1
      ambari-web/app/views/main/dashboard.js
  50. 1 0
      ambari-web/app/views/main/dashboard/cluster_metrics/network.js
  51. 1 1
      ambari-web/app/views/main/dashboard/service/hbase.js
  52. 1 1
      ambari-web/app/views/main/dashboard/service/hdfs.js
  53. 1 1
      ambari-web/app/views/main/dashboard/service/mapreduce.js
  54. 14 11
      ambari-web/app/views/main/menu.js
  55. 10 2
      ambari-web/app/views/main/service/info/configs.js
  56. 5 7
      ambari-web/app/views/main/service/info/summary.js
  57. 3 3
      ambari-web/app/views/main/service/item.js
  58. 5 2
      ambari-web/app/views/main/service/menu.js
  59. 53 19
      ambari-web/app/views/wizard/step5_view.js
  60. 1830 1830
      ambari-web/vendor/scripts/jquery-ui-timepicker-addon.js

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

@@ -64,3 +64,4 @@ require('controllers/wizard/step8_controller');
 require('controllers/wizard/step9_controller');
 require('controllers/wizard/step10_controller');
 require('controllers/global/cluster_controller');
+require('controllers/global/update_controller');

+ 49 - 62
ambari-web/app/controllers/global/background_operations_controller.js

@@ -29,54 +29,11 @@ App.BackgroundOperationsController = Em.Controller.extend({
   allOperations: [],
   allOperationsCount : 0,
 
-  /**
-   * Update it every time when background operations for services are changed
-   */
-  serviceOperationsChangeTime: function(){
-    return (new Date().getTime());
-  }.property('hdfsOperations', 'mapReduceOperations'),
-
-  hdfsOperations : function(){
-    var all = this.get('allOperations');
-    var result = [];
-
-    all.forEach(function(item){
-      if( ['NAMENODE', 'SECONDARY_NAMENODE', 'DATANODE', 'HDFS_CLIENT', 'HDFS_SERVICE_CHECK'].contains(item.role)){
-        result.push(item);
-      }
-    })
-    return result;
-  }.property('allOperations.@each'),
-
-  mapReduceOperations : function(){
-    var all = this.get('allOperations');
-    var result = [];
-
-    all.forEach(function(item){
-      if( ['MAPREDUCE_CLIENT', 'JOBTRACKER', 'TASKTRACKER', 'MAPREDUCE_SERVICE_CHECK'].contains(item.role)){
-        result.push(item);
-      }
-    })
-    return result;
-
-  }.property('allOperations.@each'),
-
-  getOperationsFor: function(serviceName){
-    switch(serviceName.toUpperCase()){
-      case 'HDFS':
-        return this.get('hdfsOperations');
-      case 'MAPREDUCE':
-        return this.get('mapReduceOperations');
-      default:
-        return [];
-    }
-  },
-
   getOperationsForRequestId: function(requestId){
     return this.get('allOperations').filterProperty('request_id', requestId);
   },
 
-  updateInterval: 6000,
+  updateInterval: App.bgOperationsUpdateInterval,
   url : '',
 
   generateUrl: function(){
@@ -88,6 +45,40 @@ App.BackgroundOperationsController = Em.Controller.extend({
     return url;
   },
 
+  timeoutId : null,
+
+  /**
+   * Background operations will not be working if receive <code>attemptsCount</code> response with errors
+   */
+  attemptsCount: 20,
+
+  errorsCount: 0,
+
+  /**
+   * Call this.loadOperations with delay
+   * @param delay time in milliseconds (updateInterval by default)
+   * @param reason reason why we call it(used to calculate count of errors)
+   */
+  loadOperationsDelayed: function(delay, reason){
+    delay = delay || this.get('updateInterval');
+    var self = this;
+
+    if(reason && reason.indexOf('error:clusterName:') === 0){
+      var errors = this.get('errorsCount') + 1;
+      this.set('errorsCount', errors);
+      if(errors > this.get('attemptsCount')){
+        console.log('Stop loading background operations: clusterName is undefined');
+        return;
+      }
+    }
+
+    this.set('timeoutId',
+      setTimeout(function(){
+        self.loadOperations();
+      }, delay)
+    );
+  },
+
   /**
    * Reload operations
    * We can call it manually <code>controller.loadOperations();</code>
@@ -95,16 +86,19 @@ App.BackgroundOperationsController = Em.Controller.extend({
    */
   loadOperations : function(){
 
+    var timeoutId = this.get('timeoutId');
+    if(timeoutId){
+      clearTimeout(timeoutId);
+      this.set('timeoutId', null);
+    }
+
     if(!this.get('isWorking')){
       return;
     }
     var self = this;
 
     if(!App.router.getClusterName()){
-      console.log('clusterName is undefined')
-      setTimeout(function(){
-        self.loadOperations();
-      },1000);
+      this.loadOperationsDelayed(this.get('updateInterval')/2, 'error:clusterName');
       return;
     }
 
@@ -122,23 +116,11 @@ App.BackgroundOperationsController = Em.Controller.extend({
         //refresh model
         self.updateBackgroundOperations(data);
 
-        //load data again if isWorking = true
-        if(self.get('isWorking')){
-          setTimeout(function(){
-            self.loadOperations();
-          }, self.get('updateInterval'));
-        }
+        self.loadOperationsDelayed();
       },
 
       error: function (request, ajaxOptions, error) {
-        console.log('cannot load background operations array');
-
-        //load data again if isWorking = true
-        if(self.get('isWorking')){
-          setTimeout(function(){
-            self.loadOperations();
-          }, self.get('updateInterval'));
-        }
+        self.loadOperationsDelayed(null, 'error:response error');
       },
 
       statusCode: require('data/statusCodes')
@@ -160,6 +142,10 @@ App.BackgroundOperationsController = Em.Controller.extend({
       });
     });
 
+    runningTasks = runningTasks.sort(function(a,b){
+      return a.id - b.id;
+    });
+
     var currentTasks = this.get('allOperations');
 
     runningTasks.forEach(function(item){
@@ -204,6 +190,7 @@ App.BackgroundOperationsController = Em.Controller.extend({
    * Onclick handler for background operations number located right to logo
    */
   showPopup: function(){
+    this.loadOperations();
     App.ModalPopup.show({
       headerClass: Ember.View.extend({
         controllerBinding: 'App.router.backgroundOperationsController',

+ 79 - 135
ambari-web/app/controllers/global/cluster_controller.js

@@ -22,6 +22,24 @@ App.ClusterController = Em.Controller.extend({
   name:'clusterController',
   cluster:null,
   isLoaded:false,
+  graphs: [],
+  graphsUpdate: function () {
+    if (!this.get('isLoaded')) return;
+    var self = this;
+    console.log('graphs updated', self.get('graphs'));
+    var interval = setInterval(function () {
+      self.get('graphs').forEach(function (_graph) {
+        var view = Em.View.views[_graph.id];
+        if (view) {
+          console.log('updated graph', _graph.name);
+          view.$(".chart-container").children().each(function (index, value) {
+            $(value).children().remove();
+          });
+          view.loadData();
+        }
+      })
+    }, App.graphUpdateInterval);
+  }.observes('isLoaded'),
   updateLoadStatus:function (item) {
     var loadList = this.get('dataLoadList');
     var loaded = true;
@@ -82,7 +100,7 @@ App.ClusterController = Em.Controller.extend({
    *
    * If null is returned, it means GANGLIA service is not installed.
    */
-  gangliaUrl: function () {
+  gangliaUrl:function () {
     if (App.testMode) {
       return 'http://gangliaserver/ganglia/?t=yes';
     } else {
@@ -103,7 +121,7 @@ App.ClusterController = Em.Controller.extend({
       }
       return null;
     }
-  }.property('dataLoadList.services'),
+  }.property('App.router.updateController.isUpdated'),
 
   /**
    * Provides the URL to use for NAGIOS server. This URL
@@ -126,14 +144,14 @@ App.ClusterController = Em.Controller.extend({
           if (nagiosSvcComponent) {
             var hostName = nagiosSvcComponent.get('host.hostName');
             if (hostName) {
-              return "http://"+hostName+"/nagios";
+              return "http://" + hostName + "/nagios";
             }
           }
         }
       }
       return null;
     }
-  }.property('dataLoadList.services'),
+  }.property('App.router.updateController.isUpdated'),
 
   isNagiosInstalled:function () {
     if (App.testMode) {
@@ -143,7 +161,7 @@ App.ClusterController = Em.Controller.extend({
       var nagiosSvc = svcs.findProperty("serviceName", "NAGIOS");
       return nagiosSvc != null;
     }
-  }.property('dataLoadList.services'),
+  }.property('App.router.updateController.isUpdated'),
 
   /**
    * Sorted list of alerts.
@@ -166,19 +184,19 @@ App.ClusterController = Em.Controller.extend({
     return sortedArray;
   }.property('dataLoadList.alerts'),
 
-  loadRuns: function(){
-    if(this.get('postLoadList.runs')){
+  loadRuns:function () {
+    if (this.get('postLoadList.runs')) {
       return;
     }
 
-    var self= this;
+    var self = this;
     var runsUrl = App.testMode ? "/data/apps/runs.json" : App.apiPrefix + "/jobhistory/workflow";
 
     App.HttpClient.get(runsUrl, App.runsMapper, {
       complete:function (jqXHR, textStatus) {
         self.set('postLoadList.runs', true);
       }
-    }, function(){
+    }, function () {
       self.set('postLoadList.runs', true);
     });
   },
@@ -217,19 +235,56 @@ App.ClusterController = Em.Controller.extend({
     }
   }.observes('nagiosUrl'),
 
-  updateStatus: function(){
+  componentsUpdateInterval: App.componentsUpdateInterval,
+
+  /**
+   * Whether we need to update statuses automatically or not
+   */
+  updateStatus: false,
+
+  statusTimeoutId: null,
+
+  loadUpdatedStatusDelayed: function(delay){
+    delay = delay || this.get('componentsUpdateInterval');
+    var self = this;
+
+    this.set('statusTimeoutId',
+      setTimeout(function(){
+        self.loadUpdatedStatus();
+      }, delay)
+    );
+  },
+
+  loadUpdatedStatus: function(){
+
+    var timeoutId = this.get('statusTimeoutId');
+    if(timeoutId){
+      clearTimeout(timeoutId);
+      this.set('statusTimeoutId', null);
+    }
+
+    if(!this.get('updateStatus')){
+      return false;
+    }
+
+    if(!this.get('clusterName')){
+      this.loadUpdatedStatusDelayed(this.get('componentsUpdateInterval')/2, 'error:clusterName');
+      return;
+    }
+    
     var servicesUrl1 = this.getUrl('/data/dashboard/services.json', '/services?ServiceInfo/service_name!=MISCELLANEOUS&ServiceInfo/service_name!=DASHBOARD&fields=*,components/host_components/*');
 
     var self = this;
     App.HttpClient.get(servicesUrl1, App.statusMapper, {
       complete:function (jqXHR, textStatus) {
-        console.log('update finished')
-        setTimeout(function(){
-          self.updateStatus();
-        }, 3000);
+        console.log('Cluster Controller: Updated components statuses successfully!!!')
+        self.loadUpdatedStatusDelayed();
       }
-    }, null);
-  },
+    }, function(){
+      self.loadUpdatedStatusDelayed(null, 'error:response error');
+    });
+
+  }.observes('updateStatus'),
 
   /**
    *
@@ -241,10 +296,12 @@ App.ClusterController = Em.Controller.extend({
       return;
     }
 
+    if(this.get('isLoaded')) { // do not load data repeatedly
+      return;
+    }
+
     var clusterUrl = this.getUrl('/data/clusters/cluster.json', '?fields=Clusters');
     var hostsUrl = this.getUrl('/data/hosts/hosts.json', '/hosts?fields=*');
-    var servicesUrl1 = this.getUrl('/data/dashboard/services.json', '/services?ServiceInfo/service_name!=MISCELLANEOUS&ServiceInfo/service_name!=DASHBOARD&fields=*,components/host_components/*');
-    var servicesUrl2 = this.getUrl('/data/dashboard/serviceComponents.json', '/services?ServiceInfo/service_name!=MISCELLANEOUS&ServiceInfo/service_name!=DASHBOARD&fields=components/ServiceComponentInfo');
     var usersUrl = App.testMode ? '/data/users/users.json' : App.apiPrefix + '/users/?fields=*';
     var racksUrl = "/data/racks/racks.json";
 
@@ -280,125 +337,12 @@ App.ClusterController = Em.Controller.extend({
       self.updateLoadStatus('users');
     });
 
-    //////////////////////////////
-    // Hack for services START  //
-    //////////////////////////////
-    var metricsJson = null;
-    var serviceComponentJson = null;
-    var metricsMapper = {
-      map:function (data) {
-        metricsJson = data;
-      }
-    };
-    var serviceComponentMapper = {
-      map:function (data) {
-        serviceComponentJson = data;
-        if (metricsJson != null && serviceComponentJson != null) {
-          var hdfsSvc1 = null;
-          var hdfsSvc2 = null;
-          var mrSvc1 = null;
-          var mrSvc2 = null;
-          var hbaseSvc1 = null;
-          var hbaseSvc2 = null;
-          metricsJson.items.forEach(function (svc) {
-            if (svc.ServiceInfo.service_name == "HDFS") {
-              hdfsSvc1 = svc;
-            }
-            if (svc.ServiceInfo.service_name == "MAPREDUCE") {
-              mrSvc1 = svc;
-            }
-            if (svc.ServiceInfo.service_name == "HBASE") {
-              hbaseSvc1 = svc;
-            }
-          });
-          serviceComponentJson.items.forEach(function (svc) {
-            if (svc.ServiceInfo.service_name == "HDFS") {
-              hdfsSvc2 = svc;
-            }
-            if (svc.ServiceInfo.service_name == "MAPREDUCE") {
-              mrSvc2 = svc;
-            }
-            if (svc.ServiceInfo.service_name == "HBASE") {
-              hbaseSvc2 = svc;
-            }
-          });
-          var nnC1 = null;
-          var nnC2 = null;
-          var jtC1 = null;
-          var jtC2 = null;
-          var hbm1 = null;
-          var hbm2 = null;
-          if (hdfsSvc1) {
-            hdfsSvc1.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "NAMENODE") {
-                nnC1 = c;
-              }
-            });
-          }
-          if (hdfsSvc2) {
-            hdfsSvc2.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "NAMENODE") {
-                nnC2 = c;
-              }
-            });
-          }
-          if (mrSvc1) {
-            mrSvc1.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "JOBTRACKER") {
-                jtC1 = c;
-              }
-            });
-          }
-          if (mrSvc2) {
-            mrSvc2.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "JOBTRACKER") {
-                jtC2 = c;
-              }
-            });
-          }
-          if (hbaseSvc1) {
-            hbaseSvc1.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "HBASE_MASTER") {
-                hbm1 = c;
-              }
-            });
-          }
-          if (hbaseSvc2) {
-            hbaseSvc2.components.forEach(function (c) {
-              if (c.ServiceComponentInfo.component_name == "HBASE_MASTER") {
-                hbm2 = c;
-              }
-            });
-          }
-          if (nnC1 && nnC2) {
-            nnC1.ServiceComponentInfo = nnC2.ServiceComponentInfo;
-          }
-          if (jtC1 && jtC2) {
-            jtC1.ServiceComponentInfo = jtC2.ServiceComponentInfo;
-          }
-          if (hbm1 && hbm2) {
-            hbm1.ServiceComponentInfo = hbm2.ServiceComponentInfo;
-          }
-          App.servicesMapper.map(metricsJson);
-          self.updateLoadStatus('services');
-        }
-      }
-    }
-    App.HttpClient.get(servicesUrl1, metricsMapper, {
-      complete:function (jqXHR, textStatus) {
-        App.HttpClient.get(servicesUrl2, serviceComponentMapper, {
-          complete:function (jqXHR, textStatus) {
-          }
-        });
-      }
-    });
-    /////////////////////////////
-    // Hack for services END   //
-    /////////////////////////////
+    //TODO: define dependencies and delete next line
+    self.updateLoadStatus('services');
 
     setTimeout(function(){
-      self.updateStatus();
-    }, 8000);
+      self.set('updateStatus', true);
+    }, this.get('componentsUpdateInterval')*2);
 
   },
 

+ 150 - 0
ambari-web/app/controllers/global/update_controller.js

@@ -0,0 +1,150 @@
+/**
+ * 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.UpdateController = Em.Controller.extend({
+  name:'updateController',
+  isUpdated:false,
+  getUrl:function (testUrl, url) {
+    return (App.testMode) ? testUrl : App.apiPrefix + '/clusters/' + this.get('clusterName') + url;
+  },
+
+  updateServiceMetric:function(){
+
+    var servicesUrl1 = this.getUrl('/data/dashboard/services.json', '/services?ServiceInfo/service_name!=MISCELLANEOUS&ServiceInfo/service_name!=DASHBOARD&fields=*,components/host_components/*');
+    var servicesUrl2 = this.getUrl('/data/dashboard/serviceComponents.json', '/services?ServiceInfo/service_name!=MISCELLANEOUS&ServiceInfo/service_name!=DASHBOARD&fields=components/ServiceComponentInfo');
+
+
+    self = this;
+    this.set("isUpdated", false);
+
+    var metricsJson = null;
+    var serviceComponentJson = null;
+    var metricsMapper = {
+      map:function (data) {
+        metricsJson = data;
+      }
+    };
+    var serviceComponentMapper = {
+      map:function (data) {
+        serviceComponentJson = data;
+        if (metricsJson != null && serviceComponentJson != null) {
+          var hdfsSvc1 = null;
+          var hdfsSvc2 = null;
+          var mrSvc1 = null;
+          var mrSvc2 = null;
+          var hbaseSvc1 = null;
+          var hbaseSvc2 = null;
+          metricsJson.items.forEach(function (svc) {
+            if (svc.ServiceInfo.service_name == "HDFS") {
+              hdfsSvc1 = svc;
+            }
+            if (svc.ServiceInfo.service_name == "MAPREDUCE") {
+              mrSvc1 = svc;
+            }
+            if (svc.ServiceInfo.service_name == "HBASE") {
+              hbaseSvc1 = svc;
+            }
+          });
+          serviceComponentJson.items.forEach(function (svc) {
+            if (svc.ServiceInfo.service_name == "HDFS") {
+              hdfsSvc2 = svc;
+            }
+            if (svc.ServiceInfo.service_name == "MAPREDUCE") {
+              mrSvc2 = svc;
+            }
+            if (svc.ServiceInfo.service_name == "HBASE") {
+              hbaseSvc2 = svc;
+            }
+          });
+          var nnC1 = null;
+          var nnC2 = null;
+          var jtC1 = null;
+          var jtC2 = null;
+          var hbm1 = null;
+          var hbm2 = null;
+          if (hdfsSvc1) {
+            hdfsSvc1.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "NAMENODE") {
+                nnC1 = c;
+              }
+            });
+          }
+          if (hdfsSvc2) {
+            hdfsSvc2.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "NAMENODE") {
+                nnC2 = c;
+              }
+            });
+          }
+          if (mrSvc1) {
+            mrSvc1.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "JOBTRACKER") {
+                jtC1 = c;
+              }
+            });
+          }
+          if (mrSvc2) {
+            mrSvc2.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "JOBTRACKER") {
+                jtC2 = c;
+              }
+            });
+          }
+          if (hbaseSvc1) {
+            hbaseSvc1.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "HBASE_MASTER") {
+                hbm1 = c;
+              }
+            });
+          }
+          if (hbaseSvc2) {
+            hbaseSvc2.components.forEach(function (c) {
+              if (c.ServiceComponentInfo.component_name == "HBASE_MASTER") {
+                hbm2 = c;
+              }
+            });
+          }
+          if (nnC1 && nnC2) {
+            nnC1.ServiceComponentInfo = nnC2.ServiceComponentInfo;
+          }
+          if (jtC1 && jtC2) {
+            jtC1.ServiceComponentInfo = jtC2.ServiceComponentInfo;
+          }
+          if (hbm1 && hbm2) {
+            hbm1.ServiceComponentInfo = hbm2.ServiceComponentInfo;
+          }
+          App.updateMapper.map(metricsJson);
+
+        }
+      }
+    }
+    App.HttpClient.get(servicesUrl1, metricsMapper, {
+      complete:function (jqXHR, textStatus) {
+        App.HttpClient.get(servicesUrl2, serviceComponentMapper, {
+          complete:function (jqXHR, textStatus) {
+            self.set("isUpdated", true);
+          }
+        });
+      }
+    });
+  }
+
+
+});

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

@@ -842,6 +842,7 @@ App.InstallerController = Em.Controller.extend({
     App.db.setMasterComponentHosts(undefined);
     App.db.setSlaveComponentHosts(undefined);
     App.db.setClusterStatus(undefined);
+    App.db.setAllHostNames(undefined);
   }
 
 });

+ 1 - 0
ambari-web/app/controllers/login_controller.js

@@ -27,6 +27,7 @@ App.LoginController = Em.Object.extend({
 
   errorMessage: '',
 
+
   submit: function (e) {
     this.set('errorMessage', '');
 

+ 7 - 0
ambari-web/app/controllers/main.js

@@ -30,6 +30,13 @@ App.MainController = Em.Controller.extend({
   initialize: function(){
     this.startLoadOperationsPeriodically();
     App.router.get('clusterController').loadClusterData();
+    App.router.get('updateController').updateServiceMetric();
+    setInterval(
+      this.updateServiceMetric
+    , App.services_update);
+  },
+  updateServiceMetric: function(){
+    App.router.get('updateController').updateServiceMetric();
   },
   startLoadOperationsPeriodically: function() {
       App.router.get('backgroundOperationsController').set('isWorking', true);

+ 3 - 3
ambari-web/app/controllers/main/host.js

@@ -18,9 +18,6 @@
 
 var App = require('app');
 var validator = require('utils/validator');
-require('models/service');
-require('models/cluster');
-require('models/host');
 
 App.MainHostController = Em.ArrayController.extend(App.Pagination, {
   name:'mainHostController',
@@ -28,6 +25,9 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
   fullContent:App.Host.find(),
   clusters:App.Cluster.find(),
   //componentsForFilter: App.Component.find(),
+  isAdmin: function(){
+    return App.db.getUser().admin;
+  }.property('App.router.loginController.loginName'),
   componentsForFilter:function() {
     var components = App.Component.find();
     ret = new Array();

+ 61 - 88
ambari-web/app/controllers/main/host/add_controller.js

@@ -43,7 +43,8 @@ App.AddHostController = Em.Controller.extend({
     masterComponentHosts: null,
     serviceConfigProperties: null,
     advancedServiceConfig: null,
-    controllerName: 'addHostController'
+    controllerName: 'addHostController',
+    isWizard: true
   }),
 
   /**
@@ -127,18 +128,6 @@ App.AddHostController = Em.Controller.extend({
     return this.get('currentStep') == 7;
   }.property('currentStep'),
 
-  isStep8: function () {
-    return this.get('currentStep') == 8;
-  }.property('currentStep'),
-
-  isStep9: function () {
-    return this.get('currentStep') == 9;
-  }.property('currentStep'),
-
-  isStep10: function () {
-    return this.get('currentStep') == 10;
-  }.property('currentStep'),
-
   gotoStep: function (step) {
     if (this.get('isStepDisabled').findProperty('step', step).get('value') === false) {
       App.router.send('gotoStep' + step);
@@ -173,27 +162,22 @@ App.AddHostController = Em.Controller.extend({
     this.gotoStep(7);
   },
 
-  gotoStep8: function () {
-    this.gotoStep(8);
-  },
-
-  gotoStep9: function () {
-    this.gotoStep(9);
-  },
-
-  gotoStep10: function () {
-    this.gotoStep(10);
-  },
-
   /**
    * Load clusterInfo(step1) to model
    */
   loadClusterInfo: function(){
-    var cluster = {
-      name: App.router.getClusterName(),
-      status: "",
-      isCompleted: true
-    };
+    var cluster = App.db.getClusterStatus();
+    if(!cluster){
+      cluster = {
+        name: App.router.getClusterName(),
+        status: undefined,
+        isCompleted: false,
+        requestId: undefined,
+        installStartTime: undefined,
+        installTime: undefined
+      };
+      App.db.setClusterStatus(cluster);
+    }
     this.set('content.cluster', cluster);
     console.log("AddHostController:loadClusterInfo: loaded data ", cluster);
   },
@@ -306,16 +290,6 @@ App.AddHostController = Em.Controller.extend({
   saveConfirmedHosts: function (stepController) {
     var hostInfo = {};
 
-    App.Host.find().forEach(function(_host){
-      hostInfo[_host.get('id')] = {
-        name: _host.get('hostName'),
-        cpu: _host.get('cpu'),
-        memory: _host.get('memory'),
-        bootStatus: 'success',
-        isInstalled: true
-      };
-    });
-
     stepController.get('content.hostsInfo').forEach(function (_host) {
       hostInfo[_host.name] = {
         name: _host.name,
@@ -349,10 +323,11 @@ App.AddHostController = Em.Controller.extend({
     var hostInfo = App.db.getHosts();
 
     for (var index in hostInfo) {
-      hostInfo[index].status = "pending";
       var host = hosts.findProperty('name', hostInfo[index].name);
       if (host) {
         hostInfo[index].status = host.status;
+        hostInfo[index].logTasks = host.logTasks;
+        hostInfo[index].tasks = host.tasks;
         hostInfo[index].message = host.message;
         hostInfo[index].progress = host.progress;
       }
@@ -385,48 +360,22 @@ App.AddHostController = Em.Controller.extend({
    */
   loadServices: function () {
     var servicesInfo = App.db.getService();
+    if(!servicesInfo || !servicesInfo.length){
+      servicesInfo = require('data/mock/services').slice(0);
+      servicesInfo.forEach(function (item) {
+        item.isSelected = App.Service.find().someProperty('id', item.serviceName)
+        item.isInstalled = item.isSelected;
+        item.isDisabled = item.isSelected;
+      });
+      App.db.setService(servicesInfo);
+    }
+
     servicesInfo.forEach(function (item, index) {
       servicesInfo[index] = Em.Object.create(item);
     });
     this.set('content.services', servicesInfo);
-    console.log('addHostController.loadServices: loaded data ', servicesInfo);
-    console.log('selected services ', servicesInfo.filterProperty('isSelected', true).mapProperty('serviceName'));
-  },
-
-  /**
-   * Save data to model
-   * @param stepController App.WizardStep4Controller
-   */
-  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);
-    });
-    App.db.setSelectedServiceNames(serviceNames);
-    console.log('addHostController.saveServices: saved data ', serviceNames);
-  },
-
-  /**
-   * Save Master Component Hosts data to Main Controller
-   * @param stepController App.WizardStep5Controller
-   */
-  saveMasterComponentHosts: function (stepController) {
-    var obj = stepController.get('selectedServicesMasters');
-    var masterComponentHosts = [];
-    obj.forEach(function (_component) {
-      masterComponentHosts.push({
-        display_name: _component.display_name,
-        component: _component.component_name,
-        hostName: _component.selectedHost
-      });
-    });
-
-    console.log("AddHostController.saveComponentHosts: saved hosts ", masterComponentHosts);
-    App.db.setMasterComponentHosts(masterComponentHosts);
-    this.set('content.masterComponentHosts', masterComponentHosts);
+    console.log('AddHostController.loadServices: loaded data ', servicesInfo);
+    console.log('selected services ', servicesInfo.filterProperty('isSelected', true).filterProperty('isDisabled', false).mapProperty('serviceName'));
   },
 
   /**
@@ -434,6 +383,19 @@ App.AddHostController = Em.Controller.extend({
    */
   loadMasterComponentHosts: function () {
     var masterComponentHosts = App.db.getMasterComponentHosts();
+    if (!masterComponentHosts) {
+      masterComponentHosts = [];
+      App.Component.find().filterProperty('isMaster', true).forEach(function(item){
+        masterComponentHosts.push({
+          component: item.get('componentName'),
+          hostName: item.get('host.hostName'),
+          isInstalled: true,
+          serviceId: item.get('service.id'),
+          display_name: item.get('displayName')
+        })
+      });
+      App.db.setMasterComponentHosts(masterComponentHosts);
+    }
     this.set("content.masterComponentHosts", masterComponentHosts);
     console.log("AddHostController.loadMasterComponentHosts: loaded hosts ", masterComponentHosts);
   },
@@ -632,17 +594,27 @@ App.AddHostController = Em.Controller.extend({
     this.set('content.clients', clients);
     console.log("AddHostController.loadClients: loaded list ", clients);
   },
-
+  dataLoading: function(){
+    var dfd = $.Deferred();
+    this.connectOutlet('loading');
+    var interval = setInterval(function(){
+      if (App.router.get('clusterController.isLoaded')){
+        dfd.resolve();
+        clearInterval(interval);
+      }
+    },50);
+    return dfd.promise();
+  },
   /**
    * Generate clients list for selected services and save it to model
    * @param stepController step4WizardController
    */
-  saveClients: function(stepController){
+  saveClients: function(){
     var clients = [];
     var serviceComponents = require('data/service_components');
     var hostComponents = App.HostComponent.find();
 
-    stepController.get('content').filterProperty('isSelected',true).forEach(function (_service) {
+    this.get('content.services').filterProperty('isSelected',true).forEach(function (_service) {
       var client = serviceComponents.filterProperty('service_name', _service.serviceName).findProperty('isClient', true);
       if (client) {
         clients.pushObject({
@@ -667,17 +639,17 @@ App.AddHostController = Em.Controller.extend({
       case '8':
       case '7':
       case '6':
-        this.loadServiceConfigProperties();
       case '5':
-        this.loadClients();
       case '4':
-        this.loadMasterComponentHosts();
-        this.loadSlaveComponentHosts();
-        this.loadConfirmedHosts();
+        this.loadServiceConfigProperties();
       case '3':
         this.loadClients();
         this.loadServices();
+        this.loadMasterComponentHosts();
+        this.loadSlaveComponentHosts();
+        this.loadConfirmedHosts();
       case '2':
+        this.loadServices();
         this.loadConfirmedHosts();
       case '1':
         this.loadInstallOptions();
@@ -687,7 +659,7 @@ App.AddHostController = Em.Controller.extend({
   },
 
   loadAdvancedConfigs: function () {
-    App.db.getSelectedServiceNames().forEach(function (_serviceName) {
+    this.get('content.services').filterProperty('isSelected', true).mapProperty('serviceName').forEach(function (_serviceName) {
       this.loadAdvancedConfig(_serviceName);
     }, this);
   },
@@ -814,6 +786,7 @@ App.AddHostController = Em.Controller.extend({
     App.db.setHosts(undefined);
     App.db.setMasterComponentHosts(undefined);
     App.db.setSlaveComponentHosts(undefined);
+    App.db.setClusterStatus(undefined);
   }
 
 });

+ 10 - 5
ambari-web/app/controllers/main/host/details.js

@@ -22,7 +22,9 @@ App.MainHostDetailsController = Em.Controller.extend({
   name: 'mainHostDetailsController',
   content: null,
   isFromHosts: false,
-
+  isAdmin: function(){
+    return App.db.getUser().admin;
+  }.property('App.router.loginController.loginName'),
   routeHome:function () {
     App.router.transitionTo('main.dashboard');
   },
@@ -89,13 +91,14 @@ App.MainHostDetailsController = Em.Controller.extend({
           }
 
           console.log('Send request for STARTING successfully');
-          component.set('workStatus', App.Component.Status.starting);
 
           if(App.testMode){
+            component.set('workStatus', App.Component.Status.starting);
             setTimeout(function(){
               component.set('workStatus', App.Component.Status.started);
             },10000);
           } else{
+            App.router.get('clusterController').loadUpdatedStatus();
             App.router.get('backgroundOperationsController.eventsArray').push({
               "when" : function(controller){
                 var result = (controller.getOperationsForRequestId(requestId).length == 0);
@@ -103,7 +106,7 @@ App.MainHostDetailsController = Em.Controller.extend({
                 return result;
               },
               "do" : function(){
-                component.set('workStatus', App.Component.Status.started);
+                App.router.get('clusterController').loadUpdatedStatus();
               }
             });
           }
@@ -140,13 +143,15 @@ App.MainHostDetailsController = Em.Controller.extend({
 
           console.log('Send request for STOPPING successfully');
 
-          component.set('workStatus', App.Component.Status.stopping);
+
 
           if(App.testMode){
+            component.set('workStatus', App.Component.Status.stopping);
             setTimeout(function(){
               component.set('workStatus', App.Component.Status.stopped);
             },10000);
           } else{
+            App.router.get('clusterController').loadUpdatedStatus();
             App.router.get('backgroundOperationsController.eventsArray').push({
               "when" : function(controller){
                 var result = (controller.getOperationsForRequestId(requestId).length == 0);
@@ -154,7 +159,7 @@ App.MainHostDetailsController = Em.Controller.extend({
                 return result;
               },
               "do" : function(){
-                component.set('workStatus', App.Component.Status.stopped);
+                App.router.get('clusterController').loadUpdatedStatus();
               }
             });
           }

+ 4 - 1
ambari-web/app/controllers/main/service.js

@@ -21,5 +21,8 @@ var App = require('app');
 App.MainServiceController = Em.ArrayController.extend({
   name:'mainServiceController',
   content: App.Service.find(),
-  additionalMenuItem:Em.Object.create({ id:'Clients', displayName:'Clients', isClients:true })
+  additionalMenuItem:Em.Object.create({ id:'Clients', displayName:'Clients', isClients:true }),
+  isAdmin: function(){
+    return App.db.getUser().admin;
+  }.property('App.router.loginController.loginName')
 })

+ 8 - 7
ambari-web/app/controllers/main/service/add_controller.js

@@ -345,13 +345,13 @@ App.AddServiceController = Em.Controller.extend({
     var masterComponentHosts = App.db.getMasterComponentHosts();
     if(!masterComponentHosts){
       masterComponentHosts = [];
-    App.Component.find().filterProperty('isMaster', true).forEach(function(item){
-      masterComponentHosts.push({
-        component: item.get('componentName'),
-        hostName: item.get('host.hostName'),
-        isInstalled: true
-      })
-    });
+      App.Component.find().filterProperty('isMaster', true).forEach(function(item){
+        masterComponentHosts.push({
+          component: item.get('componentName'),
+          hostName: item.get('host.hostName'),
+          isInstalled: true
+        })
+      });
 
     }
     this.set("content.masterComponentHosts", masterComponentHosts);
@@ -756,6 +756,7 @@ App.AddServiceController = Em.Controller.extend({
     App.db.setMasterComponentHosts(undefined);
     App.db.setSlaveComponentHosts(undefined);
     App.db.setClusterStatus(undefined);
+    App.db.setAllHostNames(undefined);
   }
 
 });

+ 9 - 2
ambari-web/app/controllers/main/service/info/configs.js

@@ -74,7 +74,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({
     }, this)
     //STEP 5: Add the advanced configs to the serviceConfigs property
 
-    var advancedConfig = App.router.get('installerController').loadAdvancedConfig(this.get('content.serviceName'));
+    var advancedConfig = App.router.get('installerController').loadAdvancedConfig(this.get('content.serviceName')) || [];
     var service = this.get('serviceConfigs').findProperty('serviceName', this.get('content.serviceName'));
     advancedConfig.forEach(function (_config) {
       if (service) {
@@ -99,6 +99,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({
         }
       }
     }, this);
+
     this.loadCustomConfig();
 
     this.renderServiceConfigs(this.get('serviceConfigs'));
@@ -266,6 +267,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({
    * @param serviceConfigs
    */
   renderServiceConfigs: function (serviceConfigs) {
+
     serviceConfigs.forEach(function (_serviceConfig) {
       var serviceConfig = App.ServiceConfig.create({
         filename: _serviceConfig.filename,
@@ -276,6 +278,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({
       });
 
       if (this.get('content.serviceName') && this.get('content.serviceName').toUpperCase() === serviceConfig.serviceName) {
+
         this.loadComponentConfigs(_serviceConfig, serviceConfig);
 
         console.log('pushing ' + serviceConfig.serviceName);
@@ -294,14 +297,18 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({
    * @param _componentConfig
    * @param componentConfig
    */
-  loadComponentConfigs: function (_componentConfig, componentConfig) {
+  loadComponentConfigs: function (_componentConfig, componentConfig) {debugger;
     _componentConfig.configs.forEach(function (_serviceConfigProperty) {
+      console.log("config", _serviceConfigProperty);
+      if(!_serviceConfigProperty) return;
       var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
       serviceConfigProperty.serviceConfig = componentConfig;
       this.initialValue(serviceConfigProperty);
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.validate();
+      console.log("config result", serviceConfigProperty);
     }, this);
+    console.log("+++++++++++++++++++++++++++++++++++++++++");
   },
 
   restartServicePopup: function (event) {

+ 10 - 6
ambari-web/app/controllers/main/service/item.js

@@ -20,7 +20,9 @@ var App = require('app');
 
 App.MainServiceItemController = Em.Controller.extend({
   name: 'mainServiceItemController',
-
+  isAdmin: function(){
+    return App.db.getUser().admin;
+  }.property('App.router.loginController.loginName'),
   /**
    * Send specific command to server
    * @param url
@@ -83,14 +85,15 @@ App.MainServiceItemController = Em.Controller.extend({
           if (!requestId) {
             return;
           }
-
-          self.content.set('workStatus', App.Service.Health.starting);
           console.log('Send request for STARTING successfully');
+
           if (App.testMode) {
+            self.content.set('workStatus', App.Service.Health.starting);
             setTimeout(function () {
               self.content.set('workStatus', App.Service.Health.live);
             }, 10000);
           } else {
+            App.router.get('clusterController').loadUpdatedStatus();
             App.router.get('backgroundOperationsController.eventsArray').push({
               "when": function (controller) {
                 var result = (controller.getOperationsForRequestId(requestId).length == 0);
@@ -98,7 +101,7 @@ App.MainServiceItemController = Em.Controller.extend({
                 return result;
               },
               "do": function () {
-                self.content.set('workStatus', App.Service.Health.live);
+                App.router.get('clusterController').loadUpdatedStatus();
               }
             });
           }
@@ -137,12 +140,13 @@ App.MainServiceItemController = Em.Controller.extend({
             return
           }
           console.log('Send request for STOPPING successfully');
-          self.content.set('workStatus', App.Service.Health.stopping);
           if (App.testMode) {
+            self.content.set('workStatus', App.Service.Health.stopping);
             setTimeout(function () {
               self.content.set('workStatus', App.Service.Health.dead);
             }, 10000);
           } else {
+            App.router.get('clusterController').loadUpdatedStatus();
             App.router.get('backgroundOperationsController.eventsArray').push({
               "when": function (controller) {
                 var result = (controller.getOperationsForRequestId(requestId).length == 0);
@@ -150,7 +154,7 @@ App.MainServiceItemController = Em.Controller.extend({
                 return result;
               },
               "do": function () {
-                self.content.set('workStatus', App.Service.Health.dead);
+                App.router.get('clusterController').loadUpdatedStatus();
               }
             });
           }

+ 62 - 50
ambari-web/app/controllers/wizard/step5_controller.js

@@ -20,30 +20,30 @@ var App = require('app');
 
 App.WizardStep5Controller = Em.Controller.extend({
 
-  name: "wizardStep5Controller",
+  name:"wizardStep5Controller",
 
-  hosts: [],
+  hosts:[],
 
-  selectedServices: [],
-  selectedServicesMasters: [],
-  zId: 0,
+  selectedServices:[],
+  selectedServicesMasters:[],
+  zId:0,
 
-  components: require('data/service_components'),
+  components:require('data/service_components'),
 
-  clearStep: function () {
+  clearStep:function () {
     this.set('hosts', []);
     this.set('selectedServices', []);
     this.set('selectedServicesMasters', []);
     this.set('zId', 0);
   },
 
-  loadStep: function () {
+  loadStep:function () {
     console.log("WizardStep5Controller: Loading step5: Assign Masters");
     this.clearStep();
     this.renderHostInfo();
     this.renderComponents(this.loadComponents());
 
-    if(!this.get("selectedServicesMasters").filterProperty('isInstalled', false).length){
+    if (!this.get("selectedServicesMasters").filterProperty('isInstalled', false).length) {
       console.log('no master components to add');
       App.router.send('next');
     }
@@ -52,19 +52,32 @@ App.WizardStep5Controller = Em.Controller.extend({
   /**
    * Load active host list to <code>hosts</code> variable
    */
-  renderHostInfo: function () {
+  renderHostInfo:function () {
 
     var hostInfo = this.get('content.hostsInfo');
 
     for (var index in hostInfo) {
       var _host = hostInfo[index];
       if (_host.bootStatus === 'success' || true) {  // TODO: remove "true" after integrating with bootstrap
-
         var hostObj = Ember.Object.create({
-          host_name: _host.name,
-          cpu: _host.cpu,
-          memory: _host.memory,
-          host_info: "%@ (%@, %@ cores)".fmt(_host.name, (_host.memory * 1024).bytesToSize(1, 'parseFloat'), _host.cpu)
+          host_name:_host.name,
+
+          cpu:_host.cpu,
+          memory:_host.memory,
+          host_info:"%@ (%@, %@ cores)".fmt(_host.name, (_host.memory * 1024).bytesToSize(1, 'parseFloat'), _host.cpu)
+
+//          Uncomment to test sorting with random cpu, memory, host_info
+//          cpu:function () {
+//            return parseInt(2 + Math.random() * 4);
+//          }.property(),
+//          memory:function () {
+//            return parseInt((Math.random() * 4000000000) + 4000000000);
+//          }.property(),
+//
+//          host_info:function () {
+//            return "%@ (%@, %@ cores)".fmt(this.get('host_name'), (this.get('memory') * 1024).bytesToSize(1, 'parseFloat'), this.get('cpu'));
+//          }.property('cpu', 'memory')
+
         });
 
         this.get("hosts").pushObject(hostObj);
@@ -76,13 +89,13 @@ App.WizardStep5Controller = Em.Controller.extend({
    * Load services info to appropriate variable and return masterComponentHosts
    * @return {Ember.Set}
    */
-  loadComponents: function () {
+  loadComponents:function () {
 
     var services = this.get('content.services')
       .filterProperty('isSelected', true).mapProperty('serviceName'); //list of shown services
 
     services.forEach(function (item) {
-      this.get("selectedServices").pushObject(Ember.Object.create({service_name: item}));
+      this.get("selectedServices").pushObject(Ember.Object.create({service_name:item}));
     }, this);
 
     var masterHosts = this.get('content.masterComponentHosts'); //saved to local storadge info
@@ -97,11 +110,11 @@ App.WizardStep5Controller = Em.Controller.extend({
 
       componentInfo.forEach(function (_componentInfo) {
 
-        if(_componentInfo.component_name == 'ZOOKEEPER_SERVER'){
+        if (_componentInfo.component_name == 'ZOOKEEPER_SERVER') {
           var savedComponents = masterHosts.filterProperty('component', _componentInfo.component_name);
-          if(savedComponents.length){
+          if (savedComponents.length) {
 
-            savedComponents.forEach(function(item){
+            savedComponents.forEach(function (item) {
               var zooKeeperHost = {};
               zooKeeperHost.display_name = _componentInfo.display_name;
               zooKeeperHost.component_name = _componentInfo.component_name;
@@ -112,7 +125,7 @@ App.WizardStep5Controller = Em.Controller.extend({
               resultComponents.add(zooKeeperHost);
             })
 
-          } else{
+          } else {
 
             var zooHosts = this.selectHost(_componentInfo.component_name);
             zooHosts.forEach(function (_host) {
@@ -148,7 +161,7 @@ App.WizardStep5Controller = Em.Controller.extend({
    * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
    * @param masterComponents
    */
-  renderComponents: function (masterComponents) {
+  renderComponents:function (masterComponents) {
     var zookeeperComponent = null, componentObj = null;
     var services = this.get('selectedServicesMasters').slice(0);
     if (services.length) {
@@ -180,7 +193,7 @@ App.WizardStep5Controller = Em.Controller.extend({
 
   },
 
-  getKerberosServer: function (noOfHosts) {
+  getKerberosServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -195,12 +208,12 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getNameNode: function (noOfHosts) {
+  getNameNode:function (noOfHosts) {
     var hosts = this.get('hosts');
     return hosts[0];
   },
 
-  getSNameNode: function (noOfHosts) {
+  getSNameNode:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -209,7 +222,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getJobTracker: function (noOfHosts) {
+  getJobTracker:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -224,7 +237,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getHBaseMaster: function (noOfHosts) {
+  getHBaseMaster:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -239,7 +252,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getOozieServer: function (noOfHosts) {
+  getOozieServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -254,7 +267,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getOozieServer: function (noOfHosts) {
+  getOozieServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -269,7 +282,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getHiveServer: function (noOfHosts) {
+  getHiveServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -284,7 +297,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getTempletonServer: function (noOfHosts) {
+  getTempletonServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts === 1) {
       return hosts[0];
@@ -299,7 +312,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getZooKeeperServer: function (noOfHosts) {
+  getZooKeeperServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     if (noOfHosts < 3) {
       return [hosts[0].host_name];
@@ -308,7 +321,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getGangliaServer: function (noOfHosts) {
+  getGangliaServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     var hostnames = [];
     var inc = 0;
@@ -324,7 +337,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  getNagiosServer: function (noOfHosts) {
+  getNagiosServer:function (noOfHosts) {
     var hosts = this.get('hosts');
     var hostnames = [];
     var inc = 0;
@@ -346,7 +359,7 @@ App.WizardStep5Controller = Em.Controller.extend({
    * @param componentName
    * @return {*}
    */
-  selectHost: function (componentName) {
+  selectHost:function (componentName) {
     var noOfHosts = this.get('hosts').length;
     if (componentName === 'KERBEROS_SERVER') {
       return this.getKerberosServer(noOfHosts).host_name;
@@ -373,8 +386,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-
-  masterHostMapping: function () {
+  masterHostMapping:function () {
     var mapping = [], mappingObject, self = this, mappedHosts, hostObj, hostInfo;
     //get the unique assigned hosts and find the master services assigned to them
 
@@ -385,9 +397,9 @@ App.WizardStep5Controller = Em.Controller.extend({
       console.log("Name of the host is: " + hostObj.host_name);
 
       mappingObject = Ember.Object.create({
-        host_name: item,
-        hostInfo: hostObj.host_info,
-        masterServices: self.get("selectedServicesMasters").filterProperty("selectedHost", item)
+        host_name:item,
+        hostInfo:hostObj.host_info,
+        masterServices:self.get("selectedServicesMasters").filterProperty("selectedHost", item)
       });
 
       mapping.pushObject(mappingObject);
@@ -399,16 +411,16 @@ App.WizardStep5Controller = Em.Controller.extend({
 
   }.property("selectedServicesMasters.@each.selectedHost"),
 
-  remainingHosts: function () {
+  remainingHosts:function () {
     return (this.get("hosts.length") - this.get("masterHostMapping.length"));
   }.property("selectedServicesMasters.@each.selectedHost"),
 
-  hasZookeeper: function () {
+  hasZookeeper:function () {
     return this.selectedServices.findProperty("service_name", "ZooKeeper");
   }.property("selectedServices"),
 
   //methods
-  getAvailableHosts: function (componentName) {
+  getAvailableHosts:function (componentName) {
     var assignableHosts = [],
       zookeeperHosts = null;
 
@@ -426,7 +438,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  assignHostToMaster: function (masterService, selectedHost, zId) {
+  assignHostToMaster:function (masterService, selectedHost, zId) {
     if (selectedHost && masterService) {
       if ((masterService === "ZooKeeper") && zId) {
         this.get('selectedServicesMasters').findProperty("zId", zId).set("selectedHost", selectedHost);
@@ -439,7 +451,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  lastZooKeeper: function () {
+  lastZooKeeper:function () {
     var currentZooKeepers = this.get("selectedServicesMasters").filterProperty("display_name", "ZooKeeper");
     if (currentZooKeepers) {
       return currentZooKeepers.get("lastObject");
@@ -448,7 +460,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     return null;
   },
 
-  addZookeepers: function () {
+  addZookeepers:function () {
     /*
      *Logic: If ZooKeeper service is selected then there can be
      * minimum 1 ZooKeeper master in total, and
@@ -513,7 +525,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     return false;//if no more zookeepers can be added
   },
 
-  removeZookeepers: function (zId) {
+  removeZookeepers:function (zId) {
     var currentZooKeepers;
 
     //work only if the Zookeeper service is selected in previous step
@@ -544,7 +556,7 @@ App.WizardStep5Controller = Em.Controller.extend({
 
   },
 
-  rebalanceZookeeperHosts: function () {
+  rebalanceZookeeperHosts:function () {
     //for a zookeeper update the available hosts for the other zookeepers
 
     var currentZooKeepers = this.get("selectedServicesMasters").filterProperty("display_name", "ZooKeeper"),
@@ -567,7 +579,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }, this);
   },
 
-  sortHostsByConfig: function (a, b) {
+  sortHostsByConfig:function (a, b) {
     //currently handling only total memory on the host
     if (a.memory < b.memory) {
       return 1;
@@ -577,7 +589,7 @@ App.WizardStep5Controller = Em.Controller.extend({
     }
   },
 
-  sortHostsByName: function (a, b) {
+  sortHostsByName:function (a, b) {
     if (a.host_name > b.host_name) {
       return 1;
     }

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

@@ -303,6 +303,10 @@ App.WizardStep6Controller = Em.Controller.extend({
       || ( this.get('isMrSelected') && this.get('isNoTaskTrackers'))
       || ( this.get('isHbSelected') && this.get('isNoRegionServers'));
 
+    if(this.get('content.isWizard')){
+      isError = false;
+    }
+
     if (isError) {
       this.set('errorMessage', Ember.I18n.t('installer.step6.error.mustSelectOne'));
     }

+ 80 - 29
ambari-web/app/controllers/wizard/step8_controller.js

@@ -651,6 +651,7 @@ App.WizardStep8Controller = Em.Controller.extend({
 
   },
 
+  // TODO: aggregate create calls.  doesn't seem like backend supports creating multiple services at the same time yet.
   createSelectedServices: function () {
     var services = this.get('selectedServices').mapProperty('serviceName');
     services.forEach(function (_service) {
@@ -690,39 +691,41 @@ App.WizardStep8Controller = Em.Controller.extend({
     var services = this.get('selectedServices').mapProperty('serviceName');
     services.forEach(function (_service) {
       var components = serviceComponents.filterProperty('service_name', _service);
-      components.forEach(function (_component) {
-        this.createComponent(_service, _component.component_name);
-      }, this);
+      var componentsData = components.map(function(_component) {
+        return { "ServiceComponentInfo": { "component_name": _component.component_name } };
+      });
+      var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
+      // Service must be specified in terms of a query for creating multiple components at the same time.
+      // See AMBARI-1018.
+      var url = App.apiPrefix + '/clusters/' + clusterName + '/services?ServiceInfo/service_name=' + _service;
+      var data = {
+        "components": componentsData
+      }
+      $.ajax({
+        type: 'POST',
+        url: url,
+        async: false,
+        dataType: 'text',
+        data: JSON.stringify(data),
+        timeout: App.timeout,
+        success: function (data) {
+          var jsonData = jQuery.parseJSON(data);
+          console.log("TRACE: STep8 -> In success function for createComponent");
+          console.log("TRACE: STep8 -> value of the url is: " + url);
+          console.log("TRACE: STep8 -> value of the received data is: " + jsonData);
+        },
+
+        error: function (request, ajaxOptions, error) {
+          console.log('Step8: In Error ');
+          console.log('Step8: Error message is: ' + request.responseText);
+        },
+
+        statusCode: require('data/statusCodes')
+      });
     }, this);
 
   },
 
-  createComponent: function (service, component) {
-    var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
-    var url = App.apiPrefix + '/clusters/' + clusterName + '/services/' + service + '/components/' + component;
-    $.ajax({
-      type: 'POST',
-      url: url,
-      async: false,
-      dataType: 'text',
-      timeout: App.timeout,
-      success: function (data) {
-        var jsonData = jQuery.parseJSON(data);
-        console.log("TRACE: STep8 -> In success function for createComponent");
-        console.log("TRACE: STep8 -> value of the url is: " + url);
-        console.log("TRACE: STep8 -> value of the received data is: " + jsonData);
-
-      },
-
-      error: function (request, ajaxOptions, error) {
-        console.log('Step8: In Error ');
-        console.log('Step8: Error message is: ' + request.responseText);
-      },
-
-      statusCode: require('data/statusCodes')
-    });
-  },
-
   registerHostsToCluster: function() {
     var allHosts = this.get('content.hostsInfo');
     for(var hostName in allHosts){
@@ -765,11 +768,59 @@ App.WizardStep8Controller = Em.Controller.extend({
     var slaveHosts = this.get('content.slaveComponentHosts');
     var clients = this.get('content.clients');
     var allHosts = this.get('content.hostsInfo');
+    var slaveClient = slaveHosts.filterProperty('componentName', "CLIENT").objectAt(0);
 
     masterHosts.forEach(function (_masterHost) {
       this.createHostComponent(_masterHost);
     }, this);
 
+    masterHosts.filterProperty('component', 'HBASE_MASTER').filterProperty('isInstalled', false).forEach(function (_masterHost) {
+      var hosts = slaveClient.hosts.filterProperty("hostName", _masterHost.hostName);
+      if (!hosts.length) {
+        var slaveObj = {};
+        slaveObj.component = "HDFS_CLIENT";
+        slaveObj.hostName = _masterHost.hostName;
+        slaveObj.isInstalled = false;
+        this.createHostComponent(slaveObj);
+      }
+    }, this);
+
+    masterHosts.filterProperty('component', 'HIVE_SERVER').filterProperty('isInstalled', false).forEach(function (_masterHost) {
+      var hosts = slaveClient.hosts.filterProperty("hostName", _masterHost.hostName);
+      if (!hosts.length) {
+        var slaveObj = {};
+        slaveObj.component = "MAPREDUCE_CLIENT";
+        slaveObj.hostName = _masterHost.hostName;
+        slaveObj.isInstalled = false;
+        this.createHostComponent(slaveObj);
+      }
+    }, this);
+
+    masterHosts.filterProperty('component', 'OOZIE_SERVER').filterProperty('isInstalled', false).forEach(function (_masterHost) {
+      var hosts = slaveClient.hosts.filterProperty("hostName", _masterHost.hostName);
+      if (!hosts.length) {
+        var slaveObj = {};
+        slaveObj.component = "MAPREDUCE_CLIENT";
+        slaveObj.hostName = _masterHost.hostName;
+        slaveObj.isInstalled = false;
+        this.createHostComponent(slaveObj);
+      }
+    }, this);
+
+
+    slaveHosts.filterProperty('componentName', "HBASE_REGIONSERVER").forEach(function (slave) {
+      slave.hosts.forEach(function(_slaveHost){
+        var hosts = slaveClient.hosts.filterProperty('hostName', _slaveHost.hostName);
+        if (!hosts.length) {
+          var slaveObj = {};
+          slaveObj.component = "HDFS_CLIENT";
+          slaveObj.hostName = _slaveHost.hostName;
+          slaveObj.isInstalled = false;
+          this.createHostComponent(slaveObj);
+        }
+      }, this)
+    }, this);
+
     slaveHosts.forEach(function (_slaveHosts) {
       var slaveObj = {};
       if (_slaveHosts.componentName !== 'CLIENT') {

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

@@ -25,6 +25,10 @@ App.alwaysGoToInstaller = false;
 App.apiPrefix = '/api/v1';
 // default AJAX timeout
 App.timeout = 20000;
+App.bgOperationsUpdateInterval = 6000;
+App.componentsUpdateInterval = 6000;
+App.graphUpdateInterval = 15000;
+App.services_update = 15000;
 
 require('messages');
 require('utils/data_table');
@@ -36,7 +40,6 @@ require('views');
 require('router');
 
 require('mappers/server_data_mapper');
-require('mappers/services_mapper');
 require('mappers/status_mapper');
 require('mappers/hosts_mapper');
 require('mappers/cluster_mapper');
@@ -45,6 +48,7 @@ require('mappers/runs_mapper');
 require('mappers/racks_mapper');
 require('mappers/alerts_mapper');
 require('mappers/users_mapper');
+require('mappers/update_mapper');
 
 require('utils/http_client');
 

+ 2 - 3
ambari-web/app/mappers/jobs_mapper.js

@@ -77,13 +77,12 @@ App.jobTasksMapper = App.QuickDataMapper.create({
     mapRackLocal:'mapRackLocal',
     mapOffSwitch:'mapOffSwitch',
     reduceOffSwitch:'reduceOffSwitch',
-    submitTime:'submitTime',
-    finishTime:'finishTime'
+    submit:'submitTime',
+    finish:'finishTime'
   },
   map:function (json) {
     var job = this.get('model'); // @model App.MainAppsItemBarView
     var parseResult = this.parseIt(json, this.config);
-
     $.each(parseResult, function (field, value) {
       job.set(field, value);
     });

+ 0 - 307
ambari-web/app/mappers/services_mapper.js

@@ -1,307 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-App.servicesMapper = App.QuickDataMapper.create({
-  model:App.Service,
-  servicesSortOrder: [
-    'HDFS',
-    'MAPREDUCE',
-    'HBASE',
-    'HIVE',
-    'OOZIE',
-    'GANGLIA',
-    'NAGIOS',
-    'ZOOKEEPER',
-    'PIG',
-    'SQOOP'
-  ],
-  sortByOrder: function (sortOrder, array) {
-    var sorted = [];
-    for (var i = 0; i < sortOrder.length; i++)
-      for (var j = 0; j < array.length; j++) {
-        if (sortOrder[i] == array[j].id) {
-          sorted.push(array[j]);
-        }
-      }
-    return sorted;
-  },
-  config:{
-    id:'ServiceInfo.service_name',
-    service_name:'ServiceInfo.service_name',
-    work_status:'ServiceInfo.state',
-    $service_audit:[ 1, 2, 3 ],
-    $alerts:[ 1, 2, 3 ],
-    components_key:'components',
-    components_type:'array',
-    components:{
-      item:'ServiceComponentInfo.component_name'
-    },
-    host_components:'host_components'
-  },
-
-  hdfsConfig:{
-    version:'nameNodeComponent.ServiceComponentInfo.Version',
-    name_node_id:'nameNodeComponent.host_components[0].HostRoles.host_name',
-    sname_node_id:'snameNodeComponent.host_components[0].HostRoles.host_name',
-    data_nodes:'data_nodes',
-    name_node_start_time:'nameNodeComponent.ServiceComponentInfo.StartTime',
-    jvm_memory_heap_used:'nameNodeComponent.host_components[0].metrics.jvm.memHeapUsedM',
-    jvm_memory_heap_committed:'nameNodeComponent.host_components[0].metrics.jvm.memHeapCommittedM',
-    live_data_nodes:'live_data_nodes',
-    dead_data_nodes:'dead_data_nodes',
-    decommision_data_nodes:'decommission_data_nodes',
-    capacity_used:'nameNodeComponent.ServiceComponentInfo.CapacityUsed',
-    capacity_total:'nameNodeComponent.ServiceComponentInfo.CapacityTotal',
-    dfs_total_blocks:'nameNodeComponent.ServiceComponentInfo.BlocksTotal',
-    dfs_corrupt_blocks:'nameNodeComponent.ServiceComponentInfo.CorruptBlocks',
-    dfs_missing_blocks:'nameNodeComponent.ServiceComponentInfo.MissingBlocks',
-    dfs_under_replicated_blocks:'nameNodeComponent.ServiceComponentInfo.UnderReplicatedBlocks',
-    dfs_total_files:'nameNodeComponent.ServiceComponentInfo.TotalFiles',
-    upgrade_status:'nameNodeComponent.ServiceComponentInfo.UpgradeFinalized',
-    safe_mode_status:'nameNodeComponent.ServiceComponentInfo.Safemode'
-  },
-
-  mapReduceConfig:{
-    version:'jobTrackerComponent.ServiceComponentInfo.Version',
-    job_tracker_id:'jobTrackerComponent.host_components[0].HostRoles.host_name',
-    task_trackers:'task_trackers',
-    job_tracker_start_time:'jobTrackerComponent.ServiceComponentInfo.StartTime',
-    job_tracker_heap_used:'jobTrackerComponent.ServiceComponentInfo.HeapMemoryUsed',
-    job_tracker_heap_max:'jobTrackerComponent.ServiceComponentInfo.HeapMemoryMax',
-    alive_trackers:'alive_trackers',
-    black_list_trackers:'black_list_trackers',
-    gray_list_trackers:'gray_list_trackers',
-    map_slots:'map_slots',
-    reduce_slots:'reduce_slots',
-    jobs_submitted:'jobTrackerComponent.ServiceComponentInfo.jobtracker.jobs_submitted',
-    jobs_completed:'jobTrackerComponent.ServiceComponentInfo.jobtracker.jobs_completed',
-    map_slots_occupied:'jobTrackerComponent.ServiceComponentInfo.jobtracker.occupied_map_slots',
-    map_slots_reserved:'jobTrackerComponent.ServiceComponentInfo.jobtracker.reserved_map_slots',
-    reduce_slots_occupied:'jobTrackerComponent.ServiceComponentInfo.jobtracker.occupied_reduce_slots',
-    reduce_slots_reserved:'jobTrackerComponent.ServiceComponentInfo.jobtracker.reserved_reduce_slots',
-    maps_running:'jobTrackerComponent.ServiceComponentInfo.jobtracker.running_maps',
-    maps_waiting:'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_maps',
-    reduces_running:'jobTrackerComponent.ServiceComponentInfo.jobtracker.running_reduces',
-    reduces_waiting:'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_reduces',
-    trackers_decommisioned:'jobTrackerComponent.host_components[0].metrics.mapred.jobtracker.trackers_decommissioned'
-  },
-
-  hbaseConfig:{
-    version:'masterComponent.ServiceComponentInfo.Version',
-    master_id:'masterComponent.host_components[0].HostRoles.host_name',
-    region_servers:'region_servers',
-    master_start_time:'masterComponent.ServiceComponentInfo.MasterStartTime',
-    master_active_time:'masterComponent.ServiceComponentInfo.MasterActiveTime',
-    average_load:'masterComponent.ServiceComponentInfo.AverageLoad',
-    regions_in_transition:'regions_in_transition',
-    revision:'masterComponent.ServiceComponentInfo.Revision',
-    heap_memory_used:'masterComponent.ServiceComponentInfo.HeapMemoryUsed',
-    heap_memory_max:'masterComponent.ServiceComponentInfo.HeapMemoryMax'
-  },
-
-  model2:App.Component,
-  config2:{
-    id:'ServiceComponentInfo.component_name',
-    component_name:'ServiceComponentInfo.component_name',
-    service_id:'ServiceComponentInfo.service_name',
-    work_status:'host_components[0].HostRoles.state',
-    host_id:'host_components[0].HostRoles.host_name',
-    $decommissioned:false
-  },
-  model3:App.HostComponent,
-  config3:{
-    id:'id',
-    work_status:'HostRoles.state',
-    component_name:'HostRoles.component_name',
-    host_id:'HostRoles.host_name',
-    service_id:'component[0].ServiceComponentInfo.service_name'
-  },
-
-  map:function (json) {
-    if (!this.get('model')) {
-      return;
-    }
-
-    if (json.items) {
-      var result = [];
-      json.items.forEach(function (item) {
-        var finalConfig = jQuery.extend({}, this.config);
-        item.host_components = [];
-        item.components.forEach(function (component) {
-          component.host_components.forEach(function (host_component) {
-            host_component.id = host_component.HostRoles.component_name + "_" + host_component.HostRoles.host_name;
-            item.host_components.push(host_component.id);
-          }, this)
-        }, this);
-
-        if (item && item.ServiceInfo && item.ServiceInfo.service_name == "HDFS") {
-          // Change the JSON so that it is easy to map
-          var hdfsConfig = this.hdfsConfig;
-          item.components.forEach(function (component) {
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "NAMENODE") {
-              item.nameNodeComponent = component;
-              finalConfig = jQuery.extend(finalConfig, hdfsConfig);
-              // Get the live, dead & decommision nodes from string json
-              var liveNodesJson = App.parseJSON(component.ServiceComponentInfo.LiveNodes);
-              var deadNodesJson = App.parseJSON(component.ServiceComponentInfo.DeadNodes);
-              var decommisionNodesJson = App.parseJSON(component.ServiceComponentInfo.DecomNodes);
-              item.live_data_nodes = [];
-              item.dead_data_nodes = [];
-              item.decommision_data_nodes = [];
-              for (var ln in liveNodesJson) {
-                item.live_data_nodes.push(ln);
-              }
-              for (var dn in deadNodesJson) {
-                item.dead_data_nodes.push(dn);
-              }
-              for (var dcn in decommisionNodesJson) {
-                item.decommision_data_nodes.push(dcn);
-              }
-            }
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "SECONDARY_NAMENODE") {
-              item.snameNodeComponent = component;
-            }
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "DATANODE") {
-              if (!item.data_nodes) {
-                item.data_nodes = [];
-              }
-              if (component.host_components) {
-                component.host_components.forEach(function (hc) {
-                  item.data_nodes.push(hc.HostRoles.host_name);
-                });
-              }
-            }
-          });
-          // Map
-          var finalJson = this.parseIt(item, finalConfig);
-          finalJson.quick_links = [1, 2, 3, 4];
-          result.push(finalJson);
-          App.store.load(App.HDFSService, finalJson);
-        } else if (item && item.ServiceInfo && item.ServiceInfo.service_name == "MAPREDUCE") {
-          // Change the JSON so that it is easy to map
-          var mapReduceConfig = this.mapReduceConfig;
-          item.components.forEach(function (component) {
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "JOBTRACKER") {
-              item.jobTrackerComponent = component;
-              finalConfig = jQuery.extend(finalConfig, mapReduceConfig);
-              // Get the live, gray & black nodes from string json
-              item.map_slots = 0;
-              item.reduce_slots = 0;
-              var liveNodesJson = App.parseJSON(component.ServiceComponentInfo.AliveNodes);
-              var grayNodesJson = App.parseJSON(component.ServiceComponentInfo.GrayListedNodes);
-              var blackNodesJson = App.parseJSON(component.ServiceComponentInfo.BlackListedNodes);
-              item.alive_trackers = [];
-              item.gray_list_trackers = [];
-              item.black_list_trackers = [];
-              if (liveNodesJson != null) {
-                liveNodesJson.forEach(function (nj) {
-                  item.alive_trackers.push(nj.hostname);
-                  if (nj.slots && nj.slots.map_slots)
-                    item.map_slots += nj.slots.map_slots;
-                  if (nj.slots && nj.slots.map_slots_used)
-                    item.map_slots_used += nj.slots.map_slots_used;
-                  if (nj.slots && nj.slots.reduce_slots)
-                    item.reduce_slots += nj.slots.reduce_slots;
-                  if (nj.slots && nj.slots.reduce_slots_used)
-                    item.reduce_slots_used += nj.slots.reduce_slots_used;
-                });
-              }
-              if (grayNodesJson != null) {
-                grayNodesJson.forEach(function (nj) {
-                  item.gray_list_trackers.push(nj.hostname);
-                });
-              }
-              if (blackNodesJson != null) {
-                blackNodesJson.forEach(function (nj) {
-                  item.black_list_trackers.push(nj.hostname);
-                });
-              }
-            }
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "TASKTRACKER") {
-              if (!item.task_trackers) {
-                item.task_trackers = [];
-              }
-              if (component.host_components) {
-                component.host_components.forEach(function (hc) {
-                  item.task_trackers.push(hc.HostRoles.host_name);
-                });
-              }
-            }
-          });
-          // Map
-          finalJson = this.parseIt(item, finalConfig);
-          finalJson.quick_links = [5, 6, 7, 8, 9, 10, 11, 12];
-          result.push(finalJson);
-          App.store.load(App.MapReduceService, finalJson);
-        } else if (item && item.ServiceInfo && item.ServiceInfo.service_name == "HBASE") {
-          // Change the JSON so that it is easy to map
-          var hbaseConfig = this.hbaseConfig;
-          item.components.forEach(function (component) {
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "HBASE_MASTER") {
-              item.masterComponent = component;
-              finalConfig = jQuery.extend(finalConfig, hbaseConfig);
-              var regionsArray = App.parseJSON(component.ServiceComponentInfo.RegionsInTransition);
-              item.regions_in_transition = regionsArray == null ? 0 : regionsArray.length;
-            }
-            if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "HBASE_REGIONSERVER") {
-              if (!item.region_servers) {
-                item.region_servers = [];
-              }
-              if (component.host_components) {
-                component.host_components.forEach(function (hc) {
-                  item.region_servers.push(hc.HostRoles.host_name);
-                });
-              }
-            }
-          });
-          // Map
-          finalJson = this.parseIt(item, finalConfig);
-          finalJson.quick_links = [13, 14, 15, 16, 17, 18];
-          result.push(finalJson);
-          App.store.load(App.HBaseService, finalJson);
-        } else {
-          result.push(this.parseIt(item, this.config));
-        }
-      }, this);
-
-
-
-
-      result = this.sortByOrder(this.get('servicesSortOrder'), result);
-      App.store.loadMany(this.get('model'), result);
-
-      result = [];
-      json.items.forEach(function (item) {
-        item.components.forEach(function (component) {
-          result.push(this.parseIt(component, this.config2));
-        }, this)
-      }, this);
-
-      App.store.loadMany(this.get('model2'), result);
-
-      result = [];
-      json.items.forEach(function (item) {
-        item.components.forEach(function (component) {
-          component.host_components.forEach(function (host_component) {
-            result.push(this.parseIt(host_component, this.config3));
-          }, this)
-        }, this)
-      }, this);
-      App.store.loadMany(this.get('model3'), result);
-    }
-  }
-});

+ 12 - 3
ambari-web/app/mappers/status_mapper.js

@@ -52,7 +52,10 @@ App.statusMapper = App.QuickDataMapper.create({
       //console.log(result)
       var services = App.Service.find();
       result.forEach(function(item){
-        services.findProperty('id', item.id).set('workStatus', item.work_status);
+        var service = services.findProperty('id', item.id);
+        if(service){
+          service.set('workStatus', item.work_status);
+        }
       })
 
       result = [];
@@ -65,7 +68,10 @@ App.statusMapper = App.QuickDataMapper.create({
       //console.log(result)
       var components = App.Component.find();
       result.forEach(function(item){
-        components.findProperty('id', item.id).set('workStatus', item.work_status);
+        var component = components.findProperty('id', item.id);
+        if(component){
+          component.set('workStatus', item.work_status);
+        }
       })
 
       result = [];
@@ -80,7 +86,10 @@ App.statusMapper = App.QuickDataMapper.create({
       //console.log(result)
       var hostComponents = App.HostComponent.find();
       result.forEach(function(item){
-        hostComponents.findProperty('id', item.id).set('workStatus', item.work_status);
+        var hostComponent = hostComponents.findProperty('id', item.id);
+        if(hostComponent){
+          hostComponent.set('workStatus', item.work_status);
+        }
       })
     }
   }

+ 372 - 0
ambari-web/app/mappers/update_mapper.js

@@ -0,0 +1,372 @@
+/**
+ * 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.
+ */
+
+
+App.updateMapper = App.QuickDataMapper.create({
+  map:function (json) {
+    var self=this;
+    var result = [];
+
+    json.items.forEach(function (service) {
+      switch(service.ServiceInfo.service_name){
+        case 'NAGIOS':
+          self.nagiosMapper(service);
+          break;
+        case 'GANGLIA':
+          self.gangliaMapper(service);
+          break;
+        case 'HDFS':
+          self.hdfsMapper(service);
+          break;
+        case 'MAPREDUCE':
+          self.mapreduceMapper(service);
+          break;
+        case 'HBASE':
+          self.hbaseMapper(service);
+          break;
+      }
+    })
+
+    if (json.items) {
+
+      if(this.get('model2').find().get("content").length<1)
+      {
+        result = [];
+        json.items.forEach(function (item) {
+          item.components.forEach(function (component) {
+            result.push(this.parseIt(component, this.config2));
+          }, this)
+        }, this);
+        App.store.loadMany(this.get('model2'), result);
+      }
+      if(this.get('model3').find().get("content").length<1)
+      {
+        result = [];
+        json.items.forEach(function (item) {
+          item.components.forEach(function (component) {
+            component.host_components.forEach(function (host_component) {
+              result.push(this.parseIt(host_component, this.config3));
+            }, this)
+          }, this)
+        }, this);
+        App.store.loadMany(this.get('model3'), result);
+      }
+    }
+  },
+
+  nagiosMapper:function(item){
+
+  },
+  gangliaMapper:function(item){
+
+  },
+  hdfsMapper:function(item){
+    var result=[];
+    var finalConfig = jQuery.extend({}, this.config);
+    // Change the JSON so that it is easy to map
+    var hdfsConfig = this.hdfsConfig;
+    item.components.forEach(function (component) {
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "NAMENODE") {
+        item.nameNodeComponent = component;
+        finalConfig = jQuery.extend(finalConfig, hdfsConfig);
+        // Get the live, dead & decommision nodes from string json
+        var liveNodesJson = App.parseJSON(component.ServiceComponentInfo.LiveNodes);
+        var deadNodesJson = App.parseJSON(component.ServiceComponentInfo.DeadNodes);
+        var decommisionNodesJson = App.parseJSON(component.ServiceComponentInfo.DecomNodes);
+        item.live_data_nodes = [];
+        item.dead_data_nodes = [];
+        item.decommision_data_nodes = [];
+        for (var ln in liveNodesJson) {
+          item.live_data_nodes.push(ln);
+        }
+        for (var dn in deadNodesJson) {
+          item.dead_data_nodes.push(dn);
+        }
+        for (var dcn in decommisionNodesJson) {
+          item.decommision_data_nodes.push(dcn);
+        }
+      }
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "SECONDARY_NAMENODE") {
+        item.snameNodeComponent = component;
+      }
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "DATANODE") {
+        if (!item.data_nodes) {
+          item.data_nodes = [];
+        }
+        if (component.host_components) {
+          component.host_components.forEach(function (hc) {
+            item.data_nodes.push(hc.HostRoles.host_name);
+          });
+        }
+      }
+    });
+    // Map
+    var finalJson = this.parseIt(item, finalConfig);
+    finalJson.quick_links = [1, 2, 3, 4];
+
+    if(App.HDFSService.find().get("content").length<1)
+    {
+      App.store.load(App.HDFSService, finalJson);
+      result.push(finalJson);
+      App.store.loadMany(App.Service, result);
+    }else
+    {
+      App.HDFSService.find().map(function(e){
+        e.set("version",finalJson.version);
+        e.set("nameNodeStartTime",finalJson.name_node_start_time);
+        e.set("jvmMemoryHeapCommitted",finalJson.jvm_memory_heap_committed);
+        e.set("capacityUsed",finalJson.capacity_used);
+        e.set("capacityTotal",finalJson.capacity_total);
+        e.set("dfsTotalBlocks",finalJson.dfs_total_blocks);
+        e.set("dfsCorruptBlocks",finalJson.dfs_corrupt_blocks);
+        e.set("dfsMissingBlocks",finalJson.dfs_missing_blocks);
+        e.set("dfsUnderReplicatedBlocks",finalJson.dfs_under_replicated_blocks);
+        e.set("dfsTotalFiles",finalJson.dfs_total_files);
+        e.set("upgradeStatus",finalJson.upgrade_status);
+        e.set("safeModeStatus",finalJson.safe_mode_status);
+      })
+    }
+  },
+  mapreduceMapper:function(item){
+    // Change the JSON so that it is easy to map
+    var result=[];
+    var finalConfig = jQuery.extend({}, this.config);
+    var mapReduceConfig = this.mapReduceConfig;
+    item.components.forEach(function (component) {
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "JOBTRACKER") {
+        item.jobTrackerComponent = component;
+        finalConfig = jQuery.extend(finalConfig, mapReduceConfig);
+        // Get the live, gray & black nodes from string json
+        item.map_slots = 0;
+        item.reduce_slots = 0;
+        var liveNodesJson = App.parseJSON(component.ServiceComponentInfo.AliveNodes);
+        var grayNodesJson = App.parseJSON(component.ServiceComponentInfo.GrayListedNodes);
+        var blackNodesJson = App.parseJSON(component.ServiceComponentInfo.BlackListedNodes);
+        item.alive_trackers = [];
+        item.gray_list_trackers = [];
+        item.black_list_trackers = [];
+        if (liveNodesJson != null) {
+          liveNodesJson.forEach(function (nj) {
+            item.alive_trackers.push(nj.hostname);
+            if (nj.slots && nj.slots.map_slots)
+              item.map_slots += nj.slots.map_slots;
+            if (nj.slots && nj.slots.map_slots_used)
+              item.map_slots_used += nj.slots.map_slots_used;
+            if (nj.slots && nj.slots.reduce_slots)
+              item.reduce_slots += nj.slots.reduce_slots;
+            if (nj.slots && nj.slots.reduce_slots_used)
+              item.reduce_slots_used += nj.slots.reduce_slots_used;
+          });
+        }
+        if (grayNodesJson != null) {
+          grayNodesJson.forEach(function (nj) {
+            item.gray_list_trackers.push(nj.hostname);
+          });
+        }
+        if (blackNodesJson != null) {
+          blackNodesJson.forEach(function (nj) {
+            item.black_list_trackers.push(nj.hostname);
+          });
+        }
+      }
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "TASKTRACKER") {
+        if (!item.task_trackers) {
+          item.task_trackers = [];
+        }
+        if (component.host_components) {
+          component.host_components.forEach(function (hc) {
+            item.task_trackers.push(hc.HostRoles.host_name);
+          });
+        }
+      }
+    });
+    // Map
+    finalJson = this.parseIt(item, finalConfig);
+    finalJson.quick_links = [5, 6, 7, 8, 9, 10, 11, 12];
+
+    if(App.MapReduceService.find().get("content").length<1)
+    {
+      result.push(finalJson);
+      App.store.load(App.MapReduceService, finalJson);
+      App.store.loadMany(App.Service, result);
+    }else
+    {
+      App.MapReduceService.find().map(function(e){
+        e.set("version",finalJson.version);
+        e.set("jobTrackerStartTime",finalJson.job_tracker_start_time);
+        e.set("jobTrackerHeapUsed",finalJson.job_tracker_heap_used);
+        e.set("jobTrackerHeapMax",finalJson.job_tracker_heap_max);
+        e.set("mapSlots",finalJson.map_slots);
+        e.set("reduceSlots",finalJson.reduce_slots);
+        e.set("jobsSubmitted",finalJson.jobs_submitted);
+        e.set("jobsCompleted",finalJson.jobs_completed);
+        e.set("mapSlotsOccupied",finalJson.map_slots_occupied);
+        e.set("mapSlotsReserved",finalJson.map_slots_reserved);
+        e.set("reduceSlotsOccupied",finalJson.reduce_slots_occupied);
+        e.set("reduceSlotsReserved",finalJson.reduce_slots_reserved);
+        e.set("mapsRunning",finalJson.maps_running);
+        e.set("mapsWaiting",finalJson.maps_waiting);
+        e.set("reducesRunning",finalJson.reduces_running);
+        e.set("reducesWaiting",finalJson.reduces_waiting);
+        e.set("trackersDecommisioned",finalJson.trackers_decommisioned);
+      })
+    }
+
+
+  },
+  hbaseMapper:function(item){
+    // Change the JSON so that it is easy to map
+    var result=[];
+    var finalConfig = jQuery.extend({}, this.config);
+    var hbaseConfig = this.hbaseConfig;
+    item.components.forEach(function (component) {
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "HBASE_MASTER") {
+        item.masterComponent = component;
+        finalConfig = jQuery.extend(finalConfig, hbaseConfig);
+        var regionsArray = App.parseJSON(component.ServiceComponentInfo.RegionsInTransition);
+        item.regions_in_transition = regionsArray == null ? 0 : regionsArray.length;
+      }
+      if (component.ServiceComponentInfo && component.ServiceComponentInfo.component_name == "HBASE_REGIONSERVER") {
+        if (!item.region_servers) {
+          item.region_servers = [];
+        }
+        if (component.host_components) {
+          component.host_components.forEach(function (hc) {
+            item.region_servers.push(hc.HostRoles.host_name);
+          });
+        }
+      }
+    });
+    // Map
+    finalJson = this.parseIt(item, finalConfig);
+    finalJson.quick_links = [13, 14, 15, 16, 17, 18];
+
+    if(App.HBaseService.find().get("content").length<1)
+    {
+      result.push(finalJson);
+      App.store.load(App.HBaseService, finalJson);
+      App.store.loadMany(App.Service, result);
+    }else
+    {
+      App.HBaseService.find().map(function(e){
+        e.set("version",finalJson.version);
+        e.set("masterStartTime",finalJson.master_start_time);
+        e.set("masterActiveTime",finalJson.master_active_time);
+        e.set("averageLoad",finalJson.average_load);
+        e.set("regionsInTransition",finalJson.regions_in_transition);
+        e.set("revision",finalJson.revision);
+        e.set("heapMemoryUsed",finalJson.heap_memory_used);
+        e.set("heapMemoryMax",finalJson.heap_memory_max);
+      })
+    }
+  },
+
+
+  model2:App.Component,
+  config2:{
+    id:'ServiceComponentInfo.component_name',
+    component_name:'ServiceComponentInfo.component_name',
+    service_id:'ServiceComponentInfo.service_name',
+    work_status:'host_components[0].HostRoles.state',
+    host_id:'host_components[0].HostRoles.host_name',
+    $decommissioned:false
+  },
+  model3:App.HostComponent,
+  config3:{
+    id:'id',
+    work_status:'HostRoles.state',
+    component_name:'HostRoles.component_name',
+    host_id:'HostRoles.host_name',
+    service_id:'component[0].ServiceComponentInfo.service_name'
+  },
+
+  hbaseConfig:{
+    version:'masterComponent.ServiceComponentInfo.Version',
+    master_id:'masterComponent.host_components[0].HostRoles.host_name',
+    region_servers:'region_servers',
+    master_start_time:'masterComponent.ServiceComponentInfo.MasterStartTime',
+    master_active_time:'masterComponent.ServiceComponentInfo.MasterActiveTime',
+    average_load:'masterComponent.ServiceComponentInfo.AverageLoad',
+    regions_in_transition:'regions_in_transition',
+    revision:'masterComponent.ServiceComponentInfo.Revision',
+    heap_memory_used:'masterComponent.ServiceComponentInfo.HeapMemoryUsed',
+    heap_memory_max:'masterComponent.ServiceComponentInfo.HeapMemoryMax'
+  },
+
+  mapReduceConfig:{
+    version:'jobTrackerComponent.ServiceComponentInfo.Version',
+    job_tracker_id:'jobTrackerComponent.host_components[0].HostRoles.host_name',
+    task_trackers:'task_trackers',
+    job_tracker_start_time:'jobTrackerComponent.ServiceComponentInfo.StartTime',
+    job_tracker_heap_used:'jobTrackerComponent.ServiceComponentInfo.HeapMemoryUsed',
+    job_tracker_heap_max:'jobTrackerComponent.ServiceComponentInfo.HeapMemoryMax',
+    alive_trackers:'alive_trackers',
+    black_list_trackers:'black_list_trackers',
+    gray_list_trackers:'gray_list_trackers',
+    map_slots:'map_slots',
+    reduce_slots:'reduce_slots',
+    jobs_submitted:'jobTrackerComponent.ServiceComponentInfo.jobtracker.jobs_submitted',
+    jobs_completed:'jobTrackerComponent.ServiceComponentInfo.jobtracker.jobs_completed',
+    map_slots_occupied:'jobTrackerComponent.ServiceComponentInfo.jobtracker.occupied_map_slots',
+    map_slots_reserved:'jobTrackerComponent.ServiceComponentInfo.jobtracker.reserved_map_slots',
+    reduce_slots_occupied:'jobTrackerComponent.ServiceComponentInfo.jobtracker.occupied_reduce_slots',
+    reduce_slots_reserved:'jobTrackerComponent.ServiceComponentInfo.jobtracker.reserved_reduce_slots',
+    maps_running:'jobTrackerComponent.ServiceComponentInfo.jobtracker.running_maps',
+    maps_waiting:'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_maps',
+    reduces_running:'jobTrackerComponent.ServiceComponentInfo.jobtracker.running_reduces',
+    reduces_waiting:'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_reduces',
+    trackers_decommisioned:'jobTrackerComponent.host_components[0].metrics.mapred.jobtracker.trackers_decommissioned'
+  },
+
+  hdfsConfig:{
+    version:'nameNodeComponent.ServiceComponentInfo.Version',
+    name_node_id:'nameNodeComponent.host_components[0].HostRoles.host_name',
+    sname_node_id:'snameNodeComponent.host_components[0].HostRoles.host_name',
+    data_nodes:'data_nodes',
+    name_node_start_time:'nameNodeComponent.ServiceComponentInfo.StartTime',
+    jvm_memory_heap_used:'nameNodeComponent.host_components[0].metrics.jvm.memHeapUsedM',
+    jvm_memory_heap_committed:'nameNodeComponent.host_components[0].metrics.jvm.memHeapCommittedM',
+    live_data_nodes:'live_data_nodes',
+    dead_data_nodes:'dead_data_nodes',
+    decommision_data_nodes:'decommission_data_nodes',
+    capacity_used:'nameNodeComponent.ServiceComponentInfo.CapacityUsed',
+    capacity_total:'nameNodeComponent.ServiceComponentInfo.CapacityTotal',
+    dfs_total_blocks:'nameNodeComponent.ServiceComponentInfo.BlocksTotal',
+    dfs_corrupt_blocks:'nameNodeComponent.ServiceComponentInfo.CorruptBlocks',
+    dfs_missing_blocks:'nameNodeComponent.ServiceComponentInfo.MissingBlocks',
+    dfs_under_replicated_blocks:'nameNodeComponent.ServiceComponentInfo.UnderReplicatedBlocks',
+    dfs_total_files:'nameNodeComponent.ServiceComponentInfo.TotalFiles',
+    upgrade_status:'nameNodeComponent.ServiceComponentInfo.UpgradeFinalized',
+    safe_mode_status:'nameNodeComponent.ServiceComponentInfo.Safemode'
+  },
+
+  config:{
+    id:'ServiceInfo.service_name',
+    service_name:'ServiceInfo.service_name',
+    work_status:'ServiceInfo.state',
+    $service_audit:[ 1, 2, 3 ],
+    $alerts:[ 1, 2, 3 ],
+    components_key:'components',
+    components_type:'array',
+    components:{
+      item:'ServiceComponentInfo.component_name'
+    },
+    host_components:'host_components'
+  }
+
+});

+ 0 - 1
ambari-web/app/models/host.js

@@ -58,7 +58,6 @@ App.Host = DS.Model.extend({
   }.property('lastHeartBeatTime'),
 
   loadAvg: function() {
-    console.log(this.get('loadOne'), this.get('loadFive'), this.get('loadFifteen'));
     if (this.get('loadOne') != null) return this.get('loadOne');
     if (this.get('loadFive') != null) return this.get('loadFive');
     if (this.get('loadFifteen') != null) return this.get('loadFifteen');

File diff suppressed because it is too large
+ 6 - 8
ambari-web/app/models/hosts.js


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

@@ -35,9 +35,7 @@ App.User = DS.Model.extend({
     return 'Local';
   }.property('isLdap'),
   auditItems:DS.hasMany('App.ServiceAudit'),
-  admin:function () {
-    return !!(/^admin/.test(this.get('roles')))
-  }.property('userName')
+  admin: DS.attr('boolean')
 });
 
 App.EditUserForm = App.Form.extend({

+ 70 - 17
ambari-web/app/router.js

@@ -134,7 +134,6 @@ App.Router = Em.Router.extend({
     var loginName = controller.get('loginName');
     var hash = window.btoa(loginName + ":" + controller.get('password'));
     var router = this;
-
     $.ajax({
       url : App.apiPrefix + '/users/' + loginName,
       dataType : 'text',
@@ -158,13 +157,36 @@ App.Router = Em.Router.extend({
 
         var resp = $.parseJSON(data);
         var isAdmin = resp.Users.roles.indexOf('admin') >= 0;
-
-        router.setAuthenticated(true);
-        router.setLoginName(loginName);
-
-        router.setUser(App.store.createRecord(App.User, { userName: loginName, admin: isAdmin }));
-        router.transitionTo(router.getSection());
-        postLogin(true);
+        if(isAdmin){
+          router.setAuthenticated(true);
+          router.setLoginName(loginName);
+
+          router.setUser(App.store.createRecord(App.User, { userName: loginName, admin: isAdmin }));
+          router.transitionTo(router.getSection());
+          postLogin(true);
+        } else {
+          $.ajax({
+            url: App.apiPrefix + '/clusters',
+            dataType: 'text',
+            type: 'GET',
+            success: function (data) {
+              var clusterResp = $.parseJSON(data);
+              if (clusterResp.items.length) {
+                router.setAuthenticated(true);
+                router.setLoginName(loginName);
+
+                router.setUser(App.store.createRecord(App.User, { userName: loginName, admin: isAdmin }));
+                router.transitionTo(router.getSection());
+                postLogin(true);
+              } else {
+                controller.set('errorMessage', "Your administrator has not set up a Hadoop cluster yet.");
+              }
+            },
+            error: function (req) {
+              console.log("Server not responding: " + req.statusCode);
+            }
+          });
+        }
       },
       error: function (req) {
         console.log("login error: " + req.statusCode);
@@ -172,6 +194,7 @@ App.Router = Em.Router.extend({
         postLogin(false);
       }
     });
+
   },
 
   setAmbariStacks: function () {
@@ -213,19 +236,49 @@ App.Router = Em.Router.extend({
   mockLogin: function (postLogin) {
     var controller = this.get('loginController');
     var loginName = controller.get('loginName');
-
+    var router = this;
     if ((loginName === 'admin' && controller.get('password') === 'admin') ||
-        (loginName === 'user' && controller.get('password') === 'user')) {
-      this.setAuthenticated(true);
-      this.setLoginName(loginName);
-      this.setUser(App.store.createRecord(App.User, { userName: loginName, admin: loginName === 'admin' }));
-      this.setAmbariStacks();
-      this.transitionTo(this.getSection());
-      postLogin(true);
+      (loginName === 'user' && controller.get('password') === 'user')) {
+      if(loginName === 'admin'){
+        router.setAuthenticated(true);
+        router.setLoginName(loginName);
+
+        router.setUser(App.store.createRecord(App.User, { userName: loginName, admin: loginName === 'admin' }));
+        router.setAmbariStacks();
+
+        router.transitionTo(router.getSection());
+        postLogin(true);
+      } else {
+        $.ajax({
+          url: '/data/clusters/info.json',
+          dataType: 'text',
+          type: 'GET',
+          success: function (data) {
+            var clusterResp = $.parseJSON(data);
+            if (clusterResp.items.length) {
+              router.setAuthenticated(true);
+              router.setLoginName(loginName);
+
+              router.setUser(App.store.createRecord(App.User, { userName: loginName, admin: loginName === 'admin' }));
+              router.setAmbariStacks();
+
+              router.transitionTo(router.getSection());
+              postLogin(true);
+            } else {
+              controller.set('errorMessage', "Your administrator has not set up a Hadoop cluster yet.");
+            }
+          },
+          error: function (req) {
+            console.log("Server not responding: " + req.statusCode);
+          }
+        });
+      }
     } else {
-      this.setAuthenticated(false);
+      router.setAuthenticated(false);
       postLogin(false);
     }
+
+
   },
 
   getSection: function () {

+ 52 - 78
ambari-web/app/routes/add_host_routes.js

@@ -41,8 +41,10 @@ module.exports = Em.Route.extend({
       var controller = router.get('addHostController');
       controller.setCurrentStep('1', false);
       controller.set('hideBackButton', true);
+      controller.dataLoading().done(function () {
       controller.loadAllPriorSteps();
       controller.connectOutlet('wizardStep2', controller.get('content.hosts'));
+      })
     },
 
     next: function (router) {
@@ -69,17 +71,19 @@ module.exports = Em.Route.extend({
       console.log('in addHost.step2:connectOutlets');
       var controller = router.get('addHostController');
       controller.setCurrentStep('2', false);
+      controller.dataLoading().done(function () {
       controller.loadAllPriorSteps();
       controller.connectOutlet('wizardStep3', controller.get('content'));
+      })
     },
     back: Em.Router.transitionTo('step1'),
     next: function (router, context) {
       var addHostController = router.get('addHostController');
       var wizardStep3Controller = router.get('wizardStep3Controller');
       addHostController.saveConfirmedHosts(wizardStep3Controller);
+      addHostController.saveClients();
 
       App.db.setBootStatus(true);
-      App.db.setService(require('data/mock/services'));
       router.transitionTo('step3');
     },
     /**
@@ -101,116 +105,84 @@ module.exports = Em.Route.extend({
       console.log('in addHost.step3:connectOutlets');
       var controller = router.get('addHostController');
       controller.setCurrentStep('3', false);
-      controller.set('hideBackButton', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep4', controller.get('content.services'));
+      controller.dataLoading().done(function () {
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('wizardStep6', controller.get('content'));
+      })
     },
     back: Em.Router.transitionTo('step2'),
-    next: function (router, context) {
-      var addHostController = router.get('addHostController');
-      var wizardStep4Controller = router.get('wizardStep4Controller');
-      addHostController.saveServices(wizardStep4Controller);
-      addHostController.saveClients(wizardStep4Controller);
-      App.db.setMasterComponentHosts(undefined);
-      router.transitionTo('step4');
-    }
-  }),
-
-  step4: Em.Route.extend({
-    route: '/step4',
-    connectOutlets: function (router, context) {
-      console.log('in addHost.step4:connectOutlets');
-      var controller = router.get('addHostController');
-      controller.setCurrentStep('4', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep5', controller.get('content'));
-
-    },
-    back: Em.Router.transitionTo('step3'),
-    next: function (router, context) {
-      var addHostController = router.get('addHostController');
-      var wizardStep5Controller = router.get('wizardStep5Controller');
-      addHostController.saveMasterComponentHosts(wizardStep5Controller);
-      App.db.setSlaveComponentHosts(undefined);
-      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);
+        addHostController.get('content').set('serviceConfigProperties', null);
         App.db.setServiceConfigProperties(null);
-        router.transitionTo('step6');
+        addHostController.loadAdvancedConfigs();
+        router.transitionTo('step4');
       }
     }
   }),
 
-  step6: Em.Route.extend({
-    route: '/step6',
+  step4: Em.Route.extend({
+    route: '/step4',
     connectOutlets: function (router) {
-      console.log('in addhost.step6:connectOutlets');
+      console.log('in addHost.step4:connectOutlets');
       var controller = router.get('addHostController');
-      controller.setCurrentStep('6', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep7', controller.get('content'));
+      controller.setCurrentStep('4', false);
+      controller.dataLoading().done(function () {
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('wizardStep7', controller.get('content'));
+      })
     },
-    back: Em.Router.transitionTo('step5'),
+    back: Em.Router.transitionTo('step3'),
     next: function (router) {
       var addHostController = router.get('addHostController');
       var wizardStep7Controller = router.get('wizardStep7Controller');
       addHostController.saveServiceConfigProperties(wizardStep7Controller);
-      router.transitionTo('step7');
+      router.transitionTo('step5');
     }
   }),
 
-  step7: Em.Route.extend({
-    route: '/step7',
+  step5: Em.Route.extend({
+    route: '/step5',
     connectOutlets: function (router, context) {
-      console.log('in addHost.step7:connectOutlets');
+      console.log('in addHost.step5:connectOutlets');
       var controller = router.get('addHostController');
-      controller.setCurrentStep('7', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep8', controller.get('content'));
+      controller.setCurrentStep('5', false);
+      controller.dataLoading().done(function () {
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('wizardStep8', controller.get('content'));
+      })
     },
-    back: Em.Router.transitionTo('step6'),
+    back: Em.Router.transitionTo('step4'),
     next: function (router) {
       var addHostController = router.get('addHostController');
       var wizardStep8Controller = router.get('wizardStep8Controller');
       addHostController.installServices();
       addHostController.setInfoForStep9();
-      router.transitionTo('step8');
+      router.transitionTo('step6');
     }
   }),
 
-  step8: Em.Route.extend({
-    route: '/step8',
+  step6: Em.Route.extend({
+    route: '/step6',
     connectOutlets: function (router, context) {
-      console.log('in addHost.step8:connectOutlets');
+      console.log('in addHost.step6:connectOutlets');
       var controller = router.get('addHostController');
-      controller.setInfoForStep9();
-      controller.setCurrentStep('8', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep9', controller.get('content'));
+      controller.setCurrentStep('6', false);
+      controller.dataLoading().done(function () {
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('wizardStep9', controller.get('content'));
+      })
     },
-    back: Em.Router.transitionTo('step7'),
+    back: Em.Router.transitionTo('step5'),
     retry: function(router,context) {
       var addHostController = router.get('addHostController');
       var wizardStep9Controller = router.get('wizardStep9Controller');
       if (!wizardStep9Controller.get('isSubmitDisabled')) {
-        addHostController.installServices(true);
+        addHostController.installServices();
         addHostController.setInfoForStep9();
         wizardStep9Controller.navigateStep();
       }
@@ -219,20 +191,22 @@ module.exports = Em.Route.extend({
       var addHostController = router.get('addHostController');
       var wizardStep9Controller = router.get('wizardStep9Controller');
       addHostController.saveInstalledHosts(wizardStep9Controller);
-      router.transitionTo('step9');
+      router.transitionTo('step7');
     }
   }),
 
-  step9: Em.Route.extend({
-    route: '/step9',
+  step7: Em.Route.extend({
+    route: '/step7',
     connectOutlets: function (router, context) {
-      console.log('in addHost.step9:connectOutlets');
+      console.log('in addHost.step7:connectOutlets');
       var controller = router.get('addHostController');
-      controller.setCurrentStep('9', false);
-      controller.loadAllPriorSteps();
-      controller.connectOutlet('wizardStep10');
+      controller.setCurrentStep('7', false);
+      controller.dataLoading().done(function () {
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('wizardStep10', controller.get('content'));
+      })
     },
-    back: Em.Router.transitionTo('step8'),
+    back: Em.Router.transitionTo('step6'),
     complete: function (router, context) {
       if (true) {   // this function will be moved to installerController where it will validate
         var addHostController = router.get('addHostController');

+ 10 - 5
ambari-web/app/routes/add_service_routes.js

@@ -21,11 +21,16 @@ module.exports = Em.Route.extend({
 
   enter: function (router) {
     console.log('in /service/add:enter');
-
-    Ember.run.next(function () {
-      var addServiceController = router.get('addServiceController');
-      router.transitionTo('step' + addServiceController.get('currentStep'));
-    });
+    if (App.db.getUser().admin) {
+      Em.run.next(function () {
+        var addServiceController = router.get('addServiceController');
+        router.transitionTo('step' + addServiceController.get('currentStep'));
+      });
+    } else {
+      Em.run.next(function () {
+        App.router.transitionTo('main.services');
+      });
+    }
 
   },
 

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

@@ -26,14 +26,19 @@ module.exports = Em.Route.extend({
     console.log('in /installer:enter');
 
     if (router.getAuthenticated()) {
-      router.get('mainController').stopLoadOperationsPeriodically();
-      console.log('In installer with successful authenticated');
-      console.log('current step=' + router.get('installerController.currentStep'));
-      Ember.run.next(function () {
-        var installerController = router.get('installerController');
-        router.transitionTo('step' + installerController.get('currentStep'));
-      });
-
+      if (App.db.getUser().admin) {
+        router.get('mainController').stopLoadOperationsPeriodically();
+        console.log('In installer with successful authenticated');
+        console.log('current step=' + router.get('installerController.currentStep'));
+        Ember.run.next(function () {
+          var installerController = router.get('installerController');
+          router.transitionTo('step' + installerController.get('currentStep'));
+        });
+      } else {
+        Em.run.next(function () {
+          App.router.transitionTo('main.services');
+        });
+      }
     } else {
       console.log('In installer but its not authenticated');
       console.log('value of authenticated is: ' + router.getAuthenticated());

+ 8 - 2
ambari-web/app/routes/main.js

@@ -97,7 +97,7 @@ module.exports = Em.Route.extend({
 
     showDetails:function (router, event) {
       router.get('mainHostDetailsController').setBack(true);
-      router.transitionTo('hostDetails.index', event.context)
+      router.transitionTo('hostDetails.summary', event.context)
     },
 
     addHost:function (router) {
@@ -154,7 +154,13 @@ module.exports = Em.Route.extend({
 
   admin:Em.Route.extend({
     route:'/admin',
-
+    enter: function(){
+      if(!App.db.getUser().admin){
+        Em.run.next(function () {
+          App.router.transitionTo('main.dashboard');
+        });
+      }
+    },
     connectOutlets:function (router, context) {
       router.get('mainController').connectOutlet('mainAdmin');
     },

+ 10 - 3
ambari-web/app/styles/application.less

@@ -533,7 +533,7 @@ a:focus {
           width: 100%;
           text-align: left;
         }
-      } 
+      }
       .chartLabel {
         font-size: 11px;
         color: #7b7b7b;
@@ -644,6 +644,7 @@ a:focus {
 }
 
 .modal-graph-line {
+  width: 750px;
   .modal-body {
     min-height: 320px !important;
   }
@@ -678,9 +679,9 @@ a:focus {
     z-index: 2;
   }
   .chart-legend {
+    font-family: 'Courier New';
     position: absolute;
     top: 180px;
-    left: 35%;
     z-index: 2;
   }
   .rickshaw_legend {
@@ -743,6 +744,12 @@ a:focus {
         padding: 3px 10px;
       }
     }
+
+    li.clients {
+      a {
+        padding-left: 37px;
+      }
+    }
   }
   .add-service-button {
     margin: 20px 20px 10px;
@@ -1503,7 +1510,7 @@ ul.filter {
       margin-right: 10px;
     }
   }
-  
+
   h4{
     color: #777777;
     margin-top: 5px;

+ 4 - 0
ambari-web/app/styles/apps.less

@@ -278,6 +278,10 @@
     margin-bottom: 0;
   }
 
+  #bars {
+    height: 350px;
+  }
+
   #jobs, #bars{
     border: 1px solid #ddd;
     border-top: none;

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

@@ -18,8 +18,10 @@
 <div id="{{unbound view.id}}-container" class="chart-container" {{action showGraphInPopup target="view"}}>
   <div id="{{unbound view.id}}-yaxis" class="chart-y-axis"></div>
   <div id="{{unbound view.id}}-xaxis" class="chart-x-axis"></div>
+  <div id="{{unbound view.id}}-timeline" class="timeline"></div>
   <div id="{{unbound view.id}}-legend" class="chart-legend"></div>
   <div id="{{unbound view.id}}-overlay" class="chart-overlay"></div>
   <div id="{{unbound view.id}}-chart" class="chart"></div>
+
   <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
 </div>

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

@@ -137,7 +137,7 @@
         </a>
         <ul class="dropdown-menu">
           {{#each view.quickLinks}}
-            <li><a {{bindAttr href="url"}}>{{label}}</a></li>
+            <li><a {{bindAttr href="url"}} target="_blank">{{label}}</a></li>
           {{/each}}
         </ul>
       </div>

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

@@ -124,7 +124,7 @@
     </a>
     <ul class="dropdown-menu">
       {{#each view.quickLinks}}
-      <li><a {{bindAttr href="url"}}>{{label}}</a></li>
+      <li><a {{bindAttr href="url"}} target="_blank">{{label}}</a></li>
       {{/each}}
     </ul>
   </div>

+ 2 - 0
ambari-web/app/templates/main/host.hbs

@@ -17,6 +17,7 @@
 }}
 
 <div id="hosts">
+  {{#if controller.isAdmin}}
   <div class="box-header">
     <div class="button-section">
       <button class="btn btn-inverse add-host-button" {{action addHost}}>
@@ -25,6 +26,7 @@
       </button>
     </div>
   </div>
+  {{/if}}
   <table class="datatable table table-bordered table-striped" id="hosts-table">
     <thead>
     <tr>

+ 5 - 7
ambari-web/app/templates/main/host/add.hbs

@@ -30,13 +30,11 @@
               <li class="nav-header">{{t hosts.add.header}}</li>
               <li {{bindAttr class="isStep1:active view.isStep1Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep1 target="controller"}}>{{t installer.step2.header}}</a></li>
               <li {{bindAttr class="isStep2:active view.isStep2Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep2 target="controller"}}>{{t installer.step3.header}}</a></li>
-              <li {{bindAttr class="isStep3:active view.isStep3Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep3 target="controller"}}>{{t installer.step4.header}}</a></li>
-              <li {{bindAttr class="isStep4:active view.isStep4Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep4 target="controller"}}>{{t installer.step5.header}}</a></li>
-              <li {{bindAttr class="isStep5:active view.isStep5Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep5 target="controller"}}>{{t installer.step6.header}}</a></li>
-              <li {{bindAttr class="isStep6:active view.isStep6Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep6 target="controller"}}>{{t installer.step7.header}}</a></li>
-              <li {{bindAttr class="isStep7:active view.isStep7Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep7 target="controller"}}>{{t installer.step8.header}}</a></li>
-              <li {{bindAttr class="isStep8:active view.isStep8Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep8 target="controller"}}>{{t installer.step9.header}}</a></li>
-              <li {{bindAttr class="isStep9:active view.isStep9Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep9 target="controller"}}>{{t installer.step10.header}}</a></li>
+              <li {{bindAttr class="isStep3:active view.isStep3Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep3 target="controller"}}>{{t installer.step6.header}}</a></li>
+              <li {{bindAttr class="isStep4:active view.isStep4Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep4 target="controller"}}>{{t installer.step7.header}}</a></li>
+              <li {{bindAttr class="isStep5:active view.isStep5Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep5 target="controller"}}>{{t installer.step8.header}}</a></li>
+              <li {{bindAttr class="isStep6:active view.isStep6Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep6 target="controller"}}>{{t installer.step9.header}}</a></li>
+              <li {{bindAttr class="isStep7:active view.isStep7Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep7 target="controller"}}>{{t installer.step10.header}}</a></li>
             </ul>
           </div>
         </div>

+ 2 - 0
ambari-web/app/templates/main/host/details.hbs

@@ -20,6 +20,7 @@
   <a href='#' {{action "routeHome" target="controller"}}><i class="icon-home"></i></a> /
   <a href="javascript:void(null)" data-toggle="modal" {{action backToHostsList}}>Hosts</a> /
   <span {{bindAttr class=":host-title view.content.healthClass"}}>{{unbound view.content.hostName}}</span>
+  {{#if controller.isAdmin}}
   <div class="host-maintenance">
     <div class="host-maintenance-btn btn-group display-inline-block">
       <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
@@ -36,6 +37,7 @@
       </ul>
     </div>
   </div>
+  {{/if}}
   <div class="content">
     {{view App.MainHostMenuView}}
     {{outlet}}

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

@@ -19,12 +19,14 @@
 <div class="row-fluid">
   <div id="services-menu" class="well span2" style="padding: 8px 0">
     {{view App.MainServiceMenuView}}
+    {{#if controller.isAdmin}}
     <div class="add-service-button">
       <a class="btn" {{action addService href="true"}}>
         <i class="icon-plus"></i>
         Add Service
       </a>
     </div>
+    {{/if}}
   </div>
   <div class="span10">
     {{outlet}}

+ 9 - 0
ambari-web/app/templates/main/service/info/configs.hbs

@@ -136,12 +136,21 @@
 <div id="serviceConfig">
   <div class="accordion">
     {{#each category in selectedService.configCategories}}
+
       <div class="accordion-group">
+        {{#if category.isAdvanced}}
+        <div class="accordion-heading">
+          <a class="accordion-toggle" {{action "onToggleBlock" category.name target="view"}}>
+            {{category.name}}
+          </a>
+        </div>
+        {{else}}
         <div class="accordion-heading">
           <a class="accordion-toggle">
             {{category.name}}
           </a>
         </div>
+        {{/if}}
 
         {{#unless category.isForSlaveComponent}}
           {{#view App.ServiceConfigsByCategoryView categoryBinding="category" serviceConfigsBinding="selectedService.configs"}}

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

@@ -22,10 +22,10 @@
 {{#view App.QuickViewLinks contentBinding="view.service"}}
 <ul class="nav nav-pills move">
   <li class="dropdown">
-    <a class="dropdown-toggle" data-toggle="dropdown" href="#">Quick Links <b class="caret"></b></a>
+    <a class="dropdown-toggle" data-toggle="dropdown" href="#">Quick Links<b class="caret"></b></a>
     <ul class="dropdown-menu">
      {{#each view.quickLinks}}
-      <a {{bindAttr href="url"}}>{{label}}</a>
+      <a {{bindAttr href="url"}} {{bindAttr target="view.linkTarget"}}>{{label}}</a>
       {{/each}}
     </ul>
   </li>

+ 2 - 0
ambari-web/app/templates/main/service/item.hbs

@@ -18,6 +18,7 @@
 
 {{view App.MainServiceInfoMenuView configTabBinding="view.hasConfigTab"}}
 {{#unless controller.content.isClients}}
+{{#if controller.isAdmin}}
 <div class="service-button">
   {{#if view.hasMaintenanceControl}}
   <div class="btn-group display-inline-block">
@@ -46,5 +47,6 @@
     Stop
   </a>
 </div>
+{{/if}}
 {{/unless}}
 {{outlet}}

+ 10 - 11
ambari-web/app/utils/graph.js

@@ -19,9 +19,9 @@
 
 module.exports = {
   drawJobTimeLine:function (map, shuffle, reduce, w, h, element, legend_id, timeline_id) {
-    var map = $.parseJSON(map);
-    var shuffle = $.parseJSON(shuffle);
-    var reduce = $.parseJSON(reduce);
+    map = $.parseJSON(map);
+    shuffle = $.parseJSON(shuffle);
+    reduce = $.parseJSON(reduce);
     if (!map || !shuffle || !reduce) {
       console.warn('drawJobTimeLine');
       return;
@@ -99,12 +99,11 @@ module.exports = {
       element:document.getElementById(timeline_id)
     });
   },
-  drawJobTasks:function (mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, w, h, element, legend_id, timeline_id) {
-    var mapNodeLocal = $.parseJSON(mapNodeLocal);
-    var mapRackLocal = $.parseJSON(mapRackLocal);
-    var mapOffSwitch = $.parseJSON(mapOffSwitch);
-    var reduceOffSwitch = $.parseJSON(reduceOffSwitch);
-    console.log(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch);
+  drawJobTasks:function (mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, submitTime, w, h, element, legend_id, timeline_id) {
+    mapNodeLocal = $.parseJSON(mapNodeLocal);
+    mapRackLocal = $.parseJSON(mapRackLocal);
+    mapOffSwitch = $.parseJSON(mapOffSwitch);
+    reduceOffSwitch = $.parseJSON(reduceOffSwitch);
     if (!mapNodeLocal || !mapRackLocal || !mapOffSwitch || !reduceOffSwitch) {
       console.warn('drawJobTasks');
       return;
@@ -177,14 +176,14 @@ module.exports = {
     var hoverDetail = new Rickshaw.Graph.HoverDetail({
       graph:graph,
       xFormatter:function (x) {
-        return (x - json.submitTime) + 's'
+        return (x - submitTime) + 's'
       },
       yFormatter:function (y) {
         return y / 1000 + 's'
       },
       formatter:function (series, x, y, formattedX, formattedY, d) {
         var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
-        return swatch + d.label +
+        return swatch + (d.label? d.label: d.name) +
           '<br>Run-time: ' + formattedY + '<br>Wait-time: ' + formattedX;
       }
 

+ 54 - 6
ambari-web/app/utils/helper.js

@@ -20,6 +20,41 @@ String.prototype.trim = function () {
   return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
 };
 
+/**
+ * convert ip address string to long int
+ * @return {*}
+ */
+String.prototype.ip2long = function () {
+  // *     example 1: ip2long('192.0.34.166');
+  // *     returns 1: 3221234342
+  // *     example 2: ip2long('0.0xABCDEF');
+  // *     returns 2: 11259375
+  // *     example 3: ip2long('255.255.255.256');
+  // *     returns 3: false
+  var i = 0;
+  // PHP allows decimal, octal, and hexadecimal IP components.
+  // PHP allows between 1 (e.g. 127) to 4 (e.g 127.0.0.1) components.
+  var IP = this.match(/^([1-9]\d*|0[0-7]*|0x[\da-f]+)(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?$/i); // Verify IP format.
+  if (!IP) {
+    return false; // Invalid format.
+  }
+  // Reuse IP variable for component counter.
+  IP[0] = 0;
+  for (i = 1; i < 5; i += 1) {
+    IP[0] += !!((IP[i] || '').length);
+    IP[i] = parseInt(IP[i]) || 0;
+  }
+  // Continue to use IP for overflow values.
+  // PHP does not allow any component to overflow.
+  IP.push(256, 256, 256, 256);
+  // Recalculate overflow of last component supplied to make up for missing components.
+  IP[4 + IP[0]] *= Math.pow(256, 4 - IP[0]);
+  if (IP[1] >= IP[5] || IP[2] >= IP[6] || IP[3] >= IP[7] || IP[4] >= IP[8]) {
+    return false;
+  }
+  return IP[1] * (IP[0] === 1 || 16777216) + IP[2] * (IP[0] <= 2 || 65536) + IP[3] * (IP[0] <= 3 || 256) + IP[4] * 1;
+};
+
 String.prototype.capitalize = function () {
   return this.charAt(0).toUpperCase() + this.slice(1);
 }
@@ -30,30 +65,32 @@ Em.CoreObject.reopen({
   }
 });
 
-Handlebars.registerHelper('log', function (variable) {
+Em.Handlebars.registerHelper('log', function (variable) {
   console.log(variable);
 });
 
-Handlebars.registerHelper('warn', function (variable) {
+Em.Handlebars.registerHelper('warn', function (variable) {
   console.warn(variable);
 });
 
-Handlebars.registerHelper('highlight', function (variable, words) {
+Em.Handlebars.registerHelper('highlight', function (property, words, fn) {
+  var context = (fn.contexts && fn.contexts[0]) || this;
+  property = Em.Handlebars.getPath(context, property, fn);
+
   words = words.split(";");
 
-//  var self = this;
 //  if (highlightTemplate == undefined) {
   var highlightTemplate = "<b>{0}</b>";
 //  }
 
   words.forEach(function (word) {
     var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
-    variable = variable.replace(searchRegExp, function (found) {
+    property = property.replace(searchRegExp, function (found) {
       return highlightTemplate.format(found);
     });
   });
 
-  return new Handlebars.SafeString(variable);
+  return new Em.Handlebars.SafeString(property);
 })
 /**
  * Replace {i} with argument. where i is number of argument to replace with
@@ -130,6 +167,17 @@ Number.prototype.countPercentageRatio = function (maxValue) {
   return Math.round((usedValue / maxValue) * 100) + "%";
 }
 
+Number.prototype.long2ip = function () {
+  // http://kevin.vanzonneveld.net
+  // +   original by: Waldo Malqui Silva
+  // *     example 1: long2ip( 3221234342 );
+  // *     returns 1: '192.0.34.166'
+  if (!isFinite(this))
+    return false;
+
+  return [this >>> 24, this >>> 16 & 0xFF, this >>> 8 & 0xFF, this & 0xFF].join('.');
+}
+
 /**
  * Formats the given URL template by replacing keys in 'substitutes'
  * with their values. If not in App.testMode, the testUrl is used.

+ 55 - 0
ambari-web/app/utils/string_utils.js

@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module.exports = {
+
+  pad: function(str, len, pad, dir) {
+
+    var STR_PAD_LEFT = 1;
+    var STR_PAD_RIGHT = 2;
+    var STR_PAD_BOTH = 3;
+
+    if (typeof(len) == "undefined") { var len = 0; }
+    if (typeof(pad) == "undefined") { var pad = ' '; }
+    if (typeof(dir) == "undefined") { var dir = STR_PAD_RIGHT; }
+
+    if (len + 1 >= str.length) {
+
+      switch (dir){
+
+        case STR_PAD_LEFT:
+          str = Array(len + 1 - str.length).join(pad) + str;
+          break;
+
+        case STR_PAD_BOTH:
+          var right = Math.ceil((padlen = len - str.length) / 2);
+          var left = padlen - right;
+          str = Array(left+1).join(pad) + str + Array(right+1).join(pad);
+          break;
+
+        default:
+          str = str + Array(len + 1 - str.length).join(pad);
+          break;
+
+      } // switch
+
+    }
+    return str;
+
+  }
+}

+ 166 - 80
ambari-web/app/views/common/chart/linear_time.js

@@ -16,6 +16,7 @@
  */
 
 var App = require('app');
+var string_utils = require('utils/string_utils');
 
 /**
  * @class
@@ -81,6 +82,8 @@ App.ChartLinearTimeView = Ember.View.extend({
        */
       _graph: null,
 
+      _popupGraph: null,
+
       popupSuffix: '-popup',
 
       isPopup: false,
@@ -108,6 +111,14 @@ App.ChartLinearTimeView = Ember.View.extend({
       didInsertElement: function () {
         this._super();
         this.loadData();
+        this.registerGraph()
+      },
+      registerGraph: function(){
+        var graph = {
+          name: this.get('title'),
+          id: this.get('elementId')
+        };
+        App.router.get('clusterController.graphs').push(graph);
       },
 
       loadData: function() {
@@ -193,12 +204,26 @@ App.ChartLinearTimeView = Ember.View.extend({
        * 
        * @type Function
        */
-      yAxisFormatter: Rickshaw.Fixtures.Number.formatKMBT,
+      yAxisFormatter: function(y) {
+        var value = Rickshaw.Fixtures.Number.formatKMBT(y);
+        if (value == '') return '0';
+        value = String(value);
+        var c = value[value.length - 1];
+        if (!isNaN(parseInt(c))) {
+          // c is digit
+          value = parseFloat(value).toFixed(3);
+        }
+        else {
+          // c in not digit
+          value = parseFloat(value.substr(0, value.length - 1)).toFixed(3) + c;
+        }
+        return value;
+      },
 
       /**
        * Provides the color (in any HTML color format) to use for a particular
        * series.
-       * 
+       *
        * @param series
        *          Series for which color is being requested
        * @return color String. Returning null allows this chart to pick a color
@@ -250,82 +275,131 @@ App.ChartLinearTimeView = Ember.View.extend({
       },
 
       draw: function(seriesData) {
-          var isPopup = this.get('isPopup');
-          var p = '';
+        var isPopup = this.get('isPopup');
+        var p = '';
+        if (isPopup) {
+          p = this.get('popupSuffix');
+        }
+        var palette = new Rickshaw.Color.Palette({
+          scheme: this._paletteScheme
+        });
+        seriesData.forEach(function (series) {
+          series.color = /*this.colorForSeries(series) ||*/ palette.color();
+          series.stroke = 'rgba(0,0,0,0.3)';
           if (isPopup) {
-            p = this.get('popupSuffix');
+            // calculate statistic data for popup legend
+            var avg = 0;
+            var min = Number.MAX_VALUE;
+            var max = Number.MIN_VALUE;
+            for (var i = 0; i < series.data.length; i++) {
+              avg += series.data[i]['y'];
+              if (series.data[i]['y'] < min) {
+                min = series.data[i]['y'];
+              }
+              else {
+                if (series.data[i]['y'] > max) {
+                  max = series.data[i]['y'];
+                }
+              }
+            }
+            series.name = string_utils.pad(series.name, 30, '&nbsp;', 2) + string_utils.pad('min', 5, '&nbsp;', 3) + string_utils.pad(this.get('yAxisFormatter')(min), 12, '&nbsp;', 3) + string_utils.pad('avg', 5, '&nbsp;', 3) + string_utils.pad(this.get('yAxisFormatter')(avg/series.data.length), 12, '&nbsp;', 3) + string_utils.pad('max', 12, '&nbsp;', 3) + string_utils.pad(this.get('yAxisFormatter')(max), 5, '&nbsp;', 3);
           }
-          var palette = new Rickshaw.Color.Palette({
-            scheme: this._paletteScheme
+        }.bind(this));
+        var chartId = "#" + this.id + "-chart" + p;
+        var chartOverlayId = "#" + this.id + "-overlay" + p;
+        var xaxisElementId = "#" + this.id + "-xaxis" + p;
+        var yaxisElementId = "#" + this.id + "-yaxis" + p;
+        var legendElementId = "#" + this.id + "-legend" + p;
+        var timelineElementId = "#" + this.id + "-timeline" + p;
+
+        var chartElement = document.querySelector(chartId);
+        var overlayElement = document.querySelector(chartOverlayId);
+        var xaxisElement = document.querySelector(xaxisElementId);
+        var yaxisElement = document.querySelector(yaxisElementId);
+        var legendElement = document.querySelector(legendElementId);
+        var timelineElement = document.querySelector(timelineElementId);
+
+        var _graph = new Rickshaw.Graph({
+          height: 150,
+          element: chartElement,
+          series: seriesData,
+          interpolation: 'step-after',
+          stroke: true,
+          renderer: 'area',
+          strokeWidth: 1
+        });
+        _graph.renderer.unstack = false;
+
+        xAxis = new Rickshaw.Graph.Axis.Time({
+          graph: _graph
+        });
+
+        yAxis = new Rickshaw.Graph.Axis.Y({
+          tickFormat: this.yAxisFormatter,
+          element: yaxisElement,
+          graph: _graph
+        });
+
+        var legend = new Rickshaw.Graph.Legend({
+          graph: _graph,
+          element: legendElement
+        });
+
+        if (!isPopup) {
+          overlayElement.addEventListener('mousemove', function () {
+            $(xaxisElement).removeClass('hide');
+            $(legendElement).removeClass('hide');
+            $(chartElement).children("div").removeClass('hide');
+          });
+          overlayElement.addEventListener('mouseout', function () {
+            $(legendElement).addClass('hide');
           });
-          seriesData.forEach(function (series) {
-            series.color = this.colorForSeries(series) || palette.color();
-            series.stroke = 'rgba(0,0,0,0.3)';
-          }.bind(this));
-            var chartId = "#" + this.id + "-chart" + p;
-            var chartOverlayId = "#" + this.id + "-overlay" + p;
-            var xaxisElementId = "#" + this.id + "-xaxis" + p;
-            var yaxisElementId = "#" + this.id + "-yaxis" + p;
-            var legendElementId = "#" + this.id + "-legend" + p;
-
-            var chartElement = document.querySelector(chartId);
-            var overlayElement = document.querySelector(chartOverlayId);
-            var xaxisElement = document.querySelector(xaxisElementId);
-            var yaxisElement = document.querySelector(yaxisElementId);
-            var legendElement = document.querySelector(legendElementId);
-            this._graph = new Rickshaw.Graph({
-              height: 150,
-              element: chartElement,
-              series: seriesData,
-              interpolation: 'step-after',
-              stroke: true,
-              renderer: 'area',
-              strokeWidth: 1
-            });
-            this._graph.renderer.unstack = true;
-
-            xAxis = new Rickshaw.Graph.Axis.Time({
-              graph: this._graph
-            });
-            yAxis = new Rickshaw.Graph.Axis.Y({
-              tickFormat: this.yAxisFormatter,
-              element: yaxisElement,
-              graph: this._graph
-            });
-
-            overlayElement.addEventListener('mousemove', function () {
-              $(xaxisElement).removeClass('hide');
-              $(legendElement).removeClass('hide');
-              $(chartElement).children("div").removeClass('hide');
-            });
-            overlayElement.addEventListener('mouseout', function () {
-              //$(xaxisElement).addClass('hide');
-              $(legendElement).addClass('hide');
-              //$(chartElement).children("div").addClass('hide');
-            });
-            // Hide axes
-            this._graph.onUpdate(function () {
-              //$(xaxisElement).addClass('hide');
-              $(legendElement).addClass('hide');
-              //$(chartElement).children('div').addClass('hide');
-            });
-
-            new Rickshaw.Graph.Legend({
-              graph: this._graph,
-              element: legendElement
-            });
-
-            // The below code will be needed if we ever use curve
-            // smoothing in our graphs. (see rickshaw defect below)
-            // this._graph.onUpdate(jQuery.proxy(function () {
-            // this._adjustSVGHeight();
-            // }, this));
-
-        this._graph.render();
-        this._graph = null;
+          _graph.onUpdate(function () {
+            $(legendElement).addClass('hide');
+          });
+        }
+
+       var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
+          graph: _graph,
+          legend:legend
+        });
+
+        var order = new Rickshaw.Graph.Behavior.Series.Order({
+          graph: _graph,
+          legend:legend
+        });
+
+        var annotator = new Rickshaw.Graph.Annotate({
+          graph: _graph,
+          element:timelineElement
+        });
+
+        _graph.render();
+
+        var hoverDetail = new Rickshaw.Graph.HoverDetail({
+          graph: _graph,
+          onShow: function() {
+            console.log('show');
+          },
+          onHide: function() {
+            console.log('hide');
+          },
+          onRender: function() {
+            console.log('render');
+          }
+        });
+
+        if (isPopup) {
+          this.set('_popupGraph', _graph);
+        }
+        else {
+          this.set('_graph', _graph);
+        }
+
         this.set('isPopup', false);
       },
 
+
       showGraphInPopup: function() {
         this.set('isPopup', true);
         App.ModalPopup.show({
@@ -340,7 +414,17 @@ App.ChartLinearTimeView = Ember.View.extend({
             '</div>',
             '<div class="modal-body">',
             '{{#if bodyClass}}{{view bodyClass}}',
-            '{{else}}<div id="'+this.get('id')+'-container'+this.get('popupSuffix')+'" class="chart-container"><div id="'+this.get('id')+'-yaxis'+this.get('popupSuffix')+'" class="'+this.get('id')+'-yaxis chart-y-axis"></div><div id="'+this.get('id')+'-xaxis'+this.get('popupSuffix')+'" class="'+this.get('id')+'-xaxis chart-x-axis"></div><div id="'+this.get('id')+'-legend'+this.get('popupSuffix')+'" class="'+this.get('id')+'-legend chart-legend"></div><div id="'+this.get('id')+'-overlay'+this.get('popupSuffix')+'" class="'+this.get('id')+'-overlay chart-overlay"></div><div id="'+this.get('id')+'-chart'+this.get('popupSuffix')+'" class="'+this.get('id')+'-chart chart"></div><div id="'+this.get('id')+'-title'+this.get('popupSuffix')+'" class="'+this.get('id')+'-title chart-title">{{view.title}}</div></div>{{/if}}',
+            '{{else}}'+
+              '<div id="'+this.get('id')+'-container'+this.get('popupSuffix')+'" class="chart-container">'+
+                '<div id="'+this.get('id')+'-yaxis'+this.get('popupSuffix')+'" class="'+this.get('id')+'-yaxis chart-y-axis"></div>'+
+                '<div id="'+this.get('id')+'-xaxis'+this.get('popupSuffix')+'" class="'+this.get('id')+'-xaxis chart-x-axis"></div>'+
+                '<div id="'+this.get('id')+'-legend'+this.get('popupSuffix')+'" class="'+this.get('id')+'-legend chart-legend"></div>'+
+                '<div id="'+this.get('id')+'-overlay'+this.get('popupSuffix')+'" class="'+this.get('id')+'-overlay chart-overlay"></div>'+
+                '<div id="'+this.get('id')+'-chart'+this.get('popupSuffix')+'" class="'+this.get('id')+'-chart chart"></div>'+
+                '<div id="'+this.get('id')+'-title'+this.get('popupSuffix')+'" class="'+this.get('id')+'-title chart-title">{{view.title}}</div>'+
+                '<div id="'+this.get('id')+'-timeline'+this.get('popupSuffix')+'" class="'+this.get('id')+'-timeline timeline"></div>'+
+              '</div>'+
+            '{{/if}}',
             '</div>',
             '<div class="modal-footer">',
             '{{#if view.primary}}<a class="btn btn-success" {{action onPrimary target="view"}}>{{view.primary}}</a>{{/if}}',
@@ -368,10 +452,12 @@ App.ChartLinearTimeView = Ember.View.extend({
  * @type Function
  */
 App.ChartLinearTimeView.BytesFormatter = function (y) {
+  if (y == 0) return '0 B';
   var value = Rickshaw.Fixtures.Number.formatBase1024KMGTP(y);
   if (!y || y.length < 1) {
-    value = '';
-  } else {
+    value = '0 B';
+  }
+  else {
     if ("number" == typeof value) {
       value = String(value);
     }
@@ -395,9 +481,9 @@ App.ChartLinearTimeView.BytesFormatter = function (y) {
 App.ChartLinearTimeView.PercentageFormatter = function (percentage) {
   var value = percentage;
   if (!value || value.length < 1) {
-    value = '';
+    value = '0 %';
   } else {
-    value = value + '%';
+    value = value.toFixed(3) + '%';
   }
   return value;
 };
@@ -411,7 +497,7 @@ App.ChartLinearTimeView.PercentageFormatter = function (percentage) {
 App.ChartLinearTimeView.TimeElapsedFormatter = function (millis) {
   var value = millis;
   if (!value || value.length < 1) {
-    value = '';
+    value = '0 ms';
   } else if ("number" == typeof millis) {
     var seconds = millis > 1000 ? Math.round(millis / 1000) : 0;
     var minutes = seconds > 60 ? Math.round(seconds / 60) : 0;
@@ -426,9 +512,9 @@ App.ChartLinearTimeView.TimeElapsedFormatter = function (millis) {
     } else if (seconds > 0) {
       value = seconds + ' s';
     } else if (millis > 0) {
-      value = millis + ' ms';
+      value = millis.toFixed(3) + ' ms';
     } else {
-      value = millis + ' ms';
+      value = millis.toFixed(3) + ' ms';
     }
   }
   return value;

+ 21 - 8
ambari-web/app/views/common/quick_view_link_view.js

@@ -23,26 +23,39 @@ App.QuickViewLinks = Em.View.extend({
   /**
    * Updated quick links. Here we put correct hostname to url
    */
-  quickLinks : function(){
+  quickLinks:function () {
     var serviceName = this.get('content.serviceName');
     var components = this.get('content.components');
     var host;
 
-    if(serviceName === 'HDFS'){
+    if (serviceName === 'HDFS') {
       host = components.filterProperty('id', 'NAMENODE').objectAt(0).get('host.hostName');
-    } else if(serviceName === 'MAPREDUCE'){
+    } else if (serviceName === 'MAPREDUCE') {
       host = components.filterProperty('id', 'JOBTRACKER').objectAt(0).get('host.hostName');
-    } else if(serviceName === 'HBASE'){
+    } else if (serviceName === 'HBASE') {
       host = components.filterProperty('id', 'HBASE_MASTER').objectAt(0).get('host.hostName');
     }
-    if(!host){
+    if (!host) {
       return [];
     }
-    return this.get('content.quickLinks').map(function(item){
-      if(item.get('url')){
+    return this.get('content.quickLinks').map(function (item) {
+      if (item.get('url')) {
         item.set('url', item.get('url').fmt(host));
       }
       return item;
     });
-  }.property('content.quickLinks.@each.label')
+  }.property('content.quickLinks.@each.label'),
+
+  linkTarget:function () {
+    switch (this.get('content.serviceName').toLowerCase()) {
+      case "hdfs":
+      case "mapreduce":
+        return "_blank";
+        break;
+      default:
+        return "";
+        break;
+    }
+  }.property('service')
+
 });

+ 6 - 4
ambari-web/app/views/main/apps/item/bar_view.js

@@ -71,8 +71,8 @@ App.MainAppsItemBarView = Em.View.extend({
   mapRackLocal:false,
   mapOffSwitch:false,
   reduceOffSwitch:false,
-  submitTime:false,
-  finishTime:false,
+  submit:false,
+  finish:false,
 
   updateTimeLine:function () {
     var url = App.testMode ? '/data/apps/jobs/timeline.json' : App.apiPrefix + "/jobhistory/task?jobId=" + this.get('activeJob').get('id') + 
@@ -93,6 +93,7 @@ App.MainAppsItemBarView = Em.View.extend({
     var map = JSON.stringify(this.get('map'));
     var shuffle = JSON.stringify(this.get('shuffle'));
     var reduce = JSON.stringify(this.get('reduce'));
+    if (!this.get('map') || !this.get('shuffle') || !this.get('reduce')) {return;}
     $('#chart, #legend, #timeline1').html('');
     graph.drawJobTimeLine(map, shuffle, reduce, this.get('width'), this.get('height'), '#chart', 'legend', 'timeline1');
   }.observes('map', 'shuffle', 'reduce'),
@@ -102,8 +103,9 @@ App.MainAppsItemBarView = Em.View.extend({
     var mapRackLocal = JSON.stringify(this.get('mapRackLocal'));
     var mapOffSwitch = JSON.stringify(this.get('mapOffSwitch'));
     var reduceOffSwitch = JSON.stringify(this.get('reduceOffSwitch'));
+    if (!this.get('mapNodeLocal') || !this.get('mapRackLocal') || !this.get('mapOffSwitch') || !this.get('reduceOffSwitch')) {return;}
     $('#job_tasks, #tasks_legend, #timeline2').html('');
-    graph.drawJobTasks(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, this.get('width'), this.get('height'), '#job_tasks', 'tasks_legend', 'timeline2');
-  }.observes('mapNodeLocal', 'mapRackLocal', 'mapOffSwitch', 'reduceOffSwitch')
+    graph.drawJobTasks(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, this.get('submit'), this.get('width'), this.get('height'), '#job_tasks', 'tasks_legend', 'timeline2');
+  }.observes('mapNodeLocal', 'mapRackLocal', 'mapOffSwitch', 'reduceOffSwitch', 'submit')
 
 });

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

@@ -63,7 +63,7 @@ App.MainDashboardView = Em.View.extend({
       })
     }, this);
 
-  }.observes('App.router.clusterController.dataLoadList.services'),
+  }.observes('App.router.updateController.isUpdated'),
   
   gangliaUrl: function () {
     return App.router.get('clusterController.gangliaUrl') + "/?r=hour&cs=&ce=&m=&s=by+name&c=HDPSlaves&tab=m&vn=";

+ 1 - 0
ambari-web/app/views/main/dashboard/cluster_metrics/network.js

@@ -58,6 +58,7 @@ App.ChartClusterMetricsNetwork = App.ChartLinearTimeView.extend({
               y: seriesData[index][0]
             });
           }
+
           seriesArray.push(series);
         }
       }

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

@@ -59,6 +59,6 @@ App.MainDashboardServiceHbaseView = App.MainDashboardServiceView.extend({
   }.property('components'),
 
   toggleInfoView: function() {
-    $('#hbase-info').toggle('blind', 1000);
+    $('#hbase-info').toggle('blind', 200);
   }
 });

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

@@ -74,6 +74,6 @@ App.MainDashboardServiceHdfsView = App.MainDashboardServiceView.extend({
   }.property('+'),
 
   toggleInfoView: function() {
-    $('#hdfs-info').toggle('blind', 1000);
+    $('#hdfs-info').toggle('blind', 200);
   }
 });

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

@@ -104,6 +104,6 @@ App.MainDashboardServiceMapreduceView = App.MainDashboardServiceView.extend({
   }.property('components'),
 
   toggleInfoView: function() {
-    $('#mapreduce-info').toggle('blind', 1000);
+    $('#mapreduce-info').toggle('blind', 200);
   }
 });

+ 14 - 11
ambari-web/app/views/main/menu.js

@@ -25,18 +25,21 @@ var App = require('app');
 App.MainMenuView = Em.CollectionView.extend({
   tagName:'ul',
   classNames:['nav'],
-  content:[
-    { label:'Dashboard', routing:'dashboard', active:'active'},
-    { label:'Charts', routing:'charts'},
-    { label:'Services', routing:'services'},
-    { label:'Hosts', routing:'hosts'},
-    { label:'Apps', routing:'apps'},
-    { label:'Admin', routing:'admin'}
-  ],
+  content:function(){
+    var result = [
+      { label:'Dashboard', routing:'dashboard', active:'active'},
+      { label:'Charts', routing:'charts'},
+      { label:'Services', routing:'services'},
+      { label:'Hosts', routing:'hosts'},
+      { label:'Apps', routing:'apps'}
 
-  /**
-   *    Adds observer on lastSetURL and calls navigation sync procedure
-   */
+    ];
+      if(App.db.getUser().admin) result.push({ label:'Admin', routing:'admin'});
+    return result;
+  }.property(),
+    /**
+     *    Adds observer on lastSetURL and calls navigation sync procedure
+     */
   didInsertElement:function () {
     App.router.location.addObserver('lastSetURL', this, 'renderOnRoute');
     this.renderOnRoute();

+ 10 - 2
ambari-web/app/views/main/service/info/configs.js

@@ -23,6 +23,11 @@ App.MainServiceInfoConfigsView = Em.View.extend({
   didInsertElement: function () {
     var controller = this.get('controller');
     controller.loadStep();
+    var advanced = this.get('controller.selectedService.configCategories').filterProperty('name', 'Advanced');
+    if(advanced.length) advanced.objectAt(0).set('isAdvanced', true);
+  },
+  onToggleBlock: function(event){
+    $("#" + event.context).toggle('blind', 500);
   }
 });
 
@@ -30,12 +35,15 @@ App.ServiceConfigsByCategoryView = Ember.View.extend({
 
   content: null,
 
+
   category: null,
   serviceConfigs: null, // General, Advanced, NameNode, SNameNode, DataNode, etc.
 
   categoryConfigs: function () {
     return this.get('serviceConfigs').filterProperty('category', this.get('category.name'))
   }.property('serviceConfigs.@each').cacheable(),
-
-  layout: Ember.Handlebars.compile('<div class="accordion-body collapse in"><div class="accordion-inner">{{yield}}</div></div>')
+  didInsertElement: function(){
+    $("#Advanced").hide();
+  },
+  layout: Ember.Handlebars.compile('<div {{bindAttr id="view.category.name"}} class="accordion-body collapse in"><div class="accordion-inner">{{yield}}</div></div>')
 });

+ 5 - 7
ambari-web/app/views/main/service/info/summary.js

@@ -19,9 +19,8 @@ var App = require('app');
 
 App.MainServiceInfoSummaryView = Em.View.extend({
   templateName:function () {
-    var service = this.get('controller.content');
-    return require(service.get('isClients') ? 'templates/main/service/info/client_summary' : 'templates/main/service/info/summary');
-  }.property('controller.content'),
+    return require(this.get('service.isClients') ? 'templates/main/service/info/client_summary' : 'templates/main/service/info/summary');
+  }.property('service'),
   attributes:null,
   serviceStatus:{
     hdfs:false,
@@ -148,8 +147,8 @@ App.MainServiceInfoSummaryView = Em.View.extend({
     this.set('oldServiceName', serviceName);
     serviceName = serviceName.toLowerCase();
   }.observes('serviceName'),
-  
-  gangliaUrl: function () {
+
+  gangliaUrl:function () {
     var gangliaUrl = App.router.get('clusterController.gangliaUrl');
     var svcName = this.get('service.serviceName');
     if (svcName) {
@@ -169,7 +168,7 @@ App.MainServiceInfoSummaryView = Em.View.extend({
     }
     return gangliaUrl;
   }.property('App.router.clusterController.gangliaUrl', 'service.serviceName'),
-  
+
   didInsertElement:function () {
     // We have to make the height of the Alerts section
     // match the height of the Summary section.
@@ -186,7 +185,6 @@ App.MainServiceInfoSummaryView = Em.View.extend({
     }
   },
 
-
   clientHosts:App.Host.find(),
 
   clientHostsLength:function () {

+ 3 - 3
ambari-web/app/views/main/service/item.js

@@ -24,9 +24,9 @@ App.MainServiceItemView = Em.View.extend({
     var options = [];
     var service = this.get('controller.content');
     switch(service.get('serviceName')) {
-      case 'HDFS':
-        options.push({action: 'runRebalancer', 'label': Em.I18n.t('services.service.actions.run.rebalancer')});
-        break;
+//      case 'HDFS':
+//        options.push({action: 'runRebalancer', 'label': Em.I18n.t('services.service.actions.run.rebalancer')});
+//        break;
       case 'HBASE':
         options.push({action: 'runCompaction', 'label': Em.I18n.t('services.service.actions.run.compaction')});
         break;

+ 5 - 2
ambari-web/app/views/main/service/menu.js

@@ -61,7 +61,7 @@ App.MainServiceMenuView = Em.CollectionView.extend({
   classNames:["nav", "nav-list", "nav-services"],
 
   itemViewClass:Em.View.extend({
-    classNameBindings:["active"],
+    classNameBindings:["active", "clients"],
     active:function () {
       return this.get('content.id') == this.get('parentView.activeServiceId') ? 'active' : '';
     }.property('parentView.activeServiceId'),
@@ -73,7 +73,10 @@ App.MainServiceMenuView = Em.CollectionView.extend({
       }
       return 0;
     }.property('App.router.clusterController.alerts'),
-
+    clients: function(){
+      var content = this.get('content');
+      return this.get('content.isClients') ? "clients" : "";
+    }.property("content"),
     templateName:require('templates/main/service/menu_item')
   })
 });

+ 53 - 19
ambari-web/app/views/wizard/step5_view.js

@@ -21,9 +21,9 @@ var App = require('app');
 
 App.WizardStep5View = Em.View.extend({
 
-  templateName: require('templates/wizard/step5'),
+  templateName:require('templates/wizard/step5'),
 
-  didInsertElement: function () {
+  didInsertElement:function () {
     var controller = this.get('controller');
     controller.loadStep();
 
@@ -39,39 +39,73 @@ App.WizardStep5View = Em.View.extend({
 });
 
 App.SelectHostView = Em.Select.extend({
-  content: [],
-  zId: null,
-  selectedHost: null,
-  serviceName: null,
-  attributeBindings: ['disabled'],
+  content:[],
+  zId:null,
+  selectedHost:null,
+  serviceName:null,
+  attributeBindings:['disabled'],
 
-  change: function () {
+  filterContent:function () {
+    this.get('content').sort(function (a, b) {
+      if (a.get('memory') == b.get('memory')) {
+        if (a.get('cpu') == b.get('cpu')) {
+
+//          try to compare as ipaddresses
+          if (a.get('host_name').ip2long() && b.get('host_name').ip2long()) {
+            return a.get('host_name').ip2long() - b.get('host_name').ip2long(); // hostname asc
+          }
+
+//          try to compare as strings
+          if (a.get('host_name') > b.get('host_name')) {
+            return 1;
+          }
+
+          if (b.get('host_name') > a.get('host_name')) {
+            return -1;
+          }
+
+          return 0;
+        }
+        return b.get('cpu') - a.get('cpu'); // cores desc
+      }
+
+      return b.get('memory') - a.get('memory'); // ram desc
+    });
+
+  }.observes('content'),
+
+  init:function () {
+    this._super();
+    this.propertyDidChange('content');
+  },
+
+  change:function () {
     this.get('controller').assignHostToMaster(this.get("serviceName"), this.get("value"), this.get("zId"));
   },
 
-  didInsertElement: function () {
+  didInsertElement:function () {
     this.set("value", this.get("selectedHost"));
   }
 });
 
 App.AddControlView = Em.View.extend({
-  componentName: null,
-  tagName: "span",
-  classNames: ["badge", "badge-important"],
-  template: Ember.Handlebars.compile('+'),
+  componentName:null,
+  tagName:"span",
+  classNames:["badge", "badge-important"],
+  template:Ember.Handlebars.compile('+'),
 
-  click: function () {
+  click:function () {
     this.get('controller').addZookeepers();
   }
 });
 
 App.RemoveControlView = Em.View.extend({
-  zId: null,
-  tagName: "span",
-  classNames: ["badge", "badge-important"],
-  template: Ember.Handlebars.compile('-'),
+  zId:null,
+  tagName:"span",
+  classNames:["badge", "badge-important"],
+  template:Ember.Handlebars.compile('-'),
 
-  click: function () {
+  click:function () {
     this.get('controller').removeZookeepers(this.get("zId"));
   }
 });

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

@@ -1,1831 +1,1831 @@
-/*
- * jQuery timepicker addon
- * By: Trent Richardson [http://trentrichardson.com]
- * Version 1.0.5
- * Last Modified: 10/06/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
- */
-
-/*jslint evil: true, white: false, undef: false, nomen: false */
-
-(function($) {
-
-	/*
-	* Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded"
-	*/
-	$.ui.timepicker = $.ui.timepicker || {};
-	if ($.ui.timepicker.version) {
-		return;
-	}
-
-	/*
-	* Extend jQueryUI, get it started with our version number
-	*/
-	$.extend($.ui, {
-		timepicker: {
-			version: "1.0.5"
-		}
-	});
-
-	/* 
-	* Timepicker 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',
-			isRTL: false
-		};
-		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,
-			altSeparator: null,
-			altTimeSuffix: null,
-			showTimepicker: true,
-			timezoneIso8601: false,
-			timezoneList: null,
-			addSliderAccess: false,
-			sliderAccessArgs: null,
-			controlType: 'slider',
-			defaultValue: 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,
-		units: ['hour','minute','second','millisec'],
-		control: 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 = {},
-                fns = {},
-		        overrides, i;
-
-			for (var attrName in this._defaults) {
-				if(this._defaults.hasOwnProperty(attrName)){
-					var attrValue = $input.attr('time:' + attrName);
-					if (attrValue) {
-						try {
-							inlineSettings[attrName] = eval(attrValue);
-						} catch (err) {
-							inlineSettings[attrName] = attrValue;
-						}
-					}
-				}
-			}
-		    overrides = {
-		        beforeShow: function (input, dp_inst) {
-		            if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) {
-		                return tp_inst._defaults.evnts.beforeShow.call($input[0], 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(tp_inst._defaults.evnts.onChangeMonthYear)) {
-		                tp_inst._defaults.evnts.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(tp_inst._defaults.evnts.onClose)) {
-		                tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst);
-		            }
-		        }
-		    };
-		    for (i in overrides) {
-		        if (overrides.hasOwnProperty(i)) {
-		            fns[i] = o[i] || null;
-		        }
-		    }
-		    tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, overrides, {
-		        evnts:fns,
-		        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();
-			});
-
-			// controlType is string - key to our this._controls
-			if(typeof(tp_inst._defaults.controlType) === 'string'){
-				if(tp_inst._defaults.controlType == 'slider' && $.fn.slider === undefined){
-					tp_inst._defaults.controlType = 'select';
-				}
-				tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType];
-			}
-			// controlType is an object and must implement create, options, value methods
-			else{ 
-				tp_inst.control = tp_inst._defaults.controlType;
-			}
-
-			if (tp_inst._defaults.timezoneList === null) {
-				var timezoneList = ['-1200', '-1100', '-1000', '-0930', '-0900', '-0800', '-0700', '-0600', '-0500', '-0430', '-0400', '-0330', '-0300', '-0200', '-0100', '+0000', 
-									'+0100', '+0200', '+0300', '+0330', '+0400', '+0430', '+0500', '+0530', '+0545', '+0600', '+0630', '+0700', '+0800', '+0845', '+0900', '+0930', 
-									'+1000', '+1030', '+1100', '+1130', '+1200', '+1245', '+1300', '+1400'];
-
-				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());
-			}
-			tp_inst.$input.bind('focus', function() {
-				tp_inst._onFocus();
-			});
-
-			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.inst.settings,
-				tp_inst = this,
-				litem = '',
-				uitem = '',
-				max = {},
-				gridSize = {},
-				size = null;
-
-			// Prevent displaying twice
-			if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) {
-				var noDisplay = ' style="display:none;"',
-					html = '<div class="ui-timepicker-div'+ (o.isRTL? ' ui-timepicker-rtl' : '') +'"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + 
-								'<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>';
-
-				// Create the markup
-				for(var i=0,l=this.units.length; i<l; i++){
-					litem = this.units[i];
-					uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
-					// 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.
-					max[litem] = parseInt((o[litem+'Max'] - ((o[litem+'Max'] - o[litem+'Min']) % o['step'+uitem])), 10);
-					gridSize[litem] = 0;
-
-					html += '<dt class="ui_tpicker_'+ litem +'_label"' + ((o['show'+uitem]) ? '' : noDisplay) + '>' + o[litem +'Text'] + '</dt>' + 
-								'<dd class="ui_tpicker_'+ litem +'"><div class="ui_tpicker_'+ litem +'_slider"' + ((o['show'+uitem]) ? '' : noDisplay) + '></div>';
-
-					if (o['show'+uitem] && o[litem+'Grid'] > 0) {
-						html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
-
-						if(litem == 'hour'){
-							for (var h = o[litem+'Min']; h <= max[litem]; h += parseInt(o[litem+'Grid'], 10)) {
-								gridSize[litem]++;
-								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 data-for="'+litem+'">' + tmph + '</td>';
-							}
-						}
-						else{
-							for (var m = o[litem+'Min']; m <= max[litem]; m += parseInt(o[litem+'Grid'], 10)) {
-								gridSize[litem]++;
-								html += '<td data-for="'+litem+'">' + ((m < 10) ? '0' : '') + m + '</td>';
-							}
-						}
-
-						html += '</tr></table></div>';
-					}
-					html += '</dd>';
-				}
-				
-				// Timezone
-				html += '<dt class="ui_tpicker_timezone_label"' + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
-				html += '<dd class="ui_tpicker_timezone" ' + ((o.showTimezone) ? '' : noDisplay) + '></dd>';
-
-				// Create the elements from string
-				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();
-				}
-				
-				// add sliders, adjust grids, add events
-				for(var i=0,l=tp_inst.units.length; i<l; i++){
-					litem = tp_inst.units[i];
-					uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
-					
-					// add the slider
-					tp_inst[litem+'_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_'+litem+'_slider'), litem, tp_inst[litem], o[litem+'Min'], max[litem], o['step'+uitem]);
-
-					// adjust the grid and add click event
-					if (o['show'+uitem] && o[litem+'Grid'] > 0) {
-						size = 100 * gridSize[litem] * o[litem+'Grid'] / (max[litem] - o[litem+'Min']);
-						$tp.find('.ui_tpicker_'+litem+' table').css({
-							width: size + "%",
-							marginLeft: o.isRTL? '0' : ((size / (-2 * gridSize[litem])) + "%"),
-							marginRight: o.isRTL? ((size / (-2 * gridSize[litem])) + "%") : '0',
-							borderCollapse: 'collapse'
-						}).find("td").click(function(e){
-								var $t = $(this),
-									h = $t.html(),
-									f = $t.data('for'); // loses scope, so we use data-for
-
-								if (f == 'hour' && 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.control.value(tp_inst, tp_inst[f+'_slider'], parseInt(h,10));
-
-								tp_inst._onTimeChange();
-								tp_inst._onSelectHandler();
-							})
-						.css({
-								cursor: 'pointer',
-								width: (100 / gridSize[litem]) + '%',
-								textAlign: 'center',
-								overflow: 'hidden'
-							});
-					} // end if grid > 0
-				} // end for loop
-
-				// Add timezone options
-				this.timezone_select = $tp.find('.ui_tpicker_timezone').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 = $.timepicker.timeZoneOffsetString(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();
-				});
-				// End timezone options
-				
-				// inject timepicker into datepicker
-				var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
-				if ($buttonPanel.length) {
-					$buttonPanel.before($tp);
-				} else {
-					$dp.append($tp);
-				}
-
-				this.$timeObj = $tp.find('.ui_tpicker_time');
-
-				if (this.inst !== null) {
-					var timeDefined = this.timeDefined;
-					this._onTimeChange();
-					this.timeDefined = timeDefined;
-				}
-
-				// slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
-				if (this._defaults.addSliderAccess) {
-					var sliderAccessArgs = this._defaults.sliderAccessArgs,
-						rtl = this._defaults.isRTL;
-					sliderAccessArgs.isRTL = rtl;
-						
-					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(rtl? 'marginRight':'marginLeft').toString().replace('%', ''),
-										newWidth = oldWidth - sliderAccessWidth,
-										newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%',
-										css = { width: newWidth, marginRight: 0, marginLeft: 0 };
-									css[rtl? 'marginRight':'marginLeft'] = newMarginLeft;
-									$g.css(css);
-								});
-							}
-						}
-					}, 10);
-				}
-				// 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();
-							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.secondMin = this.secondMinOriginal;
-							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.control.options(this, this.hour_slider, { min: this._defaults.hourMin, max: hourMax });
-					this.control.value(this, this.hour_slider, this.hour);
-				}
-				if (this.minute_slider) {
-					this.control.options(this, this.minute_slider, { min: this._defaults.minuteMin, max: minMax });
-					this.control.value(this, this.minute_slider, this.minute);
-				}
-				if (this.second_slider) {
-					this.control.options(this, this.second_slider, { min: this._defaults.secondMin, max: secMax });
-					this.control.value(this, this.second_slider, this.second);
-				}
-				if (this.millisec_slider) {
-					this.control.options(this, this.millisec_slider, { min: this._defaults.millisecMin, max: millisecMax });
-					this.control.value(this, this.millisec_slider, 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.control.value(this, this.hour_slider) : false,
-				minute = (this.minute_slider) ? this.control.value(this, this.minute_slider) : false,
-				second = (this.second_slider) ? this.control.value(this, this.second_slider) : false,
-				millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider) : 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)) 
-								|| ((this.timezone === null && timezone != this.defaultTimezone) || (this.timezone !== null && 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.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 || this.inst.settings.onSelect;
-			var inputEl = this.$input ? this.$input[0] : null;
-			if (onSelect && inputEl) {
-				onSelect.apply(inputEl, [this.formattedDateTime, this]);
-			}
-		},
-
-		/*
-		* 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.$input.val(formattedDateTime);
-				var altFormattedDateTime = '',
-					altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator,
-					altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix;
-				if (this._defaults.altFormat) altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg);
-				else altFormattedDateTime = this.formattedDate;
-				if (altFormattedDateTime) altFormattedDateTime += altSeparator;
-				if (this._defaults.altTimeFormat) altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix;
-				else altFormattedDateTime += this.formattedTime + altTimeSuffix;
-				this.$altInput.val(altFormattedDateTime);
-			} else {
-				this.$input.val(formattedDateTime);
-			}
-
-			this.$input.trigger("change");
-		},
-
-		_onFocus: function() {
-			if (!this.$input.val() && this._defaults.defaultValue) {
-				this.$input.val(this._defaults.defaultValue);
-				var inst = $.datepicker._getInst(this.$input.get(0)),
-					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);
-						}
-					}
-				}
-			}
-		},
-
-		/*
-		* Small abstraction to control types
-		* We can add more, just be sure to follow the pattern: create, options, value
-		*/
-		_controls: {
-			// slider methods
-			slider: {
-				create: function(tp_inst, obj, unit, val, min, max, step){
-					var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60
-					return obj.prop('slide', null).slider({
-						orientation: "horizontal",
-						value: rtl? val*-1 : val,
-						min: rtl? max*-1 : min,
-						max: rtl? min*-1 : max,
-						step: step,
-						slide: function(event, ui) {
-							tp_inst.control.value(tp_inst, $(this), rtl? ui.value*-1:ui.value);
-							tp_inst._onTimeChange();
-						},
-						stop: function(event, ui) {
-							tp_inst._onSelectHandler();
-						}
-					});	
-				},
-				options: function(tp_inst, obj, opts, val){
-					if(tp_inst._defaults.isRTL){
-						if(typeof(opts) == 'string'){
-							if(opts == 'min' || opts == 'max'){
-								if(val !== undefined)
-									return obj.slider(opts, val*-1);
-								return Math.abs(obj.slider(opts));
-							}
-							return obj.slider(opts);
-						}
-						var min = opts.min, 
-							max = opts.max;
-						opts.min = opts.max = null;
-						if(min !== undefined)
-							opts.max = min * -1;
-						if(max !== undefined)
-							opts.min = max * -1;
-						return obj.slider(opts);
-					}
-					if(typeof(opts) == 'string' && val !== undefined)
-							return obj.slider(opts, val);
-					return obj.slider(opts);
-				},
-				value: function(tp_inst, obj, val){
-					if(tp_inst._defaults.isRTL){
-						if(val !== undefined)
-							return obj.slider('value', val*-1);
-						return Math.abs(obj.slider('value'));
-					}
-					if(val !== undefined)
-						return obj.slider('value', val);
-					return obj.slider('value');
-				}
-			},
-			// select methods
-			select: {
-				create: function(tp_inst, obj, unit, val, min, max, step){
-					var sel = '<select class="ui-timepicker-select" data-unit="'+ unit +'" data-min="'+ min +'" data-max="'+ max +'" data-step="'+ step +'">',
-						ul = tp_inst._defaults.timeFormat.indexOf('t') !== -1? 'toLowerCase':'toUpperCase',
-						m = 0;
-
-					for(var i=min; i<=max; i+=step){						
-						sel += '<option value="'+ i +'"'+ (i==val? ' selected':'') +'>';
-						if(unit == 'hour' && tp_inst._defaults.ampm){
-							m = i%12;
-							if(i === 0 || i === 12) sel += '12';
-							else if(m < 10) sel += '0'+ m.toString();
-							else sel += m;
-							sel += ' '+ ((i < 12)? tp_inst._defaults.amNames[0] : tp_inst._defaults.pmNames[0])[ul]();
-						}
-						else if(unit == 'millisec' || i >= 10) sel += i;
-						else sel += '0'+ i.toString();
-						sel += '</option>';
-					}
-					sel += '</select>';
-
-					obj.children('select').remove();
-
-					$(sel).appendTo(obj).change(function(e){
-						tp_inst._onTimeChange();
-						tp_inst._onSelectHandler();
-					});
-
-					return obj;
-				},
-				options: function(tp_inst, obj, opts, val){
-					var o = {},
-						$t = obj.children('select');
-					if(typeof(opts) == 'string'){
-						if(val === undefined)
-							return $t.data(opts);
-						o[opts] = val;	
-					}
-					else o = opts;
-					return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step'));
-				},
-				value: function(tp_inst, obj, val){
-					var $t = obj.children('select');
-					if(val !== undefined)
-						return $t.val(val);
-					return $t.val();
-				}
-			}
-		} // end _controls
-
-	});
-
-	$.fn.extend({
-		/*
-		* shorthand just to use timepicker..
-		*/
-		timepicker: function(o) {
-			o = o || {};
-			var tmp_args = Array.prototype.slice.call(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);
-				});
-			}
-		}
-	});
-
-	/*
-	* Public Utility to parse date and time
-	*/
-	$.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;
-	};
-
-	/*
-	* Public utility to parse time
-	*/
-	$.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(/(hh?|mm?|ss?|[tT]{1,2}|[lz]|'.*?')/g, function (match) {
-						switch (match.charAt(0).toLowerCase()) {
-							case 'h': return '(\\d?\\d)';
-							case 'm': return '(\\d?\\d)';
-							case 's': return '(\\d?\\d)';
-							case 'l': return '(\\d?\\d?\\d)';
-							case 'z': return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?';
-							case 't': return getPatternAmpm(o.amNames, o.pmNames);
-							default:    // literal escaped in quotes
-								return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?';
-						}
-					})
-				.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].toUpperCase(), 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;
-	};
-
-	/*
-	* Public utility to format the time
-	* 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 === null? options.defaultTimezone : 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 '';
-			default:
-				return match.replace(/\'/g, "") || "'";
-			}
-		});
-
-		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);
-	};
-
-	/*
-	* Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField
-	*/
-	$.datepicker._base_updateAlternate = $.datepicker._updateAlternate;
-	/* Update any alternate field to synchronise with the main field. */
-	$.datepicker._updateAlternate = function(inst) {
-		var tp_inst = this._get(inst, 'timepicker');
-		if(tp_inst){
-			var altField = tp_inst._defaults.altField;
-			if (altField) { // update alternate field too
-				var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat,
-					date = this._getDate(inst),
-					formatCfg = $.datepicker._getFormatConfig(inst),
-					altFormattedDateTime = '', 
-					altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, 
-					altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix,
-					altTimeFormat = tp_inst._defaults.altTimeFormat !== undefined ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat;
-				
-				altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix;
-				if(!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly){
-					if(tp_inst._defaults.altFormat)
-						altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, (date === null ? new Date() : date), formatCfg) + altSeparator + altFormattedDateTime;
-					else altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime;
-				}
-				$(altField).val(altFormattedDateTime);
-			}
-		}
-		else{
-			$.datepicker._base_updateAlternate(inst);
-		}
-	};
-
-	/*
-	* 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
-			tp_inst.hour = date ? date.getHours() : defaults.hour;
-			tp_inst.minute = date ? date.getMinutes() : defaults.minute;
-			tp_inst.second = date ? date.getSeconds() : defaults.second;
-			tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec;
-
-			//check if within min/max times.. 
-			tp_inst._limitMinMaxDateTime(inst, true);
-
-			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) {
-			// if it hasn't yet been defined, grab from field
-			if(inst.lastVal === undefined){
-				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 date;
-		try {
-			date = this._base_parseDate(format, value, settings);
-		} catch (err) {
-			// 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.
-			date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings);
-		}
-		return date;
-	};
-
-	/*
-	* 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),
-	        name_clone;
-		if (!inst) {
-			return null;
-		}
-
-		var tp_inst = this._get(inst, 'timepicker');
-		if (tp_inst) {
-			var min = null,
-				max = null,
-				onselect = null,
-				overrides = tp_inst._defaults.evnts,
-				fns = {},
-				prop;
-		    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 (overrides.hasOwnProperty(name)) {
-		            if (typeof (value) === 'undefined') {
-		                return overrides[name];
-		            }
-		            fns[name] = value;
-		            name_clone = {}; //empty results in exiting function after overrides updated
-		        }
-		    } 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;
-		        }
-		        for (prop in overrides) {
-		            if (overrides.hasOwnProperty(prop) && name[prop]) {
-		                fns[prop] = name[prop];
-		            }
-		        }
-		    }
-		    for (prop in fns) {
-		        if (fns.hasOwnProperty(prop)) {
-		            overrides[prop] = fns[prop];
-		            if (!name_clone) { name_clone = $.extend({}, name);}
-		            delete name_clone[prop];
-		        }
-		    }
-		    if (name_clone && isEmptyObject(name_clone)) { return; }
-		    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.call($.datepicker, target, name);
-		}
-		return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
-	};
-	/*
-	* jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype,
-	* it will return false for all objects
-	*/
-	function isEmptyObject (obj) {
-		var prop;
-		for (prop in obj) {
-			if (obj.hasOwnProperty(obj)) {
-				return false;
-			}
-		}
-		return true;
-	}
-	/*
-	* 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
-	* Returns [dateString, timeString]
-	*/
-	var splitDateTime = function(dateFormat, dateTimeString, dateSettings, timeSettings) {
-		try {
-			// The idea is to get the number separator occurances in datetime and the time format requested (since time has 
-			// fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
-			var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator,
-				format = timeSettings && timeSettings.timeFormat ? timeSettings.timeFormat : $.timepicker._defaults.timeFormat,
-				ampm = timeSettings && timeSettings.ampm ? timeSettings.ampm : $.timepicker._defaults.ampm,
-				timeParts = format.split(separator), // how many occurances of separator may be in our format?
-				timePartsLen = timeParts.length,
-				allParts = dateTimeString.split(separator),
-				allPartsLen = allParts.length;
-
-			// because our default ampm=false, but our default format has tt, we need to filter this out
-			if(!ampm){
-				timeParts = $.trim(format.replace(/t/gi,'')).split(separator);
-				timePartsLen = timeParts.length;
-			}
-
-			if (allPartsLen > 1) {
-				return [
-						allParts.splice(0,allPartsLen-timePartsLen).join(separator),
-						allParts.splice(0,timePartsLen).join(separator)
-					];
-			}
-
-		} 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),
-					timeString = dateTimeString.substring(dateStringLength);
-
-				return [$.trim(dateTimeString.substring(0, dateStringLength)), $.trim(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, timeSettings);
-		date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
-		if (splitRes[1] !== '') {
-			var timeString = splitRes[1],
-				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 = $.timepicker.timeZoneOffsetString(now);
-			if (tp_inst._defaults.timezoneIso8601) {
-				tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3);
-			}
-			tp_inst.timezone_select.val(tzoffset);
-		}
-	};
-
-	/*
-	* Create a Singleton Insance
-	*/
-	$.timepicker = new Timepicker();
-
-	/**
-	 * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5)
-	 * @param  date
-	 * @return string
-	 */
-	$.timepicker.timeZoneOffsetString = function(date) {
-		var off = date.getTimezoneOffset() * -1,
-			minutes = off % 60,
-			hours = (off - minutes) / 60;
-		return (off >= 0 ? '+' : '-') + ('0' + (hours * 101).toString()).substr(-2) + ('0' + (minutes * 101).toString()).substr(-2);
-	};
-
-	/**
-	 * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to
-	 * enforce date range limits.
-	 * n.b. The input value must be correctly formatted (reformatting is not supported)
-	 * @param  Element startTime
-	 * @param  Element endTime
-	 * @param  obj options Options for the timepicker() call
-	 * @return jQuery
-	 */
-	$.timepicker.timeRange = function(startTime, endTime, options) {
-		return $.timepicker.handleRange('timepicker', startTime, endTime, options);
-	};
-
-	/**
-	 * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to
-	 * enforce date range limits.
-	 * @param  Element startTime
-	 * @param  Element endTime
-	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
-	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
-	 * @param  string method Can be used to specify the type of picker to be added
-	 * @return jQuery
-	 */
-	$.timepicker.dateTimeRange = function(startTime, endTime, options) {
-		$.timepicker.dateRange(startTime, endTime, options, 'datetimepicker');
-	};
-
-	/**
-	 * Calls `method` on the `startTime` and `endTime` elements, and configures them to
-	 * enforce date range limits.
-	 * @param  Element startTime
-	 * @param  Element endTime
-	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
-	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
-	 * @param  string method Can be used to specify the type of picker to be added
-	 * @return jQuery
-	 */
-	$.timepicker.dateRange = function(startTime, endTime, options, method) {
-		method = method || 'datepicker';
-		$.timepicker.handleRange(method, startTime, endTime, options);
-	};
-
-	/**
-	 * Calls `method` on the `startTime` and `endTime` elements, and configures them to
-	 * enforce date range limits.
-	 * @param  string method Can be used to specify the type of picker to be added
-	 * @param  Element startTime
-	 * @param  Element endTime
-	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
-	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
-	 * @return jQuery
-	 */
-	$.timepicker.handleRange = function(method, startTime, endTime, options) {
-		$.fn[method].call(startTime, $.extend({
-			onClose: function(dateText, inst) {
-				checkDates(this, endTime, dateText);
-			},
-			onSelect: function(selectedDateTime) {
-				selected(this, endTime, 'minDate');
-			}
-		}, options, options.start));
-		$.fn[method].call(endTime, $.extend({
-			onClose: function(dateText, inst) {
-				checkDates(this, startTime, dateText);
-			},
-			onSelect: function(selectedDateTime) {
-				selected(this, startTime, 'maxDate');
-			}
-		}, options, options.end));
-		// timepicker doesn't provide access to its 'timeFormat' option, 
-		// nor could I get datepicker.formatTime() to behave with times, so I
-		// have disabled reformatting for timepicker
-		if (method != 'timepicker' && options.reformat) {
-			$([startTime, endTime]).each(function() {
-				var format = $(this)[method].call($(this), 'option', 'dateFormat'),
-					date = new Date($(this).val());
-				if ($(this).val() && date) {
-					$(this).val($.datepicker.formatDate(format, date));
-				}
-			});
-		}
-		checkDates(startTime, endTime, startTime.val());
-
-		function checkDates(changed, other, dateText) {
-			if (other.val() && (new Date(startTime.val()) > new Date(endTime.val()))) {
-				other.val(dateText);
-			}
-		}
-		selected(startTime, endTime, 'minDate');
-		selected(endTime, startTime, 'maxDate');
-
-		function selected(changed, other, option) {
-			if (!$(changed).val()) {
-				return;
-			}
-			var date = $(changed)[method].call($(changed), 'getDate');
-			// timepicker doesn't implement 'getDate' and returns a jQuery
-			if (date.getTime) {
-				$(other)[method].call($(other), 'option', option, date);
-			}
-		}
-		return $([startTime.get(0), endTime.get(0)]);
-	};
-
-	/*
-	* Keep up with the version
-	*/
-	$.timepicker.version = "1.0.5";
-
+/*
+ * jQuery timepicker addon
+ * By: Trent Richardson [http://trentrichardson.com]
+ * Version 1.0.5
+ * Last Modified: 10/06/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
+ */
+
+/*jslint evil: true, white: false, undef: false, nomen: false */
+
+(function($) {
+
+	/*
+	* Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded"
+	*/
+	$.ui.timepicker = $.ui.timepicker || {};
+	if ($.ui.timepicker.version) {
+		return;
+	}
+
+	/*
+	* Extend jQueryUI, get it started with our version number
+	*/
+	$.extend($.ui, {
+		timepicker: {
+			version: "1.0.5"
+		}
+	});
+
+	/* 
+	* Timepicker 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',
+			isRTL: false
+		};
+		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,
+			altSeparator: null,
+			altTimeSuffix: null,
+			showTimepicker: true,
+			timezoneIso8601: false,
+			timezoneList: null,
+			addSliderAccess: false,
+			sliderAccessArgs: null,
+			controlType: 'slider',
+			defaultValue: 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,
+		units: ['hour','minute','second','millisec'],
+		control: 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 = {},
+                fns = {},
+		        overrides, i;
+
+			for (var attrName in this._defaults) {
+				if(this._defaults.hasOwnProperty(attrName)){
+					var attrValue = $input.attr('time:' + attrName);
+					if (attrValue) {
+						try {
+							inlineSettings[attrName] = eval(attrValue);
+						} catch (err) {
+							inlineSettings[attrName] = attrValue;
+						}
+					}
+				}
+			}
+		    overrides = {
+		        beforeShow: function (input, dp_inst) {
+		            if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) {
+		                return tp_inst._defaults.evnts.beforeShow.call($input[0], 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(tp_inst._defaults.evnts.onChangeMonthYear)) {
+		                tp_inst._defaults.evnts.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(tp_inst._defaults.evnts.onClose)) {
+		                tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst);
+		            }
+		        }
+		    };
+		    for (i in overrides) {
+		        if (overrides.hasOwnProperty(i)) {
+		            fns[i] = o[i] || null;
+		        }
+		    }
+		    tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, overrides, {
+		        evnts:fns,
+		        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();
+			});
+
+			// controlType is string - key to our this._controls
+			if(typeof(tp_inst._defaults.controlType) === 'string'){
+				if(tp_inst._defaults.controlType == 'slider' && $.fn.slider === undefined){
+					tp_inst._defaults.controlType = 'select';
+				}
+				tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType];
+			}
+			// controlType is an object and must implement create, options, value methods
+			else{ 
+				tp_inst.control = tp_inst._defaults.controlType;
+			}
+
+			if (tp_inst._defaults.timezoneList === null) {
+				var timezoneList = ['-1200', '-1100', '-1000', '-0930', '-0900', '-0800', '-0700', '-0600', '-0500', '-0430', '-0400', '-0330', '-0300', '-0200', '-0100', '+0000', 
+									'+0100', '+0200', '+0300', '+0330', '+0400', '+0430', '+0500', '+0530', '+0545', '+0600', '+0630', '+0700', '+0800', '+0845', '+0900', '+0930', 
+									'+1000', '+1030', '+1100', '+1130', '+1200', '+1245', '+1300', '+1400'];
+
+				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());
+			}
+			tp_inst.$input.bind('focus', function() {
+				tp_inst._onFocus();
+			});
+
+			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.inst.settings,
+				tp_inst = this,
+				litem = '',
+				uitem = '',
+				max = {},
+				gridSize = {},
+				size = null;
+
+			// Prevent displaying twice
+			if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) {
+				var noDisplay = ' style="display:none;"',
+					html = '<div class="ui-timepicker-div'+ (o.isRTL? ' ui-timepicker-rtl' : '') +'"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + 
+								'<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>';
+
+				// Create the markup
+				for(var i=0,l=this.units.length; i<l; i++){
+					litem = this.units[i];
+					uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
+					// 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.
+					max[litem] = parseInt((o[litem+'Max'] - ((o[litem+'Max'] - o[litem+'Min']) % o['step'+uitem])), 10);
+					gridSize[litem] = 0;
+
+					html += '<dt class="ui_tpicker_'+ litem +'_label"' + ((o['show'+uitem]) ? '' : noDisplay) + '>' + o[litem +'Text'] + '</dt>' + 
+								'<dd class="ui_tpicker_'+ litem +'"><div class="ui_tpicker_'+ litem +'_slider"' + ((o['show'+uitem]) ? '' : noDisplay) + '></div>';
+
+					if (o['show'+uitem] && o[litem+'Grid'] > 0) {
+						html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+						if(litem == 'hour'){
+							for (var h = o[litem+'Min']; h <= max[litem]; h += parseInt(o[litem+'Grid'], 10)) {
+								gridSize[litem]++;
+								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 data-for="'+litem+'">' + tmph + '</td>';
+							}
+						}
+						else{
+							for (var m = o[litem+'Min']; m <= max[litem]; m += parseInt(o[litem+'Grid'], 10)) {
+								gridSize[litem]++;
+								html += '<td data-for="'+litem+'">' + ((m < 10) ? '0' : '') + m + '</td>';
+							}
+						}
+
+						html += '</tr></table></div>';
+					}
+					html += '</dd>';
+				}
+				
+				// Timezone
+				html += '<dt class="ui_tpicker_timezone_label"' + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
+				html += '<dd class="ui_tpicker_timezone" ' + ((o.showTimezone) ? '' : noDisplay) + '></dd>';
+
+				// Create the elements from string
+				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();
+				}
+				
+				// add sliders, adjust grids, add events
+				for(var i=0,l=tp_inst.units.length; i<l; i++){
+					litem = tp_inst.units[i];
+					uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
+					
+					// add the slider
+					tp_inst[litem+'_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_'+litem+'_slider'), litem, tp_inst[litem], o[litem+'Min'], max[litem], o['step'+uitem]);
+
+					// adjust the grid and add click event
+					if (o['show'+uitem] && o[litem+'Grid'] > 0) {
+						size = 100 * gridSize[litem] * o[litem+'Grid'] / (max[litem] - o[litem+'Min']);
+						$tp.find('.ui_tpicker_'+litem+' table').css({
+							width: size + "%",
+							marginLeft: o.isRTL? '0' : ((size / (-2 * gridSize[litem])) + "%"),
+							marginRight: o.isRTL? ((size / (-2 * gridSize[litem])) + "%") : '0',
+							borderCollapse: 'collapse'
+						}).find("td").click(function(e){
+								var $t = $(this),
+									h = $t.html(),
+									f = $t.data('for'); // loses scope, so we use data-for
+
+								if (f == 'hour' && 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.control.value(tp_inst, tp_inst[f+'_slider'], parseInt(h,10));
+
+								tp_inst._onTimeChange();
+								tp_inst._onSelectHandler();
+							})
+						.css({
+								cursor: 'pointer',
+								width: (100 / gridSize[litem]) + '%',
+								textAlign: 'center',
+								overflow: 'hidden'
+							});
+					} // end if grid > 0
+				} // end for loop
+
+				// Add timezone options
+				this.timezone_select = $tp.find('.ui_tpicker_timezone').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 = $.timepicker.timeZoneOffsetString(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();
+				});
+				// End timezone options
+				
+				// inject timepicker into datepicker
+				var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
+				if ($buttonPanel.length) {
+					$buttonPanel.before($tp);
+				} else {
+					$dp.append($tp);
+				}
+
+				this.$timeObj = $tp.find('.ui_tpicker_time');
+
+				if (this.inst !== null) {
+					var timeDefined = this.timeDefined;
+					this._onTimeChange();
+					this.timeDefined = timeDefined;
+				}
+
+				// slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
+				if (this._defaults.addSliderAccess) {
+					var sliderAccessArgs = this._defaults.sliderAccessArgs,
+						rtl = this._defaults.isRTL;
+					sliderAccessArgs.isRTL = rtl;
+						
+					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(rtl? 'marginRight':'marginLeft').toString().replace('%', ''),
+										newWidth = oldWidth - sliderAccessWidth,
+										newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%',
+										css = { width: newWidth, marginRight: 0, marginLeft: 0 };
+									css[rtl? 'marginRight':'marginLeft'] = newMarginLeft;
+									$g.css(css);
+								});
+							}
+						}
+					}, 10);
+				}
+				// 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();
+							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.secondMin = this.secondMinOriginal;
+							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.control.options(this, this.hour_slider, { min: this._defaults.hourMin, max: hourMax });
+					this.control.value(this, this.hour_slider, this.hour);
+				}
+				if (this.minute_slider) {
+					this.control.options(this, this.minute_slider, { min: this._defaults.minuteMin, max: minMax });
+					this.control.value(this, this.minute_slider, this.minute);
+				}
+				if (this.second_slider) {
+					this.control.options(this, this.second_slider, { min: this._defaults.secondMin, max: secMax });
+					this.control.value(this, this.second_slider, this.second);
+				}
+				if (this.millisec_slider) {
+					this.control.options(this, this.millisec_slider, { min: this._defaults.millisecMin, max: millisecMax });
+					this.control.value(this, this.millisec_slider, 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.control.value(this, this.hour_slider) : false,
+				minute = (this.minute_slider) ? this.control.value(this, this.minute_slider) : false,
+				second = (this.second_slider) ? this.control.value(this, this.second_slider) : false,
+				millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider) : 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)) 
+								|| ((this.timezone === null && timezone != this.defaultTimezone) || (this.timezone !== null && 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.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 || this.inst.settings.onSelect;
+			var inputEl = this.$input ? this.$input[0] : null;
+			if (onSelect && inputEl) {
+				onSelect.apply(inputEl, [this.formattedDateTime, this]);
+			}
+		},
+
+		/*
+		* 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.$input.val(formattedDateTime);
+				var altFormattedDateTime = '',
+					altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator,
+					altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix;
+				if (this._defaults.altFormat) altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg);
+				else altFormattedDateTime = this.formattedDate;
+				if (altFormattedDateTime) altFormattedDateTime += altSeparator;
+				if (this._defaults.altTimeFormat) altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix;
+				else altFormattedDateTime += this.formattedTime + altTimeSuffix;
+				this.$altInput.val(altFormattedDateTime);
+			} else {
+				this.$input.val(formattedDateTime);
+			}
+
+			this.$input.trigger("change");
+		},
+
+		_onFocus: function() {
+			if (!this.$input.val() && this._defaults.defaultValue) {
+				this.$input.val(this._defaults.defaultValue);
+				var inst = $.datepicker._getInst(this.$input.get(0)),
+					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);
+						}
+					}
+				}
+			}
+		},
+
+		/*
+		* Small abstraction to control types
+		* We can add more, just be sure to follow the pattern: create, options, value
+		*/
+		_controls: {
+			// slider methods
+			slider: {
+				create: function(tp_inst, obj, unit, val, min, max, step){
+					var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60
+					return obj.prop('slide', null).slider({
+						orientation: "horizontal",
+						value: rtl? val*-1 : val,
+						min: rtl? max*-1 : min,
+						max: rtl? min*-1 : max,
+						step: step,
+						slide: function(event, ui) {
+							tp_inst.control.value(tp_inst, $(this), rtl? ui.value*-1:ui.value);
+							tp_inst._onTimeChange();
+						},
+						stop: function(event, ui) {
+							tp_inst._onSelectHandler();
+						}
+					});	
+				},
+				options: function(tp_inst, obj, opts, val){
+					if(tp_inst._defaults.isRTL){
+						if(typeof(opts) == 'string'){
+							if(opts == 'min' || opts == 'max'){
+								if(val !== undefined)
+									return obj.slider(opts, val*-1);
+								return Math.abs(obj.slider(opts));
+							}
+							return obj.slider(opts);
+						}
+						var min = opts.min, 
+							max = opts.max;
+						opts.min = opts.max = null;
+						if(min !== undefined)
+							opts.max = min * -1;
+						if(max !== undefined)
+							opts.min = max * -1;
+						return obj.slider(opts);
+					}
+					if(typeof(opts) == 'string' && val !== undefined)
+							return obj.slider(opts, val);
+					return obj.slider(opts);
+				},
+				value: function(tp_inst, obj, val){
+					if(tp_inst._defaults.isRTL){
+						if(val !== undefined)
+							return obj.slider('value', val*-1);
+						return Math.abs(obj.slider('value'));
+					}
+					if(val !== undefined)
+						return obj.slider('value', val);
+					return obj.slider('value');
+				}
+			},
+			// select methods
+			select: {
+				create: function(tp_inst, obj, unit, val, min, max, step){
+					var sel = '<select class="ui-timepicker-select" data-unit="'+ unit +'" data-min="'+ min +'" data-max="'+ max +'" data-step="'+ step +'">',
+						ul = tp_inst._defaults.timeFormat.indexOf('t') !== -1? 'toLowerCase':'toUpperCase',
+						m = 0;
+
+					for(var i=min; i<=max; i+=step){						
+						sel += '<option value="'+ i +'"'+ (i==val? ' selected':'') +'>';
+						if(unit == 'hour' && tp_inst._defaults.ampm){
+							m = i%12;
+							if(i === 0 || i === 12) sel += '12';
+							else if(m < 10) sel += '0'+ m.toString();
+							else sel += m;
+							sel += ' '+ ((i < 12)? tp_inst._defaults.amNames[0] : tp_inst._defaults.pmNames[0])[ul]();
+						}
+						else if(unit == 'millisec' || i >= 10) sel += i;
+						else sel += '0'+ i.toString();
+						sel += '</option>';
+					}
+					sel += '</select>';
+
+					obj.children('select').remove();
+
+					$(sel).appendTo(obj).change(function(e){
+						tp_inst._onTimeChange();
+						tp_inst._onSelectHandler();
+					});
+
+					return obj;
+				},
+				options: function(tp_inst, obj, opts, val){
+					var o = {},
+						$t = obj.children('select');
+					if(typeof(opts) == 'string'){
+						if(val === undefined)
+							return $t.data(opts);
+						o[opts] = val;	
+					}
+					else o = opts;
+					return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step'));
+				},
+				value: function(tp_inst, obj, val){
+					var $t = obj.children('select');
+					if(val !== undefined)
+						return $t.val(val);
+					return $t.val();
+				}
+			}
+		} // end _controls
+
+	});
+
+	$.fn.extend({
+		/*
+		* shorthand just to use timepicker..
+		*/
+		timepicker: function(o) {
+			o = o || {};
+			var tmp_args = Array.prototype.slice.call(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);
+				});
+			}
+		}
+	});
+
+	/*
+	* Public Utility to parse date and time
+	*/
+	$.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;
+	};
+
+	/*
+	* Public utility to parse time
+	*/
+	$.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(/(hh?|mm?|ss?|[tT]{1,2}|[lz]|'.*?')/g, function (match) {
+						switch (match.charAt(0).toLowerCase()) {
+							case 'h': return '(\\d?\\d)';
+							case 'm': return '(\\d?\\d)';
+							case 's': return '(\\d?\\d)';
+							case 'l': return '(\\d?\\d?\\d)';
+							case 'z': return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?';
+							case 't': return getPatternAmpm(o.amNames, o.pmNames);
+							default:    // literal escaped in quotes
+								return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?';
+						}
+					})
+				.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].toUpperCase(), 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;
+	};
+
+	/*
+	* Public utility to format the time
+	* 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 === null? options.defaultTimezone : 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 '';
+			default:
+				return match.replace(/\'/g, "") || "'";
+			}
+		});
+
+		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);
+	};
+
+	/*
+	* Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField
+	*/
+	$.datepicker._base_updateAlternate = $.datepicker._updateAlternate;
+	/* Update any alternate field to synchronise with the main field. */
+	$.datepicker._updateAlternate = function(inst) {
+		var tp_inst = this._get(inst, 'timepicker');
+		if(tp_inst){
+			var altField = tp_inst._defaults.altField;
+			if (altField) { // update alternate field too
+				var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat,
+					date = this._getDate(inst),
+					formatCfg = $.datepicker._getFormatConfig(inst),
+					altFormattedDateTime = '', 
+					altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, 
+					altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix,
+					altTimeFormat = tp_inst._defaults.altTimeFormat !== undefined ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat;
+				
+				altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix;
+				if(!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly){
+					if(tp_inst._defaults.altFormat)
+						altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, (date === null ? new Date() : date), formatCfg) + altSeparator + altFormattedDateTime;
+					else altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime;
+				}
+				$(altField).val(altFormattedDateTime);
+			}
+		}
+		else{
+			$.datepicker._base_updateAlternate(inst);
+		}
+	};
+
+	/*
+	* 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
+			tp_inst.hour = date ? date.getHours() : defaults.hour;
+			tp_inst.minute = date ? date.getMinutes() : defaults.minute;
+			tp_inst.second = date ? date.getSeconds() : defaults.second;
+			tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec;
+
+			//check if within min/max times.. 
+			tp_inst._limitMinMaxDateTime(inst, true);
+
+			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) {
+			// if it hasn't yet been defined, grab from field
+			if(inst.lastVal === undefined){
+				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 date;
+		try {
+			date = this._base_parseDate(format, value, settings);
+		} catch (err) {
+			// 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.
+			date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings);
+		}
+		return date;
+	};
+
+	/*
+	* 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),
+	        name_clone;
+		if (!inst) {
+			return null;
+		}
+
+		var tp_inst = this._get(inst, 'timepicker');
+		if (tp_inst) {
+			var min = null,
+				max = null,
+				onselect = null,
+				overrides = tp_inst._defaults.evnts,
+				fns = {},
+				prop;
+		    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 (overrides.hasOwnProperty(name)) {
+		            if (typeof (value) === 'undefined') {
+		                return overrides[name];
+		            }
+		            fns[name] = value;
+		            name_clone = {}; //empty results in exiting function after overrides updated
+		        }
+		    } 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;
+		        }
+		        for (prop in overrides) {
+		            if (overrides.hasOwnProperty(prop) && name[prop]) {
+		                fns[prop] = name[prop];
+		            }
+		        }
+		    }
+		    for (prop in fns) {
+		        if (fns.hasOwnProperty(prop)) {
+		            overrides[prop] = fns[prop];
+		            if (!name_clone) { name_clone = $.extend({}, name);}
+		            delete name_clone[prop];
+		        }
+		    }
+		    if (name_clone && isEmptyObject(name_clone)) { return; }
+		    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.call($.datepicker, target, name);
+		}
+		return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
+	};
+	/*
+	* jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype,
+	* it will return false for all objects
+	*/
+	function isEmptyObject (obj) {
+		var prop;
+		for (prop in obj) {
+			if (obj.hasOwnProperty(obj)) {
+				return false;
+			}
+		}
+		return true;
+	}
+	/*
+	* 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
+	* Returns [dateString, timeString]
+	*/
+	var splitDateTime = function(dateFormat, dateTimeString, dateSettings, timeSettings) {
+		try {
+			// The idea is to get the number separator occurances in datetime and the time format requested (since time has 
+			// fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
+			var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator,
+				format = timeSettings && timeSettings.timeFormat ? timeSettings.timeFormat : $.timepicker._defaults.timeFormat,
+				ampm = timeSettings && timeSettings.ampm ? timeSettings.ampm : $.timepicker._defaults.ampm,
+				timeParts = format.split(separator), // how many occurances of separator may be in our format?
+				timePartsLen = timeParts.length,
+				allParts = dateTimeString.split(separator),
+				allPartsLen = allParts.length;
+
+			// because our default ampm=false, but our default format has tt, we need to filter this out
+			if(!ampm){
+				timeParts = $.trim(format.replace(/t/gi,'')).split(separator);
+				timePartsLen = timeParts.length;
+			}
+
+			if (allPartsLen > 1) {
+				return [
+						allParts.splice(0,allPartsLen-timePartsLen).join(separator),
+						allParts.splice(0,timePartsLen).join(separator)
+					];
+			}
+
+		} 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),
+					timeString = dateTimeString.substring(dateStringLength);
+
+				return [$.trim(dateTimeString.substring(0, dateStringLength)), $.trim(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, timeSettings);
+		date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
+		if (splitRes[1] !== '') {
+			var timeString = splitRes[1],
+				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 = $.timepicker.timeZoneOffsetString(now);
+			if (tp_inst._defaults.timezoneIso8601) {
+				tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3);
+			}
+			tp_inst.timezone_select.val(tzoffset);
+		}
+	};
+
+	/*
+	* Create a Singleton Insance
+	*/
+	$.timepicker = new Timepicker();
+
+	/**
+	 * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5)
+	 * @param  date
+	 * @return string
+	 */
+	$.timepicker.timeZoneOffsetString = function(date) {
+		var off = date.getTimezoneOffset() * -1,
+			minutes = off % 60,
+			hours = (off - minutes) / 60;
+		return (off >= 0 ? '+' : '-') + ('0' + (hours * 101).toString()).substr(-2) + ('0' + (minutes * 101).toString()).substr(-2);
+	};
+
+	/**
+	 * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to
+	 * enforce date range limits.
+	 * n.b. The input value must be correctly formatted (reformatting is not supported)
+	 * @param  Element startTime
+	 * @param  Element endTime
+	 * @param  obj options Options for the timepicker() call
+	 * @return jQuery
+	 */
+	$.timepicker.timeRange = function(startTime, endTime, options) {
+		return $.timepicker.handleRange('timepicker', startTime, endTime, options);
+	};
+
+	/**
+	 * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to
+	 * enforce date range limits.
+	 * @param  Element startTime
+	 * @param  Element endTime
+	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
+	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
+	 * @param  string method Can be used to specify the type of picker to be added
+	 * @return jQuery
+	 */
+	$.timepicker.dateTimeRange = function(startTime, endTime, options) {
+		$.timepicker.dateRange(startTime, endTime, options, 'datetimepicker');
+	};
+
+	/**
+	 * Calls `method` on the `startTime` and `endTime` elements, and configures them to
+	 * enforce date range limits.
+	 * @param  Element startTime
+	 * @param  Element endTime
+	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
+	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
+	 * @param  string method Can be used to specify the type of picker to be added
+	 * @return jQuery
+	 */
+	$.timepicker.dateRange = function(startTime, endTime, options, method) {
+		method = method || 'datepicker';
+		$.timepicker.handleRange(method, startTime, endTime, options);
+	};
+
+	/**
+	 * Calls `method` on the `startTime` and `endTime` elements, and configures them to
+	 * enforce date range limits.
+	 * @param  string method Can be used to specify the type of picker to be added
+	 * @param  Element startTime
+	 * @param  Element endTime
+	 * @param  obj options Options for the `timepicker()` call. Also supports `reformat`,
+	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
+	 * @return jQuery
+	 */
+	$.timepicker.handleRange = function(method, startTime, endTime, options) {
+		$.fn[method].call(startTime, $.extend({
+			onClose: function(dateText, inst) {
+				checkDates(this, endTime, dateText);
+			},
+			onSelect: function(selectedDateTime) {
+				selected(this, endTime, 'minDate');
+			}
+		}, options, options.start));
+		$.fn[method].call(endTime, $.extend({
+			onClose: function(dateText, inst) {
+				checkDates(this, startTime, dateText);
+			},
+			onSelect: function(selectedDateTime) {
+				selected(this, startTime, 'maxDate');
+			}
+		}, options, options.end));
+		// timepicker doesn't provide access to its 'timeFormat' option, 
+		// nor could I get datepicker.formatTime() to behave with times, so I
+		// have disabled reformatting for timepicker
+		if (method != 'timepicker' && options.reformat) {
+			$([startTime, endTime]).each(function() {
+				var format = $(this)[method].call($(this), 'option', 'dateFormat'),
+					date = new Date($(this).val());
+				if ($(this).val() && date) {
+					$(this).val($.datepicker.formatDate(format, date));
+				}
+			});
+		}
+		checkDates(startTime, endTime, startTime.val());
+
+		function checkDates(changed, other, dateText) {
+			if (other.val() && (new Date(startTime.val()) > new Date(endTime.val()))) {
+				other.val(dateText);
+			}
+		}
+		selected(startTime, endTime, 'minDate');
+		selected(endTime, startTime, 'maxDate');
+
+		function selected(changed, other, option) {
+			if (!$(changed).val()) {
+				return;
+			}
+			var date = $(changed)[method].call($(changed), 'getDate');
+			// timepicker doesn't implement 'getDate' and returns a jQuery
+			if (date.getTime) {
+				$(other)[method].call($(other), 'option', option, date);
+			}
+		}
+		return $([startTime.get(0), endTime.get(0)]);
+	};
+
+	/*
+	* Keep up with the version
+	*/
+	$.timepicker.version = "1.0.5";
+
 })(jQuery);

Some files were not shown because too many files changed in this diff