Просмотр исходного кода

AMBARI-919. Partial refactoring and consolidation of code for various wizards. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1402309 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 лет назад
Родитель
Сommit
91a7d1eeae
63 измененных файлов с 4749 добавлено и 3273 удалено
  1. 3 0
      AMBARI-666-CHANGES.txt
  2. BIN
      ambari-web/app/assets/img/health-status-dead-orange.png
  3. BIN
      ambari-web/app/assets/img/health-status-dead-yellow.png
  4. 6 4
      ambari-web/app/controllers.js
  5. 430 2
      ambari-web/app/controllers/installer.js
  6. 0 388
      ambari-web/app/controllers/installer/step7_controller.js
  7. 46 1
      ambari-web/app/controllers/main.js
  8. 133 103
      ambari-web/app/controllers/main/host.js
  9. 160 10
      ambari-web/app/controllers/main/host/add_controller.js
  10. 131 7
      ambari-web/app/controllers/main/host/details.js
  11. 268 0
      ambari-web/app/controllers/wizard/slave_component_groups_controller.js
  12. 66 0
      ambari-web/app/controllers/wizard/step1_controller.js
  13. 144 0
      ambari-web/app/controllers/wizard/step7_controller.js
  14. 122 95
      ambari-web/app/controllers/wizard/step8_controller.js
  15. 71 89
      ambari-web/app/controllers/wizard/step9_controller.js
  16. 3 0
      ambari-web/app/messages.js
  17. 3 3
      ambari-web/app/models/host.js
  18. 114 107
      ambari-web/app/models/service.js
  19. 1 1
      ambari-web/app/router.js
  20. 75 4
      ambari-web/app/routes/add_host_routes.js
  21. 27 22
      ambari-web/app/routes/installer.js
  22. 1 0
      ambari-web/app/routes/main.js
  23. 44 4
      ambari-web/app/styles/application.less
  24. 0 9
      ambari-web/app/templates/installer/master_hosts.hbs
  25. 0 5
      ambari-web/app/templates/installer/master_hosts_popup.hbs
  26. 0 15
      ambari-web/app/templates/installer/slave_component_hosts.hbs
  27. 0 12
      ambari-web/app/templates/installer/slave_hosts.hbs
  28. 0 21
      ambari-web/app/templates/installer/slave_hosts_popup.hbs
  29. 0 100
      ambari-web/app/templates/installer/step7.hbs
  30. 2 4
      ambari-web/app/templates/main.hbs
  31. 44 0
      ambari-web/app/templates/main/background_operations_popup.hbs
  32. 57 16
      ambari-web/app/templates/main/host.hbs
  33. 6 7
      ambari-web/app/templates/main/host/background_operations_popup.hbs
  34. 1 1
      ambari-web/app/templates/main/host/details.hbs
  35. 12 0
      ambari-web/app/templates/main/host/summary.hbs
  36. 30 0
      ambari-web/app/templates/main/menu_item.hbs
  37. 230 201
      ambari-web/app/templates/main/service/info/summary.hbs
  38. 19 17
      ambari-web/app/templates/main/service/item.hbs
  39. 9 0
      ambari-web/app/templates/wizard/master_hosts.hbs
  40. 5 0
      ambari-web/app/templates/wizard/master_hosts_popup.hbs
  41. 15 0
      ambari-web/app/templates/wizard/slave_component_hosts.hbs
  42. 2 2
      ambari-web/app/templates/wizard/slave_component_hosts_popup.hbs
  43. 12 0
      ambari-web/app/templates/wizard/slave_hosts.hbs
  44. 42 0
      ambari-web/app/templates/wizard/step1.hbs
  45. 129 0
      ambari-web/app/templates/wizard/step7.hbs
  46. 9 9
      ambari-web/app/templates/wizard/step8.hbs
  47. 2 2
      ambari-web/app/templates/wizard/step9.hbs
  48. 5 3
      ambari-web/app/views.js
  49. 4 1
      ambari-web/app/views/common/modal_popup.js
  50. 27 13
      ambari-web/app/views/main.js
  51. 86 61
      ambari-web/app/views/main/host.js
  52. 18 0
      ambari-web/app/views/main/host/details.js
  53. 4 1
      ambari-web/app/views/main/host/summary.js
  54. 1 1
      ambari-web/app/views/main/hosts.js
  55. 9 1
      ambari-web/app/views/main/menu.js
  56. 19 0
      ambari-web/app/views/main/service/info/summary.js
  57. 166 94
      ambari-web/app/views/wizard/controls_view.js
  58. 34 0
      ambari-web/app/views/wizard/step1_view.js
  59. 65 0
      ambari-web/app/views/wizard/step7_view.js
  60. 3 3
      ambari-web/app/views/wizard/step8_view.js
  61. 2 2
      ambari-web/app/views/wizard/step9_view.js
  62. 1830 1830
      ambari-web/vendor/scripts/jquery-ui-timepicker-addon.js
  63. 2 2
      ambari-web/vendor/styles/jquery-ui-bootstrap/jquery-ui-1.8.16.custom.css

+ 3 - 0
AMBARI-666-CHANGES.txt

@@ -347,6 +347,9 @@ AMBARI-666 branch (unreleased changes)
 
   IMPROVEMENTS
 
+  AMBARI-919. Partial refactoring and consolidation of code for various
+  wizards. (yusaku)
+
   AMBARI-918. Update styles in Cluster Management. (yusaku)
 
   AMBARI-917. Update layout and flow for App Browser. (yusaku)

BIN
ambari-web/app/assets/img/health-status-dead-orange.png


BIN
ambari-web/app/assets/img/health-status-dead-yellow.png


+ 6 - 4
ambari-web/app/controllers.js

@@ -29,9 +29,6 @@ require('controllers/installer/step4_controller');
 require('controllers/installer/step6_controller');
 require('controllers/installer/step5_controller');
 require('controllers/installer/step6_controller');
-require('controllers/installer/step7_controller');
-require('controllers/installer/step8_controller');
-require('controllers/installer/step9_controller');
 require('controllers/installer/step10_controller');
 require('controllers/main');
 require('controllers/main/admin');
@@ -58,8 +55,13 @@ require('controllers/main/charts/horizon_chart');
 require('controllers/main/rack');
 require('controllers/main/apps_controller');
 require('controllers/main/apps/item_controller');
+require('controllers/wizard/slave_component_groups_controller');
+require('controllers/wizard/step1_controller');
 require('controllers/wizard/step2_controller');
 require('controllers/wizard/step3_controller');
 require('controllers/wizard/step4_controller');
 require('controllers/wizard/step5_controller');
-require('controllers/wizard/step6_controller');
+require('controllers/wizard/step6_controller');
+require('controllers/wizard/step7_controller');
+require('controllers/wizard/step8_controller');
+require('controllers/wizard/step9_controller');

+ 430 - 2
ambari-web/app/controllers/installer.js

@@ -39,8 +39,8 @@ App.InstallerController = Em.Controller.extend({
         value: true
       }));
     }
-   // window.onbeforeunload = function () {
-     // return "You have not saved your document yet.  If you continue, your work will not be saved."
+    // window.onbeforeunload = function () {
+    // return "You have not saved your document yet.  If you continue, your work will not be saved."
     //}
   },
 
@@ -200,6 +200,434 @@ App.InstallerController = Em.Controller.extend({
    */
   createCluster: function (cluster) {
     alert('created cluster ' + cluster.name);
+  },
+
+  content: Em.Object.create({
+    cluster: {},
+    hosts: {},
+    services: {},
+    hostsInfo: {},
+    slaveComponentHosts: {},
+    hostSlaveComponents: {},
+    masterComponentHosts: {},
+    serviceConfigProperties: {}
+  }),
+
+  /**
+   * Load clusterInfo(step1) to model
+   */
+  loadClusterInfo: function () {
+    var cluster = {
+      name: App.db.getClusterName(),
+      status: App.db.getClusterStatus().status,
+      isCompleted: App.db.getClusterStatus().isCompleted
+    };
+    this.set('content.cluster', cluster);
+
+    console.log("InstallerController:loadClusterInfo: loaded data ", cluster);
+  },
+
+
+  /**
+   * Save all info about claster to model
+   * @param stepController Step1WizardController
+   */
+  saveClusterInfo: function (stepController) {
+    var cluster = stepController.get('content.cluster');
+    var clusterStatus = {
+      status: cluster.status,
+      isCompleted: cluster.isCompleted
+    }
+    App.db.setClusterName(cluster.name);
+    App.db.setClusterStatus(clusterStatus);
+
+    console.log("InstallerController:saveClusterInfo: saved data ", cluster);
+
+    //probably next line is extra work - need to check it
+    this.set('content.cluster', cluster);
+  },
+
+  /**
+   * Temporary function for wizardStep9, before back-end integration
+   */
+  setInfoForStep9: function () {
+    App.db.setClusterStatus({status: 'pending', isCompleted: false});
+    var hostInfo = App.db.getHosts();
+    for (var index in hostInfo) {
+      hostInfo[index].status = "pending";
+      hostInfo[index].message = 'Information';
+      hostInfo[index].progress = '0';
+    }
+    App.db.setHosts(hostInfo);
+  },
+
+  /**
+   * Load all data for <code>Specify Host(install step2)</code> step
+   * Data Example:
+   * {
+   *   hostNames: '',
+   *   manualInstall: false,
+   *   sshKey: '',
+   *   passphrase: '',
+   *   confirmPassphrase: '',
+   *   localRepo: false,
+   *   localRepoPath: ''
+   * }
+   */
+  loadInstallOptions: function () {
+
+    if (!this.content.hosts) {
+      this.content.hosts = Em.Object.create();
+    }
+
+    //TODO : rewire it as model. or not :)
+    var hostsInfo = Em.Object.create();
+
+    hostsInfo.hostNames = App.db.getAllHostNames() || ''; //empty string if undefined
+
+    //TODO : should we check installType for add host wizard????
+    var installType = App.db.getInstallType();
+    //false if installType not equals 'manual'
+    hostsInfo.manualInstall = installType && installType.installType === 'manual' || false;
+
+    var softRepo = App.db.getSoftRepo();
+    if (softRepo && softRepo.repoType === 'local') {
+      hostsInfo.localRepo = true;
+      hostsInfo.localRepopath = softRepo.repoPath;
+    } else {
+      hostsInfo.localRepo = false;
+      hostsInfo.localRepoPath = '';
+    }
+
+    hostsInfo.sshKey = 'random';
+    hostsInfo.passphrase = '';
+    hostsInfo.confirmPassphrase = '';
+
+    this.set('content.hosts', hostsInfo);
+    console.log("InstallerController:loadHosts: loaded data ", hostsInfo);
+  },
+
+  /**
+   * Save data, which user filled, to main controller
+   * @param stepController App.WizardStep2Controller
+   */
+  saveHosts: function (stepController) {
+    //TODO: put data to content.hosts and only then save it)
+
+    //App.db.setBootStatus(false);
+    App.db.setAllHostNames(stepController.get('hostNames'));
+    App.db.setHosts(stepController.getHostInfo());
+    if (stepController.get('manualInstall') === false) {
+      App.db.setInstallType({installType: 'ambari' });
+    } else {
+      App.db.setInstallType({installType: 'manual' });
+    }
+    if (stepController.get('localRepo') === false) {
+      App.db.setSoftRepo({ 'repoType': 'remote', 'repoPath': null});
+    } else {
+      App.db.setSoftRepo({ 'repoType': 'local', 'repoPath': stepController.get('localRepoPath') });
+    }
+  },
+
+  /**
+   * Return hosts, which were add at <code>Specify Host(step2)</code> step
+   * @paramm isNew whether return all hosts or only new ones
+   */
+  getHostList: function (isNew) {
+    var hosts = [];
+    var hostArray = App.db.getHosts()
+    console.log('in instsllerController.getHostList: host names is ', hostArray);
+
+    for (var i in hostArray) {
+      var hostInfo = App.HostInfo.create({
+        name: hostArray[i].name,
+        bootStatus: hostArray[i].bootStatus
+      });
+
+      hosts.pushObject(hostInfo);
+    }
+
+    console.log('TRACE: pushing ' + hosts);
+    return hosts;
+  },
+
+  /**
+   * Remove host from model. Used at <code>Confirm hosts(step2)</code> step
+   * @param hosts Array of hosts, which we want to delete
+   */
+  removeHosts: function (hosts) {
+    //todo Replace this code with real logic
+    App.db.removeHosts(hosts);
+  },
+
+  /**
+   * Save data, which user filled, to main controller
+   * @param stepController App.WizardStep3Controller
+   */
+  saveConfirmedHosts: function (stepController) {
+    var hostInfo = {};
+    stepController.get('content').forEach(function (_host) {
+      hostInfo[_host.name] = {
+        name: _host.name,
+        cpu: _host.cpu,
+        memory: _host.memory,
+        bootStatus: _host.bootStatus
+      };
+    });
+    console.log('installerController:saveConfirmedHosts: save hosts ', hostInfo);
+    App.db.setHosts(hostInfo);
+    this.set('content.hostsInfo', hostInfo);
+  },
+
+  /**
+   * Load confirmed hosts.
+   * Will be used at <code>Assign Masters(step5)</code> step
+   */
+  loadConfirmedHosts: function () {
+    this.set('content.hostsInfo', App.db.getHosts());
+  },
+
+  /**
+   * Save data after installation to main controller
+   * @param stepController App.WizardStep9Controller
+   */
+  saveInstalledHosts: function (stepController) {
+    var hosts = stepController.get('hosts');
+    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].message = host.message;
+        hostInfo[index].progress = host.progress;
+      }
+    }
+    App.db.setHosts(hostInfo);
+    console.log('installerController:saveInstalledHosts: save hosts ', hostInfo);
+  },
+
+  /**
+   * Remove all data for hosts
+   */
+  clearHosts: function () {
+    var hosts = this.get('content').get('hosts');
+    if (hosts) {
+      hosts.hostNames = '';
+      hosts.manualInstall = false;
+      hosts.localRepo = '';
+      hosts.localRepopath = '';
+      hosts.sshKey = '';
+      hosts.passphrase = '';
+      hosts.confirmPassphrase = '';
+    }
+  },
+
+  /**
+   * Load services data. Will be used at <code>Select services(step4)</code> step
+   */
+  loadServices: function () {
+    var servicesInfo = App.db.getService();
+    servicesInfo.forEach(function (item, index) {
+      servicesInfo[index] = Em.Object.create(item);
+    });
+    this.set('content.services', servicesInfo);
+    console.log('installerController.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('installerController.saveServices: saved data ', serviceNames);
+  },
+
+  /**
+   * Save Master Component Hosts data to Main Controller
+   * @param stepController App.WizardStep5Controller
+   */
+  saveMasterComponentHosts: function (stepController) {
+    var obj = stepController.get('selectedServicesMasters');
+    var masterComponentHosts = [];
+    obj.forEach(function (_component) {
+      masterComponentHosts.push({
+        component: _component.component_name,
+        hostName: _component.selectedHost
+      });
+    });
+
+    console.log("installerController.saveComponentHosts: saved hosts ", masterComponentHosts);
+    App.db.setMasterComponentHosts(masterComponentHosts);
+    this.set('content.masterComponentHosts', masterComponentHosts);
+  },
+
+  /**
+   * Load master component hosts data for using in required step controllers
+   */
+  loadMasterComponentHosts: function () {
+    var masterComponentHosts = App.db.getMasterComponentHosts();
+    this.set("content.masterComponentHosts", masterComponentHosts);
+    console.log("InstallerController.loadMasterComponentHosts: loaded hosts ", masterComponentHosts);
+  },
+
+  /**
+   * Save slaveHostComponents to main controller
+   * @param stepController
+   */
+  saveSlaveComponentHosts: function (stepController) {
+
+    var hosts = stepController.get('hosts');
+    var isMrSelected = stepController.get('isMrSelected');
+    var isHbSelected = stepController.get('isHbSelected');
+
+    App.db.setHostSlaveComponents(hosts);
+    this.set('content.hostSlaveComponents', hosts);
+
+    var dataNodeHosts = [];
+    var taskTrackerHosts = [];
+    var regionServerHosts = [];
+
+    hosts.forEach(function (host) {
+      if (host.get('isDataNode')) {
+        dataNodeHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+      if (isMrSelected && host.get('isTaskTracker')) {
+        taskTrackerHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+      if (isHbSelected && host.get('isRegionServer')) {
+        regionServerHosts.push({
+          hostname: host.hostname,
+          group: 'Default'
+        });
+      }
+    }, this);
+
+    var slaveComponentHosts = [];
+    slaveComponentHosts.push({
+      componentName: 'DATANODE',
+      displayName: 'DataNode',
+      hosts: dataNodeHosts
+    });
+    if (isMrSelected) {
+      slaveComponentHosts.push({
+        componentName: 'TASKTRACKER',
+        displayName: 'TaskTracker',
+        hosts: taskTrackerHosts
+      });
+    }
+    if (isHbSelected) {
+      slaveComponentHosts.push({
+        componentName: 'HBASE_REGIONSERVER',
+        displayName: 'RegionServer',
+        hosts: regionServerHosts
+      });
+    }
+
+    App.db.setSlaveComponentHosts(slaveComponentHosts);
+    this.set('content.slaveComponentHosts', slaveComponentHosts);
+  },
+
+  /**
+   * Load master component hosts data for using in required step controllers
+   */
+  loadSlaveComponentHosts: function () {
+    var slaveComponentHosts = App.db.getSlaveComponentHosts();
+    this.set("content.slaveComponentHosts", slaveComponentHosts);
+    console.log("InstallerController.loadSlaveComponentHosts: loaded hosts ", slaveComponentHosts);
+
+    var hostSlaveComponents = App.db.getHostSlaveComponents();
+    this.set('content.hostSlaveComponents', hostSlaveComponents);
+    console.log("InstallerController.loadSlaveComponentHosts: loaded hosts ", hostSlaveComponents);
+  },
+
+  /**
+   * TODO:
+   * @param stepController Step7WizardController
+   */
+  saveServiceConfigProperties: function (stepController) {
+    var serviceConfigProperties = [];
+    stepController.get('stepConfigs').forEach(function (_content) {
+      _content.get('configs').forEach(function (_configProperties) {
+        var configProperty = {
+          name: _configProperties.get('name'),
+          value: _configProperties.get('value')
+        };
+        serviceConfigProperties.push(configProperty);
+      }, this);
+
+    }, this);
+
+    App.db.setServiceConfigProperties(serviceConfigProperties);
+    this.set('content.serviceConfigProperties', serviceConfigProperties);
+  },
+
+  /**
+   * Load serviceConfigProperties to model
+   */
+  loadServiceConfigProperties: function () {
+    var serviceConfigProperties = App.db.getServiceConfigProperties();
+    this.set('content.serviceConfigProperties', serviceConfigProperties);
+    console.log("InstallerController.loadServiceConfigProperties: loaded config ", serviceConfigProperties);
+  },
+
+  /**
+   * List of statuses, what data is currently loaded
+   */
+  isStepLoaded: {},
+
+  /**
+   * Call specified function only once
+   */
+  callLoadFuncOnce: function (name) {
+    if (!this.isStepLoaded[name]) {
+      this[name]();
+      this.isStepLoaded[name] = true;
+    }
+  },
+
+  /**
+   * Load data for all steps until <code>current step</code>
+   */
+  loadAllPriorSteps: function () {
+    var step = this.get('currentStep');
+    switch (step) {
+      case '9':
+          //need to call it every time since we preload data in setInfoForStep9
+        this.loadClusterInfo();
+      case '8':
+        this.callLoadFuncOnce('loadClusterInfo');
+      case '7':
+        this.callLoadFuncOnce('loadServiceConfigProperties');
+      case '6':
+        this.callLoadFuncOnce('loadMasterComponentHosts');
+        this.callLoadFuncOnce('loadSlaveComponentHosts');
+      case '5':
+        this.callLoadFuncOnce('loadConfirmedHosts');
+      case '4':
+        this.callLoadFuncOnce('loadServices');
+      case '3':
+      case '2':
+        this.callLoadFuncOnce('loadInstallOptions');
+      case '1':
+        this.callLoadFuncOnce('loadClusterInfo');
+    }
   }
 
 });

+ 0 - 388
ambari-web/app/controllers/installer/step7_controller.js

@@ -1,388 +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.
- */
-
-var App = require('app');
-var db = require('utils/db');
-
-/**
- * By Step 7, we have the following information stored in App.db and set on this
- * controller by the router.
- *
- *   selectedServices: App.db.selectedServices (the services that the user selected in Step 4)
- *   masterComponentHosts: App.db.masterComponentHosts (master-components-to-hosts mapping the user selected in Step 5)
- *   slaveComponentHosts: App.db.slaveComponentHosts (slave-components-to-hosts mapping the user selected in Step 6)
- *
- */
-
-App.InstallerStep7Controller = Em.ArrayController.extend({
-
-  name: 'installerStep7Controller',
-
-  content: [],
-
-  selectedService: null,
-
-  slaveHostToGroup: null,
-
-  isSubmitDisabled: function () {
-    return !this.everyProperty('errorCount', 0);
-  }.property('@each.errorCount'),
-
-  // TODO: set attributes from localStorage in router
-  selectedServiceNames: [ 'HDFS', 'MAPREDUCE', 'GANGLIA', 'NAGIOS', 'HBASE', 'PIG', 'SQOOP', 'OOZIE', 'HIVE', 'ZOOKEEPER'],
-  masterComponentHosts: require('data/mock/master_component_hosts'),
-  slaveComponentHosts: [],
-  serviceConfigs: require('data/service_configs'),
-
-  clearStep: function () {
-    this.clear();
-    this.selectedServiceNames.clear();
-    this.masterComponentHosts.clear();
-//    this.slaveComponentHosts.clear();
-  },
-
-  loadStep: function () {
-    console.log("TRACE: Loading step7: Configure Services");
-    this.clearStep();
-    this.loadConfigs();
-    this.renderServiceConfigs(this.serviceConfigs);
-    var storedServices = db.getServiceConfigProperties();
-    if (storedServices === undefined) {
-      return;
-    } else {
-      var configs = new Ember.Set();
-      var configProperties = new Ember.Set();
-      this.forEach(function (_content) {
-        _content.get('configs').forEach(function (_config) {
-          configs.add(_config);
-        }, this);
-      }, this);
-
-      var configProperties = new Ember.Set();
-      configs.forEach(function (_config) {
-        var temp = {name: _config.get('name'),
-          value: _config.get('value')};
-        configProperties.add(temp);
-        if (storedServices.someProperty('name', _config.get('name'))) {
-          var componentVal = storedServices.findProperty('name', _config.get('name'));
-          _config.set('value', componentVal.value)
-        }
-      }, this);
-    }
-  },
-
-  loadConfigs: function () {
-    // load dependent data from the database
-    var selectedServiceNamesInDB = db.getSelectedServiceNames();
-    if (selectedServiceNamesInDB !== undefined) {
-      this.set('selectedServiceNames', selectedServiceNamesInDB);
-    }
-    var masterComponentHostsInDB = db.getMasterComponentHosts();
-    if (masterComponentHostsInDB != undefined) {
-      this.set('masterComponentHosts', masterComponentHostsInDB);
-    }
-    var slaveComponentHostsInDB = db.getSlaveComponentHosts();
-    if (slaveComponentHostsInDB != undefined) {
-      this.set('slaveComponentHosts', slaveComponentHostsInDB);
-    }
-  },
-
-  renderServiceConfigs: function (serviceConfigs) {
-    var self = this;
-
-    serviceConfigs.forEach(function (_serviceConfig) {
-      var serviceConfig = App.ServiceConfig.create({
-        serviceName: _serviceConfig.serviceName,
-        displayName: _serviceConfig.displayName,
-        configCategories: _serviceConfig.configCategories,
-        configs: []
-      });
-
-      if (self.selectedServiceNames.contains(serviceConfig.serviceName) || serviceConfig.serviceName === 'MISC') {
-        self.renderComponentConfigs(_serviceConfig, serviceConfig);
-      } else {
-        console.log('skipping ' + serviceConfig.serviceName);
-      }
-    }, this);
-  },
-
-  renderComponentConfigs: function (_componentConfig, componentConfig) {
-    _componentConfig.configs.forEach(function (_serviceConfigProperty) {
-      var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
-      serviceConfigProperty.serviceConfig = componentConfig;
-      serviceConfigProperty.initialValue();
-      componentConfig.configs.pushObject(serviceConfigProperty);
-      serviceConfigProperty.validate();
-    }, this);
-
-    console.log('pushing ' + componentConfig.serviceName);
-    this.content.pushObject(componentConfig);
-    this.set('selectedService', this.objectAt(0));
-  },
-
-  submit: function () {
-    if (!this.get('isSubmitDisabled')) {
-      // TODO:
-      // save service configs in App.db (localStorage)
-      var serviceConfigProperties = [];
-      this.content.forEach(function (_content) {
-        var config = [];
-        config = _content.get('configs');
-        config.forEach(function (_configProperties) {
-          var configProperty = {
-            name: _configProperties.get('name'),
-            value: _configProperties.get('value')};
-          serviceConfigProperties.push(configProperty);
-        }, this);
-
-      }, this);
-      db.setServiceConfigProperties(serviceConfigProperties);
-      App.router.send('next');
-    }
-  },
-
-  showMasterHosts: function (event) {
-    var serviceConfig = event.context;
-    App.ModalPopup.show({
-      header: serviceConfig.category + ' Hosts',
-      bodyClass: Ember.View.extend({
-        serviceConfig: serviceConfig,
-        templateName: require('templates/installer/master_hosts_popup')
-      })
-    });
-  },
-
-  showSlaveHosts: function (event) {
-    var serviceConfig = event.context;
-    App.ModalPopup.show({
-      header: serviceConfig.category + ' Hosts',
-      bodyClass: Ember.View.extend({
-        serviceConfig: serviceConfig,
-        templateName: require('templates/installer/slave_hosts_popup')
-      })
-    });
-  }
-
-})
-;
-
-App.SlaveComponentGroupsController = Ember.ArrayController.extend({
-
-  name: 'slaveComponentGroupsController',
-
-  contentBinding: 'App.router.installerStep7Controller.slaveComponentHosts',
-
-  selectedComponentName: function () {
-    switch (App.router.get('installerStep7Controller.selectedService.serviceName')) {
-      case 'HDFS':
-        return 'DATANODE';
-      case 'MAPREDUCE':
-        return 'TASKTRACKER';
-      case 'HBASE':
-        return 'HBASE_REGIONSERVER';
-      default:
-        return null;
-    }
-
-  }.property('App.router.installerStep7Controller.selectedService'),
-
-  selectedSlaveComponent: function () {
-    if (this.get('selectedComponentName') !== null && this.get('selectedComponentName') !== undefined) {
-      return this.findProperty('componentName', this.get('selectedComponentName'));
-    }
-  }.property('selectedComponentName'),
-
-  showAddSlaveComponentGroup: function (event) {
-    var componentName = event.context;
-    var component = this.get('selectedSlaveComponent');
-    App.ModalPopup.show({
-      header: componentName + ' Groups',
-      bodyClass: Ember.View.extend({
-        controllerBinding: 'App.router.slaveComponentGroupsController',
-        templateName: require('templates/installer/slave_component_hosts_popup')
-      }),
-      onPrimary: function (event) {
-        if (component.tempSelectedGroups !== undefined && component.tempSelectedGroups.length) {
-          component.tempSelectedGroups.forEach(function (item) {
-            var changed = component.hosts.filterProperty('hostname', item.hostName);
-            changed.setEach('group', item.groupName);
-          })
-        }
-        delete component.tempSelectedGroups;
-        this.hide();
-      },
-      onSecondary: function (event) {
-        delete component.tempSelectedGroups;
-        this.hide();
-      },
-      onClose: function (event) {
-        delete component.tempSelectedGroups;
-        this.hide();
-      }
-    });
-  },
-
-  changeHostGroup: function (host, groupName) {
-    var component = this.get('selectedSlaveComponent');
-    if (component.tempSelectedGroups === undefined) {
-      component.tempSelectedGroups = [];
-    }
-    var values = component.tempSelectedGroups.filterProperty('hostName', host.hostname);
-    if (values.length === 0)
-      component.tempSelectedGroups.pushObject({hostName: host.hostname, groupName: groupName});
-    else
-      values.setEach('groupName', groupName);
-
-  },
-
-  addSlaveComponentGroup: function (event) {
-    var component = this.get('selectedSlaveComponent');
-    var newGroupName = 'New Group';
-    component.groups.setEach('active', false);
-    var newGroups = component.groups.filterProperty('name', newGroupName);
-    if (newGroups.length === 0)
-      component.newGroupIndex = 0;
-    else {
-      if (component.newGroupIndex === undefined)
-        component.newGroupIndex = 0;
-      this.checkGroupName();
-      newGroupName = 'New Group ' + component.newGroupIndex;
-    }
-    var newGroup = {name: newGroupName, index: component.newGroupIndex, type: 'new', active: true};
-    component.groups.pushObject(newGroup);
-    $('.remove-group-error').hide();
-  },
-
-  checkGroupName: function () {
-    var component = this.get('selectedSlaveComponent');
-    component.newGroupIndex++;
-    var newGroupName = 'New Group ' + component.newGroupIndex;
-    var groups = component.groups.filterProperty('name', newGroupName);
-    if (groups.length !== 0) {
-      this.checkGroupName();
-    }
-  },
-
-  showEditSlaveComponentGroups: function (event) {
-    this.showAddSlaveComponentGroup(event);
-  },
-
-  hosts: function () {
-    if (this.get('selectedComponentName') !== null && this.get('selectedComponentName') !== undefined) {
-      var component = this.findProperty('componentName', this.get('selectedComponentName'));
-      if (component !== undefined && component !== null) {
-        return component.hosts;
-      }
-    }
-  }.property('@each.hosts', 'selectedComponentName'),
-
-  groups: function () {
-    if (this.get('selectedComponentName') !== null) {
-      var component = this.findProperty('componentName', this.get('selectedComponentName'));
-      if (component !== undefined && component !== null) {
-        return component.hosts.mapProperty('group').uniq();
-      }
-    }
-  }.property('@each.hosts', 'selectedComponentName'),
-
-  componentGroups: function () {
-    if (this.get('selectedComponentName') !== null) {
-      var component = this.get('selectedSlaveComponent');
-      if (component !== undefined && component !== null) {
-        if (component.groups === undefined) {
-          component.groups = [];
-          var defaultGroup = {name: 'Default', index: 'default', type: 'default', active: true};
-          component.groups.pushObject(defaultGroup);
-        }
-        return component.groups;
-      }
-    }
-  }.property('selectedSlaveComponent'),
-
-  getHostsByGroup: function (group) {
-    var component = this.get('selectedSlaveComponent');
-    return component.hosts.filterProperty('group', group.name);
-  },
-
-  getGroupsForDropDown: function () {
-    return this.get('componentGroups').getEach('name');
-  }.property('selectedComponentName', 'componentGroups.@each.name'),
-
-  activeGroup: function () {
-    if (this.get('componentGroups') !== undefined) {
-      var active = this.get('componentGroups').findProperty('active', true);
-      if (active !== undefined)
-        return active;
-    }
-  }.property('selectedComponentName', 'componentGroups.@each.active', 'componentGroups.@each.name'),
-
-  showSlaveComponentGroup: function (event) {
-    var component = this.get('selectedSlaveComponent');
-    component.groups.setEach('active', false);
-    var group = component.groups.filterProperty('name', event.context.name);
-    group.setEach('active', true);
-    var assignedHosts = component.hosts.filterProperty('group', event.context.name);
-    if (assignedHosts.length === 0) {
-      $('.remove-group-error').hide();
-    }
-  },
-
-  removeSlaveComponentGroup: function (event) {
-    var group = event.context;
-    var component = this.get('selectedSlaveComponent');
-    var assignedHosts = component.hosts.filterProperty('group', group.name);
-    if (assignedHosts.length !== 0) {
-      $('.remove-group-error').show();
-    } else {
-      $('.remove-group-error').hide();
-      var key = component.groups.indexOf(group);
-      component.groups.removeObject(component.groups[key]);
-
-      var newGroups = component.groups.filterProperty('type', 'new');
-      if (newGroups.length == 0)
-        component.newGroupIndex = 0;
-      else {
-        var lastNewGroup = newGroups[newGroups.length - 1];
-        component.newGroupIndex = lastNewGroup.index;
-      }
-      if (group.active) {
-        var lastGroup;
-        if (key === component.groups.length)
-          lastGroup = component.groups.slice(key - 1, key);
-        else lastGroup = component.groups.slice(key, key + 1);
-        lastGroup.setEach('active', true);
-      }
-    }
-  },
-
-  changeSlaveGroupName: function (group, newGroupName) {
-    var component = this.get('selectedSlaveComponent');
-    var isExist = component.groups.filterProperty('name', newGroupName);
-    if (isExist.length !== 0)
-      return true;
-    else {
-      var assignedHosts = component.hosts.filterProperty('group', group.name);
-      if (assignedHosts.length !== 0)
-        assignedHosts.setEach('group', newGroupName);
-      var groupFilter = component.groups.filterProperty('name', group.name);
-      groupFilter.setEach('name', newGroupName);
-    }
-    return false;
-  }
-
-});

+ 46 - 1
ambari-web/app/controllers/main.js

@@ -17,7 +17,52 @@
  */
 
 var App = require('app');
+require('models/background_operation');
 
 App.MainController = Em.Controller.extend({
-  name: 'mainController'
+  name: 'mainController',
+  backgroundOperations: null,
+  intervalId: false,
+  updateOperationsInterval: 8000,
+
+  startLoadOperationsPeriodically: function() {
+    this.intervalId = setInterval(this.loadBackgroundOperations, this.get('updateOperationsInterval'));
+  },
+  stopLoadOperationsPeriodically:function () {
+    if(this.intervalId) {
+      clearInterval(this.intervalId);
+    }
+    this.intervalId = false;
+  },
+  loadBackgroundOperations: function(){
+    var self = App.router.get('mainController');
+    jQuery.getJSON('data/hosts/background_operations/bg_operations.json',
+      function (data) {
+        var backgroundOperations = self.get('backgroundOperations');
+        if(!backgroundOperations || self.get('backgroundOperationsCount') >= 6)
+          self.set('backgroundOperations', data);
+        else backgroundOperations.tasks.pushObjects(data['tasks'])
+      }
+    )
+  },
+
+  backgroundOperationsCount: function() {
+    return this.get('backgroundOperations.tasks.length');
+  }.property('backgroundOperations.tasks.length'),
+
+  showBackgroundOperationsPopup: function(){
+    App.ModalPopup.show({
+      headerClass: Ember.View.extend({
+        controllerBinding: 'App.router.mainController',
+        template:Ember.Handlebars.compile('{{backgroundOperationsCount}} Background Operations Running')
+      }),
+      bodyClass: Ember.View.extend({
+        controllerBinding: 'App.router.mainController',
+        templateName: require('templates/main/background_operations_popup')
+      }),
+      onPrimary: function() {
+        this.hide();
+      }
+    });
+  }
 })

+ 133 - 103
ambari-web/app/controllers/main/host.js

@@ -17,109 +17,102 @@
  */
 
 var App = require('app');
+var validator = require('utils/validator');
 require('models/service');
 require('models/cluster');
 require('models/host');
-require('models/background_operation');
 
 App.MainHostController = Em.ArrayController.extend(App.Pagination, {
   name:'mainHostController',
-  content: [],
-  fullContent: App.Host.find(),
-  clusters: App.Cluster.find(),
-  componentsForFilter: App.Component.find(),
-  totalBinding: 'fullContent.length',
-  filters: {components:[]},
-  pageSize: 3,
-  pageSizeRange: [1,3,5,'all'],
-  rangeStart: 0,
-  allChecked: false,
-  selectedHostsIds: [],
-  sortingAsc: true,
-  isSort: false,
-  intervalId: false,
-  updateOperationsInterval: 8000,
-  sortClass: function(){
-    return this.get('sortingAsc')? 'icon-arrow-down' : 'icon-arrow-up';
+  content:[],
+  fullContent:App.Host.find(),
+  clusters:App.Cluster.find(),
+  componentsForFilter:App.Component.find(),
+  totalBinding:'fullContent.length',
+  filters:{components:[]},
+  pageSize:3,
+  pageSizeRange:[1, 3, 5, 'all'],
+  rangeStart:0,
+  allChecked:false,
+  selectedHostsIds:[],
+  sortingAsc:true,
+  isSort:false,
+  sortClass:function () {
+    return this.get('sortingAsc') ? 'icon-arrow-down' : 'icon-arrow-up';
   }.property('sortingAsc'),
   isDisabled:true,
-  backgroundOperations: null,
-  startLoadOperationsPeriodically: function() {
-    this.intervalId = setInterval(this.loadBackgroundOperations, this.get('updateOperationsInterval'));
-  },
-  stopLoadOperationsPeriodically:function () {
-    if(this.intervalId) {
-      clearInterval(this.intervalId);
-    }
-    this.intervalId = false;
-  },
-  loadBackgroundOperations: function(){
-    var self = App.router.get('mainHostController');
-    jQuery.getJSON('data/hosts/background_operations/bg_operations.json',
-      function (data) {
-        var backgroundOperations = self.get('backgroundOperations');
-        if(!backgroundOperations)
-          self.set('backgroundOperations', data);
-        else backgroundOperations.tasks.pushObjects(data['tasks'])
-      }
-    )
-  },
 
   checkRemoved: function(host_id) {
     var hosts = this.get('content');
     var selectedHosts = hosts.filterProperty('id', host_id);
     this.get('fullContent').removeObjects(selectedHosts);
   },
+  masterComponents:function () {
+    var components = [];
+    this.get('componentsForFilter').forEach(function (component) {
+      if (component.get('isMaster')) {
+        components.push(component);
+      }
+    });
+    return components;
+  }.property('componentsForFilter'),
 
-  backgroundOperationsCount: function() {
-    return this.get('backgroundOperations.tasks.length');
-  }.property('backgroundOperations.tasks.length'),
-
-  showBackgroundOperationsPopup: function(){
-    var self = this;
-    App.ModalPopup.show({
-      header: self.get('backgroundOperationsCount') + ' Background Operations Running',
-      bodyClass: Ember.View.extend({
-        controllerBinding: 'App.router.mainHostController',
-        templateName: require('templates/main/host/background_operations_popup')
-      }),
-      onPrimary: function() {
-        this.hide();
+  slaveComponents:function () {
+    var components = [];
+    this.get('componentsForFilter').forEach(function (component) {
+      if (component.get('isSlave')) {
+        components.push(component);
       }
     });
-  },
+    return components;
+  }.property('componentsForFilter'),
+
+  backgroundOperationsCount:function () {
+    return 5;
+  }.property(),
 
-  onAllChecked: function () {
+  onAllChecked:function () {
     var hosts = this.get('content');
     hosts.setEach('isChecked', this.get('allChecked'));
     this.set('isDisabled', !this.get('allChecked'));
-    var selectedHostsIds = this.get('allChecked') ? hosts.getEach('id'):[];
+    var selectedHostsIds = this.get('allChecked') ? hosts.getEach('id') : [];
     this.set('selectedHostsIds', selectedHostsIds);
   }.observes('allChecked'),
 
-  onHostChecked: function (host) {
+  onHostChecked:function (host) {
     var selected = this.get('selectedHostsIds');
     host.set('isChecked', !host.get('isChecked'));
     if (host.get('isChecked')) selected.push(host.get('id'));
     else {
       var index = selected.indexOf(host.get('id'));
-      if(index!=-1) selected.splice(index, 1);
+      if (index != -1) selected.splice(index, 1);
     }
     this.set('isDisabled', selected.length == 0);
   },
 
-  changeSelectedHosts: function() {
+  changeSelectedHosts:function () {
     var visibleHosts = this.get('content');
     var selectedHosts = visibleHosts.filterProperty('isChecked', true);
-    this.get('fullContent').forEach(function(item) {
+    this.get('fullContent').forEach(function (item) {
       var index = visibleHosts.getEach('id').indexOf(item.get('id'));
-      if(index == -1) item.set('isChecked', false);
+      if (index == -1) item.set('isChecked', false);
     });
     this.set('isDisabled', selectedHosts.length == 0);
     this.set('selectedHostsIds', selectedHosts.getEach('id'));
   },
 
-  filterByComponentsIds: function(componentsIds) {
+  checkedComponentsIds:function () {
+    var checked = [];
+    this.get('componentsForFilter').forEach(function (comp) {
+      if (comp.get('checkedForHostFilter'))
+        checked.push(comp.get('id'));
+    });
+
+    return checked;
+  },
+
+  filterByComponentsIds:function () {
+    var componentsIds = this.checkedComponentsIds();
     this.set('filters.components', componentsIds);
     this.get('componentsForFilter').forEach(function(component) {
       if (componentsIds.indexOf(component.get('id')) != -1){
@@ -129,100 +122,138 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
     this.changeContent();
   },
 
-  filterByComponent: function(component) {
+  filterHostsBy:function (field, value) {
+    this.set('hostFilter' + field, value);
+    this.changeContent();
+  },
+
+  filterByComponent:function (component) {
     this.get('componentsForFilter').setEach('isChecked', false);
     component.set('isChecked', true);
     this.set('filters.components', [component.get('id')]);
     this.changeContent();
   },
 
-  changeContent: function() {
+
+  applyHostFilters:function (items) {
+
+    var field = 'hostName'; // make this function universal
+    var value = this.get('hostFilter' + field);
+
+    var itemsToDelete = [];
+    if (value) {
+      items.forEach(function (host, index) {
+        if (host) {
+          var fieldValue = host.get(field);
+          if (fieldValue) {
+            if (fieldValue.indexOf(value) == -1) {
+              itemsToDelete.push(host);
+            }
+          }
+        }
+      });
+    }
+
+    if (itemsToDelete.length) {
+      itemsToDelete.forEach(function (hostToDelete) {
+        var index = items.indexOf(hostToDelete);
+        items.removeAt(index);
+      })
+    }
+
+    return items;
+  },
+
+  changeContent:function () {
     var items = [];
     var filters = this.get('filters.components');
-    if (filters.length){
-      this.get('fullContent').forEach(function(item) {
+    this.get('fullContent').forEach(function (item) {
+      if (filters.length) {
         var inFilters = false;
         item.get('components').forEach(function(component) {
           if (filters.indexOf(component.get('id')) == -1){
             inFilters = true;
           }
         });
-        if (inFilters){
+        if (inFilters) {
           items.push(item);
         }
-      });
-      this.set('total', items.length);
-    } else {
-      items = this.get('fullContent');
-      this.set('total', this.get('fullContent.length'));
-    }
+
+      } else {
+        items.push(item);
+      }
+    });
+
+    items = this.applyHostFilters(items);
+    this.set('total', items.length);
+
     var content = items.slice(this.get('rangeStart'), this.get('rangeStop'));
     this.replace(0, this.get('length'), content);
     this.changeSelectedHosts();
   }.observes('rangeStart', 'rangeStop', 'total'),
 
-  showNextPage: function() {
+  showNextPage:function () {
     this.nextPage();
   },
-  showPreviousPage: function() {
+  showPreviousPage:function () {
     this.previousPage();
   },
-  assignedToRackPopup: function(event) {
+  assignedToRackPopup:function (event) {
     var self = this;
     App.ModalPopup.show({
-      header: Em.I18n.t('hosts.assignedToRack.popup.header'),
-      body: Em.I18n.t('hosts.assignedToRack.popup.body'),
-      primary: 'Yes',
-      secondary: 'No',
-      onPrimary: function() {
+      header:Em.I18n.t('hosts.assignedToRack.popup.header'),
+      body:Em.I18n.t('hosts.assignedToRack.popup.body'),
+      primary:'Yes',
+      secondary:'No',
+      onPrimary:function () {
         self.assignedToRack(event.context);
         this.hide();
       },
-      onSecondary: function() {
+      onSecondary:function () {
         this.hide();
       }
     });
   },
 
-  assignedToRack: function(rack) {
+  assignedToRack:function (rack) {
     var hosts = this.get('content');
     var selectedHosts = hosts.filterProperty('isChecked', true);
     selectedHosts.setEach('cluster', rack);
   },
 
-  decommissionButtonPopup: function() {
+  decommissionButtonPopup:function () {
     var self = this;
     App.ModalPopup.show({
-      header: Em.I18n.t('hosts.decommission.popup.header'),
-      body: Em.I18n.t('hosts.decommission.popup.body'),
-      primary: 'Yes',
-      secondary: 'No',
-      onPrimary: function() {
+      header:Em.I18n.t('hosts.decommission.popup.header'),
+      body:Em.I18n.t('hosts.decommission.popup.body'),
+      primary:'Yes',
+      secondary:'No',
+      onPrimary:function () {
         alert('do');
         this.hide();
       },
-      onSecondary: function() {
+      onSecondary:function () {
         this.hide();
       }
     });
   },
-  deleteButtonPopup: function() {
+  deleteButtonPopup:function () {
     var self = this;
     App.ModalPopup.show({
-      header: Em.I18n.t('hosts.delete.popup.header'),
-      body: Em.I18n.t('hosts.delete.popup.body'),
-      primary: 'Yes',
-      secondary: 'No',
-      onPrimary: function() {
+      header:Em.I18n.t('hosts.delete.popup.header'),
+      body:Em.I18n.t('hosts.delete.popup.body'),
+      primary:'Yes',
+      secondary:'No',
+      onPrimary:function () {
         self.removeHosts();
         this.hide();
       },
-      onSecondary: function() {
+      onSecondary:function () {
         this.hide();
       }
     });
   },
-  removeHosts: function () {
+  removeHosts:function () {
     var hosts = this.get('content');
     var selectedHosts = hosts.filterProperty('isChecked', true);
     selectedHosts.forEach(function (_hostInfo) {
@@ -231,15 +262,14 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
 //    App.db.removeHosts(selectedHosts);
     this.get('fullContent').removeObjects(selectedHosts);
   },
-  sortByName: function () {
+  sortByName:function () {
     var asc = this.get('sortingAsc');
-    var objects = this.get('fullContent').toArray().sort(function(a, b)
-    {
+    var objects = this.get('fullContent').toArray().sort(function (a, b) {
       var nA = a.get('hostName').toLowerCase();
       var nB = b.get('hostName').toLowerCase();
-      if(nA < nB)
+      if (nA < nB)
         return asc ? -1 : 1;
-      else if(nA > nB)
+      else if (nA > nB)
         return asc ? 1 : -1;
       return 0;
     });

+ 160 - 10
ambari-web/app/controllers/main/host/add_controller.js

@@ -25,8 +25,32 @@ App.AddHostController = Em.Controller.extend({
 
   /**
    * All wizards data will be stored in this variable
+   *
+   * cluster - cluster name
+   * hosts - hosts, ssh key, repo info, etc.
+   * services - services list
+   * hostsInfo - list of selected hosts
+   * slaveComponentHosts, hostSlaveComponents - info about slave hosts
+   * masterComponentHosts - info about master hosts
+   * config??? - to be described later
    */
-  content: Em.Object.create(),
+  content: Em.Object.create({
+    cluster: {},
+    hosts: {},
+    services: {},
+    hostsInfo: {},
+    slaveComponentHosts: {},
+    hostSlaveComponents: {},
+    masterComponentHosts: {},
+    serviceConfigProperties: {}
+  }),
+
+  /**
+   * List of statuses, what data is currently loaded
+   */
+  isStepLoaded: {
+
+  },
 
   /**
    * Used for hiding back button in wizard
@@ -165,6 +189,52 @@ App.AddHostController = Em.Controller.extend({
     this.gotoStep(10);
   },
 
+  /**
+   * Load clusterInfo(step1) to model
+   */
+  loadClusterInfo: function(){
+    var cluster = {
+      name: App.db.getClusterName(),
+      status: App.db.getClusterStatus().status,
+      isCompleted: App.db.getClusterStatus().isCompleted
+    };
+    this.set('content.cluster', cluster);
+    console.log("AddHostController:loadClusterInfo: loaded data ", cluster);
+  },
+
+  /**
+   * Save all info about claster to model
+   * @param stepController Step1WizardController
+   */
+  saveClusterInfo: function (stepController) {
+    var cluster = stepController.get('content.cluster');
+    var clusterStatus = {
+      status: cluster.status,
+      isCompleted: cluster.isCompleted
+    }
+    App.db.setClusterName(cluster.name);
+    App.db.setClusterStatus(clusterStatus);
+
+    console.log("AddHostController:saveClusterInfo: saved data ", cluster);
+
+    //probably next line is extra work - need to check it
+    this.set('content.cluster', cluster);
+  },
+
+  /**
+   * Temporary function for wizardStep9, before back-end integration
+   */
+  setInfoForStep9: function () {
+    App.db.setClusterStatus({status: 'pending', isCompleted: false});
+    var hostInfo = App.db.getHosts();
+    for (var index in hostInfo) {
+      hostInfo[index].status = "pending";
+      hostInfo[index].message = 'Information';
+      hostInfo[index].progress = '0';
+    }
+    App.db.setHosts(hostInfo);
+  },
+
   /**
    * Load all data for <code>Specify Host(install step2)</code> step
    * Data Example:
@@ -249,7 +319,7 @@ App.AddHostController = Em.Controller.extend({
       });
 
       hosts.pushObject(hostInfo);
-    };
+    }
 
     console.log('TRACE: pushing ' + hosts);
     return hosts;
@@ -287,10 +357,31 @@ App.AddHostController = Em.Controller.extend({
    * Load confirmed hosts.
    * Will be used at <code>Assign Masters(step5)</code> step
    */
-  loadConfirmedHosts : function(){
+  loadConfirmedHosts: function(){
     this.set('content.hostsInfo', App.db.getHosts());
   },
 
+  /**
+   * Save data after installation to main controller
+   * @param stepController App.WizardStep9Controller
+   */
+  saveInstalledHosts: function (stepController) {
+    var hosts = stepController.get('hosts');
+    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].message = host.message;
+        hostInfo[index].progress = host.progress;
+      }
+    }
+    App.db.setHosts(hostInfo);
+    console.log('addHostController:saveInstalledHosts: save hosts ', hostInfo);
+  },
+
   /**
    * Remove all data for hosts
    */
@@ -368,7 +459,7 @@ App.AddHostController = Em.Controller.extend({
    * Save slaveHostComponents to main controller
    * @param stepController
    */
-  saveSlaveComponentHosts : function(stepController){
+  saveSlaveComponentHosts: function (stepController) {
 
     var hosts = stepController.get('hosts');
     var isMrSelected = stepController.get('isMrSelected');
@@ -427,25 +518,84 @@ App.AddHostController = Em.Controller.extend({
     this.set('content.slaveComponentHosts', slaveComponentHosts);
   },
 
+  /**
+   * Load master component hosts data for using in required step controllers
+   */
+  loadSlaveComponentHosts: function () {
+    var slaveComponentHosts = App.db.getSlaveComponentHosts();
+    this.set("content.slaveComponentHosts", slaveComponentHosts);
+    console.log("AddHostController.loadSlaveComponentHosts: loaded hosts ", slaveComponentHosts);
+
+    var hostSlaveComponents = App.db.getHostSlaveComponents();
+    this.set('content.hostSlaveComponents', hostSlaveComponents);
+    console.log("AddHostController.loadSlaveComponentHosts: loaded hosts ", hostSlaveComponents);
+  },
+
+  /**
+   * TODO:
+   * @param stepController Step7WizardController
+   */
+  saveServiceConfigProperties: function (stepController) {
+    var serviceConfigProperties = [];
+    stepController.get('stepConfigs').forEach(function (_content) {
+      _content.get('configs').forEach(function (_configProperties) {
+        var configProperty = {
+          name: _configProperties.get('name'),
+          value: _configProperties.get('value')
+        };
+        serviceConfigProperties.push(configProperty);
+      }, this);
+
+    }, this);
+
+    App.db.setServiceConfigProperties(serviceConfigProperties);
+    this.set('content.serviceConfigProperties', serviceConfigProperties);
+  },
+
+  /**
+   * Load serviceConfigProperties to model
+   */
+  loadServiceConfigProperties: function () {
+    var serviceConfigProperties = App.db.getServiceConfigProperties();
+    this.set('content.serviceConfigProperties', serviceConfigProperties);
+    console.log("AddHostController.loadServiceConfigProperties: loaded config ", serviceConfigProperties);
+  },
+
+  /**
+   * Call specified function only once
+   */
+  callLoadFuncOnce: function (name) {
+    if (!this.isStepLoaded[name]) {
+      this[name]();
+      this.isStepLoaded[name] = true;
+    }
+  },
+
   /**
    * Load data for all steps until <code>current step</code>
    */
   loadAllPriorSteps: function () {
     var step = this.get('currentStep');
     switch (step) {
+      case '8':
+          //need to call it every time since we preload data in setInfoForStep9
+        this.loadClusterInfo();
       case '7':
-        //current
+        this.callLoadFuncOnce('loadClusterInfo');
       case '6':
-        //Sasha
+        this.callLoadFuncOnce('loadServiceConfigProperties');
       case '5':
-        this.loadMasterComponentHosts();
+        this.callLoadFuncOnce('loadMasterComponentHosts');
+        this.callLoadFuncOnce('loadSlaveComponentHosts');
       case '4':
-        this.loadConfirmedHosts();
+        this.callLoadFuncOnce('loadConfirmedHosts');
       case '3':
-        this.loadServices();
+        this.callLoadFuncOnce('loadServices');
       case '2':
       case '1':
-        this.loadInstallOptions();
+        this.callLoadFuncOnce('loadInstallOptions');
+      case '0':
+        this.callLoadFuncOnce('loadClusterInfo');
     }
   },
 

+ 131 - 7
ambari-web/app/controllers/main/host/details.js

@@ -22,6 +22,7 @@ App.MainHostDetailsController = Em.Controller.extend({
   name: 'mainHostDetailsController',
   content: null,
   isFromHosts: false,
+  backgroundOperations: [],
   isStarting: true,
   isStopping: function(){
     return !this.get('isStarting');
@@ -31,6 +32,31 @@ App.MainHostDetailsController = Em.Controller.extend({
     this.set('isFromHosts', isFromHosts);
   },
 
+  hostOperations: function(){
+    var hostName = this.get('content.hostName');
+    return this.get('backgroundOperations').filterProperty('hostName', hostName);
+  }.property('backgroundOperations.length', 'content'),
+
+  hostOperationsCount: function() {
+    return this.get('hostOperations.length');
+  }.property('backgroundOperations.length', 'content'),
+
+  showBackgroundOperationsPopup: function(){
+    App.ModalPopup.show({
+      headerClass: Ember.View.extend({
+        controllerBinding: 'App.router.mainHostDetailsController',
+        template:Ember.Handlebars.compile('{{hostOperationsCount}} Background Operations Running')
+      }),
+      bodyClass: Ember.View.extend({
+        controllerBinding: 'App.router.mainHostDetailsController',
+        templateName: require('templates/main/host/background_operations_popup')
+      }),
+      onPrimary: function() {
+        this.hide();
+      }
+    });
+  },
+
   startComponent: function(event){
     var self = this;
     App.ModalPopup.show({
@@ -41,6 +67,17 @@ App.MainHostDetailsController = Em.Controller.extend({
       onPrimary: function() {
         var component = event.context;
         component.set('workStatus', true);
+        var backgroundOperations = self.get('backgroundOperations');
+        backgroundOperations.pushObject({
+          "hostName": self.get('content.hostName'),
+          "role":component.get('componentName'),
+          "command": "START",
+          "details": [
+            {"startTime":"4 min ago", "name":"Some intermediate operation"},
+            {"startTime":"5 min ago", "name":"Component started"}
+          ],
+          "logs":{"exitcode":"404", "stdout":27, "stderror":501}
+        });
         var stopped = self.get('content.components').filterProperty('workStatus', false);
         if (stopped.length == 0)
           self.set('isStarting', true);
@@ -61,6 +98,17 @@ App.MainHostDetailsController = Em.Controller.extend({
       onPrimary: function() {
         var component = event.context;
         component.set('workStatus', false);
+        var backgroundOperations = self.get('backgroundOperations');
+        backgroundOperations.pushObject({
+          "hostName": self.get('content.hostName'),
+          "role": component.get('componentName'),
+          "command": "STOP",
+          "details": [
+            {"startTime":"4 min ago", "name":"Some intermediate operation"},
+            {"startTime":"5 min ago", "name":"Component stopped"}
+          ],
+          "logs":{"exitcode":"404", "stdout":15, "stderror":501}
+        });
         var started = self.get('content.components').filterProperty('workStatus', true);
         if (started.length == 0)
           self.set('isStarting', false);
@@ -70,9 +118,37 @@ App.MainHostDetailsController = Em.Controller.extend({
         this.hide();
       }
     });
+  },
 
+  decommission: function(event){
+    App.ModalPopup.show({
+      header: Em.I18n.t('hosts.host.start.popup.header'),
+      body: Em.I18n.t('hosts.host.start.popup.body'),
+      primary: 'Yes',
+      secondary: 'No',
+      onPrimary: function() {
+        this.hide();
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    });
   },
 
+  recommission: function(event){
+    App.ModalPopup.show({
+      header: Em.I18n.t('hosts.host.start.popup.header'),
+      body: Em.I18n.t('hosts.host.start.popup.body'),
+      primary: 'Yes',
+      secondary: 'No',
+      onPrimary: function() {
+        this.hide();
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    });
+  },
   startConfirmPopup: function (event) {
     var self = this;
     App.ModalPopup.show({
@@ -107,7 +183,58 @@ App.MainHostDetailsController = Em.Controller.extend({
       }
     });
   },
-  deleteButtonPopup: function(event) {
+
+  validateDeletion: function() {
+    var slaveComponents = ['DataNode', 'TaskTracker', 'RegionServer'];
+    var masterComponents = [];
+    var workingComponents = [];
+
+    var components = this.get('content.components');
+    components.forEach(function(cInstance){
+      var cName = cInstance.get('componentName');
+      if(slaveComponents.contains(cName)) {
+        if(cInstance.get('workStatus')){
+          workingComponents.push(cName);
+        }
+      } else {
+        masterComponents.push(cName);
+      }
+    });
+    //debugger;
+    if(workingComponents.length || masterComponents.length) {
+      this.raiseWarning(workingComponents, masterComponents);
+    } else {
+      this.deleteButtonPopup();
+    }
+  },
+
+  raiseWarning: function (workingComponents, masterComponents) {
+    var self = this;
+    var masterString = '';
+    var workingString = '';
+    if(masterComponents && masterComponents.length) {
+      var masterList = masterComponents.join(', ');
+      var ml_text = Em.I18n.t('hosts.cant.do.popup.masterList.body');
+      masterString = ml_text.format(masterList);
+    }
+    if(workingComponents && workingComponents.length) {
+      var workingList = workingComponents.join(', ');
+      var wl_text = Em.I18n.t('hosts.cant.do.popup.workingList.body');
+      workingString = wl_text.format(workingList);
+    }
+    App.ModalPopup.show({
+      header: Em.I18n.t('hosts.cant.do.popup.header'),
+      html: true,
+      body: masterString + workingString,
+      primary: "OK",
+      secondary: null,
+      onPrimary: function() {
+        this.hide();
+      }
+    })
+  },
+
+  deleteButtonPopup: function() {
     var self = this;
     App.ModalPopup.show({
       header: Em.I18n.t('hosts.delete.popup.header'),
@@ -115,7 +242,7 @@ App.MainHostDetailsController = Em.Controller.extend({
       primary: 'Yes',
       secondary: 'No',
       onPrimary: function() {
-        self.removeHost(event);
+        self.removeHost();
         this.hide();
       },
       onSecondary: function() {
@@ -124,11 +251,8 @@ App.MainHostDetailsController = Em.Controller.extend({
     });
   },
   removeHost: function () {
-    var clientId = this.get('content.clientId');
-    var host_ids = this.get('content.store.clientIdToId');
-    var host_id = host_ids[clientId];
-    App.router.get('mainHostController').checkRemoved(host_id);
+    App.router.get('mainHostController').checkRemoved(this.get('content.id'));
     App.router.transitionTo('hosts');
-
   }
+
 })

+ 268 - 0
ambari-web/app/controllers/wizard/slave_component_groups_controller.js

@@ -0,0 +1,268 @@
+/**
+ * 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');
+/**
+ * Used to manage slave component config. User could create different settings for separate group
+ * @type {*}
+ */
+App.SlaveComponentGroupsController = Em.ArrayController.extend({
+
+  name: 'slaveComponentGroupsController',
+
+  contentBinding: 'App.router.wizardStep7Controller.slaveComponentHosts',
+
+  serviceBinding: 'App.router.wizardStep7Controller.selectedService',
+
+  selectedComponentName: function () {
+    switch (App.router.get('wizardStep7Controller.selectedService.serviceName')) {
+      case 'HDFS':
+        return 'DATANODE';
+      case 'MAPREDUCE':
+        return 'TASKTRACKER';
+      case 'HBASE':
+        return 'HBASE_REGIONSERVER';
+      default:
+        return null;
+    }
+
+  }.property('App.router.wizardStep7Controller.selectedService'),
+
+  selectedSlaveComponent: function () {
+    var selectedComponentName = this.get('selectedComponentName');
+    if (selectedComponentName) {
+      return this.findProperty('componentName', selectedComponentName);
+    }
+  }.property('selectedComponentName'),
+
+  hosts: function () {
+    var selectedComponentName = this.get('selectedComponentName');
+    if (selectedComponentName) {
+      var component = this.findProperty('componentName', selectedComponentName);
+      if(component){
+        return component.hosts;
+      }
+    }
+  }.property('@each.hosts', 'selectedComponentName'),
+
+  groups: function () {
+    var hosts = this.get('hosts');
+    if(hosts){
+      return hosts.mapProperty('group').uniq();
+    }
+  }.property('hosts'),
+
+  componentGroups: function () {
+    var component = this.get('selectedSlaveComponent');
+    if(component){
+        if (!component.groups) {
+          component.groups = [];
+          var defaultGroup = {name: 'Default', index: 'default', type: 'default', active: true};
+          component.groups.pushObject(defaultGroup);
+        }
+        return component.groups;
+    }
+    return [];
+  }.property('selectedSlaveComponent'),
+
+  getGroupsForDropDown: function () {
+    return this.get('componentGroups').getEach('name');
+  }.property('selectedComponentName', 'componentGroups.@each.name'),
+
+  activeGroup: function () {
+    var componentGroups = this.get('componentGroups');
+    if (componentGroups) {
+      var active = componentGroups.findProperty('active', true);
+      if (active){
+        return active;
+      }
+    }
+    return null;
+  }.property('componentGroups.@each.active', 'componentGroups.@each.name'),
+
+
+  /**
+   * Show slave hosts to groups popup
+   * @param event
+   */
+  showAddSlaveComponentGroup: function (event) {
+    var componentName = event.context;
+    var component = this.get('selectedSlaveComponent');
+    App.ModalPopup.show({
+      header: componentName + ' Groups',
+      bodyClass: Ember.View.extend({
+        controllerBinding: 'App.router.slaveComponentGroupsController',
+        templateName: require('templates/wizard/slave_component_hosts_popup')
+      }),
+      onPrimary: function (event) {
+        if (component.tempSelectedGroups && component.tempSelectedGroups.length) {
+          component.tempSelectedGroups.forEach(function (item) {
+            var changed = component.hosts.filterProperty('hostname', item.hostName);
+            changed.setEach('group', item.groupName);
+          })
+        }
+        delete component.tempSelectedGroups;
+        this.hide();
+      },
+      onSecondary: function (event) {
+        delete component.tempSelectedGroups;
+        this.hide();
+      },
+      onClose: function (event) {
+        delete component.tempSelectedGroups;
+        this.hide();
+      }
+    });
+  },
+
+  /**
+   * Utility method. Save temporary info about changes in <code>slave hosts to groups</code> popup
+   * @param host
+   * @param groupName
+   */
+  changeHostGroup: function (host, groupName) {
+    var component = this.get('selectedSlaveComponent');
+    if (component.tempSelectedGroups === undefined) {
+      component.tempSelectedGroups = [];
+    }
+    var values = component.tempSelectedGroups.filterProperty('hostName', host.hostname);
+    if (values.length === 0)
+      component.tempSelectedGroups.pushObject({hostName: host.hostname, groupName: groupName});
+    else
+      values.setEach('groupName', groupName);
+
+  },
+
+  /**
+   * add new group to component(click on button)
+   */
+  addSlaveComponentGroup: function () {
+    var component = this.get('selectedSlaveComponent');
+    var newGroupName = 'New Group';
+    component.groups.setEach('active', false);
+    var newGroups = component.groups.filterProperty('name', newGroupName);
+    if (newGroups.length === 0){
+      component.newGroupIndex = 0;
+    }
+    else {
+      component.newGroupIndex = component.newGroupIndex || 0;
+      this.checkGroupName();
+      newGroupName = 'New Group ' + component.newGroupIndex;
+    }
+    var newGroup = {name: newGroupName, index: component.newGroupIndex, type: 'new', active: true};
+    component.groups.pushObject(newGroup);
+    $('.remove-group-error').hide();
+  },
+
+  checkGroupName: function () {
+    var component = this.get('selectedSlaveComponent');
+    component.newGroupIndex++;
+    var newGroupName = 'New Group ' + component.newGroupIndex;
+    var groups = component.groups.filterProperty('name', newGroupName);
+    if (groups.length !== 0) {
+      this.checkGroupName();
+    }
+  },
+
+  /**
+   * Onclick handler for <code>choose hosts for slave group</code> link
+   * @param event
+   */
+  showEditSlaveComponentGroups: function (event) {
+    this.showAddSlaveComponentGroup(event);
+  },
+
+  getHostsByGroup: function (group) {
+    var hosts = this.get('hosts');
+    if(hosts){
+      return hosts.filterProperty('group', group.name);
+    }
+  },
+
+  /**
+   * Change tab
+   * @param event
+   */
+  showSlaveComponentGroup: function (event) {
+    var component = this.get('selectedSlaveComponent');
+    if(!component.groups){
+      debugger;
+    }
+    component.groups.setEach('active', false);
+    var group = component.groups.filterProperty('name', event.context.name);
+    group.setEach('active', true);
+    var assignedHosts = component.hosts.filterProperty('group', event.context.name);
+    if (assignedHosts.length === 0) {
+      $('.remove-group-error').hide();
+    }
+  },
+
+  /**
+   * Remove tab
+   * @param event
+   */
+  removeSlaveComponentGroup: function (event) {
+    var group = event.context;
+    var component = this.get('selectedSlaveComponent');
+    var assignedHosts = component.hosts.filterProperty('group', group.name);
+    if (assignedHosts.length !== 0) {
+      $('.remove-group-error').show();
+    } else {
+      $('.remove-group-error').hide();
+      var key = component.groups.indexOf(group);
+      component.groups.removeObject(component.groups[key]);
+
+      var newGroups = component.groups.filterProperty('type', 'new');
+      if (newGroups.length == 0)
+        component.newGroupIndex = 0;
+      else {
+        var lastNewGroup = newGroups[newGroups.length - 1];
+        component.newGroupIndex = lastNewGroup.index;
+      }
+      if (group.active) {
+        var lastGroup;
+        if (key === component.groups.length)
+          lastGroup = component.groups.slice(key - 1, key);
+        else lastGroup = component.groups.slice(key, key + 1);
+        lastGroup.setEach('active', true);
+      }
+    }
+  },
+
+  /**
+   * change group name of slave component
+   * @param group
+   * @param newGroupName
+   * @return {Boolean}
+   */
+  changeSlaveGroupName: function (group, newGroupName) {
+    var component = this.get('selectedSlaveComponent');
+    var isExist = component.groups.filterProperty('name', newGroupName);
+    if (isExist.length !== 0)
+      return true;
+    else {
+      var assignedHosts = component.hosts.filterProperty('group', group.name);
+      if (assignedHosts.length !== 0){
+        assignedHosts.setEach('group', newGroupName);
+      }
+      var groupFilter = component.groups.filterProperty('name', group.name);
+      groupFilter.setEach('name', newGroupName);
+    }
+    return false;
+  }
+
+});

+ 66 - 0
ambari-web/app/controllers/wizard/step1_controller.js

@@ -0,0 +1,66 @@
+/**
+ * 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.WizardStep1Controller = Em.Controller.extend({
+  name: 'wizardStep1Controller',
+
+  clusterName: function(){
+    return this.get('content.cluster.name');
+  }.property('content.cluster.name'),
+
+  hasSubmitted : false,
+
+  invalidClusterName : function(){
+    if(!this.get('hasSubmitted')){
+      return false;
+    }
+
+    var clusterName = this.get('content.cluster.name');
+    if (clusterName == '') {
+      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.required'));
+      return true;
+    } else if (/\s/.test(clusterName)) {
+      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.whitespaces'));
+      return true;
+    } else if (/[^\w\s]/gi.test(clusterName)) {
+      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.specialChar'));
+      return true;
+    } else {
+      this.set('clusterNameError', '');
+      return false;
+    }
+  }.property('hasSubmitted', 'content.cluster.name').cacheable(),
+
+  /**
+   * calculates by <code>invalidClusterName</code> property
+   */
+  clusterNameError: '',
+
+  /**
+   * Onclick handler for <code>next</code> button
+   */
+  submit: function () {
+    this.set('hasSubmitted', true);
+    if (!this.get('invalidClusterName')) {
+      App.router.send('next');
+    }
+  }
+
+});

+ 144 - 0
ambari-web/app/controllers/wizard/step7_controller.js

@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+/**
+ * By Step 7, we have the following information stored in App.db and set on this
+ * controller by the router.
+ *
+ *   selectedServices: App.db.selectedServices (the services that the user selected in Step 4)
+ *   masterComponentHosts: App.db.masterComponentHosts (master-components-to-hosts mapping the user selected in Step 5)
+ *   slaveComponentHosts: App.db.slaveComponentHosts (slave-components-to-hosts mapping the user selected in Step 6)
+ *
+ */
+
+App.WizardStep7Controller = Em.Controller.extend({
+
+  name: 'wizardStep7Controller',
+
+  stepConfigs: [], //contains all field properties that are viewed in this step
+
+  selectedService: null,
+
+  slaveHostToGroup: null,
+
+  isSubmitDisabled: function () {
+    return !this.stepConfigs.everyProperty('errorCount', 0);
+  }.property('stepConfigs.@each.errorCount'),
+
+  selectedServiceNames : function(){
+    return this.get('content.services').filterProperty('isSelected', true).mapProperty('serviceName');
+  }.property('content.services').cacheable(),
+
+  masterComponentHosts : function(){
+    return this.get('content.masterComponentHosts');
+  }.property('content.masterComponentHosts'),
+
+  slaveComponentHosts : function(){
+    return this.get('content.slaveComponentHosts');
+  }.property('content.slaveComponentHosts'),
+
+  serviceConfigs: require('data/service_configs'),
+
+  clearStep: function () {
+    this.get('stepConfigs').clear();
+  },
+
+  /**
+   * On load function
+   */
+  loadStep: function () {
+    console.log("TRACE: Loading step7: Configure Services");
+
+    this.clearStep();
+    this.renderServiceConfigs(this.serviceConfigs);
+
+    var storedServices = this.get('content.serviceConfigProperties');
+    if (storedServices) {
+      var configs = new Ember.Set();
+
+      // for all services`
+      this.get('stepConfigs').forEach(function (_content) {
+        //for all components
+        _content.get('configs').forEach(function (_config) {
+
+          var componentVal = storedServices.findProperty('name', _config.get('name'));
+          //if we have config for specified component
+          if(componentVal){
+
+            //set it
+            _config.set('value', componentVal.value)
+          }
+
+        }, this);
+      }, this);
+
+    }
+  },
+
+  /**
+   * Render configs for active services
+   * @param serviceConfigs
+   */
+  renderServiceConfigs: function (serviceConfigs) {
+    serviceConfigs.forEach(function (_serviceConfig) {
+      var serviceConfig = App.ServiceConfig.create({
+        serviceName: _serviceConfig.serviceName,
+        displayName: _serviceConfig.displayName,
+        configCategories: _serviceConfig.configCategories,
+        configs: []
+      });
+
+      if (this.get('selectedServiceNames').contains(serviceConfig.serviceName) || serviceConfig.serviceName === 'MISC') {
+        this.loadComponentConfigs(_serviceConfig, serviceConfig);
+
+        console.log('pushing ' + serviceConfig.serviceName);
+        this.get('stepConfigs').pushObject(serviceConfig);
+
+      } else {
+        console.log('skipping ' + serviceConfig.serviceName);
+      }
+    }, this);
+
+    this.set('selectedService', this.get('stepConfigs').objectAt(0));
+  },
+
+  /**
+   * Load child components to service config object
+   * @param _componentConfig
+   * @param componentConfig
+   */
+  loadComponentConfigs: function (_componentConfig, componentConfig) {
+    _componentConfig.configs.forEach(function (_serviceConfigProperty) {
+      var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
+      serviceConfigProperty.serviceConfig = componentConfig;
+      serviceConfigProperty.initialValue();
+      componentConfig.configs.pushObject(serviceConfigProperty);
+      serviceConfigProperty.validate();
+    }, this);
+  },
+
+
+  submit: function () {
+    if (!this.get('isSubmitDisabled')) {
+      App.router.send('next');
+    }
+  }
+
+});

+ 122 - 95
ambari-web/app/controllers/installer/step8_controller.js → ambari-web/app/controllers/wizard/step8_controller.js

@@ -18,70 +18,82 @@
 
 var App = require('app');
 
-App.InstallerStep8Controller = Em.ArrayController.extend({
-	name: 'installerStep8Controller',
+App.WizardStep8Controller = Em.Controller.extend({
+	name: 'wizardStep8Controller',
 	rawContent: require('data/review_configs'),
-	content: [],
+
+  clusterInfo : [],
 	services: [],
 
+  selectedServices : function(){
+    return this.get('content.services').filterProperty('isSelected', true);
+  }.property('content.services').cacheable(),
+
 	clearStep: function () {
-		this.clear();
 		this.get('services').clear();
+    this.get('clusterInfo').clear();
 	},
 
 	loadStep: function () {
 		console.log("TRACE: Loading step8: Review Page");
 		this.clearStep();
-		var configObj = new Ember.Set();
-		this.loadClusterName();
-		this.loadHosts();
-		this.loadRepo();
+    this.loadClusterInfo();
 		this.loadServices();
 	},
 
-	loadClusterName: function () {
-		var obj = {};
-		var cluster = this.rawContent.findProperty('config_name', 'cluster');
-		cluster.config_value = App.db.getClusterName();
-		this.pushObject(Ember.Object.create(cluster));
-	},
+  /**
+   * Load all info about cluster to <code>clusterInfo</code> variable
+   */
+  loadClusterInfo : function(){
+
+    // cluster name
+    var cluster = this.rawContent.findProperty('config_name', 'cluster');
+    cluster.config_value = this.get('content.cluster.name');
+    this.get('clusterInfo').pushObject(Ember.Object.create(cluster));
+
+    //hosts
+    var masterHosts = this.get('content.masterComponentHosts').mapProperty('hostName').uniq();
+    var slaveHosts = this.get('content.slaveComponentHosts');
+
+    var hostObj = [];
+    slaveHosts.forEach(function (_hosts) {
+      hostObj = hostObj.concat(_hosts.hosts);
+    }, this);
+
+    slaveHosts = hostObj.mapProperty('hostname').uniq();
+
+    var totalHosts = masterHosts.concat(slaveHosts).uniq().length;
+
+    var totalHostsObj = this.rawContent.findProperty('config_name', 'hosts');
+    totalHostsObj.config_value = totalHosts;
+    this.get('clusterInfo').pushObject(Ember.Object.create(totalHostsObj));
+
+    //repo
+    var repoOption = this.get('content.hosts.localRepo');
+    var repoObj = this.rawContent.findProperty('config_name', 'Repo');
+    if (repoOption) {
+      repoObj.config_value = 'Yes';
+    } else {
+      repoObj.config_value = 'No';
+    }
+    this.get('clusterInfo').pushObject(Ember.Object.create(repoObj));
+
+  },
 
-	loadHosts: function () {
-		var masterHosts = App.db.getMasterComponentHosts().mapProperty('hostName').uniq();
-		var slaveHosts = App.db.getSlaveComponentHosts();
-		var hostObj = [];
-		slaveHosts.forEach(function (_hosts) {
-			hostObj = hostObj.concat(_hosts.hosts);
-		}, this);
-		slaveHosts = hostObj.mapProperty('hostname').uniq();
-		console.log('The value of slaveHosts is: ' + slaveHosts);
-		var totalHosts = masterHosts.concat(slaveHosts).uniq().length;
-		var totalHostsObj = this.rawContent.findProperty('config_name', 'hosts');
-		totalHostsObj.config_value = totalHosts;
-		this.pushObject(Ember.Object.create(totalHostsObj));
-	},
-
-	loadRepo: function () {
-		var repoOption = App.db.getSoftRepo().repoType;
-		var repoObj = this.rawContent.findProperty('config_name', 'Repo');
-		if (repoOption === 'local') {
-			repoObj.config_value = 'Yes';
-		} else {
-			repoObj.config_value = 'No';
-		}
-		this.pushObject(Ember.Object.create(repoObj));
-	},
 
+  /**
+   * Load all info about services to <code>services</code> variable
+   */
 	loadServices: function () {
-		this.set('services', App.db.getSelectedServiceNames());
-		var services = App.db.getService().filterProperty('isSelected', true);
-		services.forEach(function (_service) {
+    var selectedServices = this.get('selectedServices');
+		this.set('services', selectedServices.mapProperty('serviceName'));
+
+    selectedServices.forEach(function (_service) {
 			console.log('INFO: step8: Name of the service from getService function: ' + _service.serviceName);
-			var serviceObj = {};
-			//var tempObj = {};
 			var reviewService = this.rawContent.findProperty('config_name', 'services');
-			serviceObj = reviewService.config_value.findProperty('service_name', _service.serviceName);
-			if (serviceObj !== undefined) {
+			var serviceObj = reviewService.config_value.findProperty('service_name', _service.serviceName);
+
+			if (serviceObj) {
 				switch (serviceObj.service_name) {
 					case 'HDFS':
 						this.loadHDFS(serviceObj);
@@ -117,6 +129,10 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 		}, this);
 	},
 
+  /**
+   * load all info about HDFS service
+   * @param hdfsObj
+   */
 	loadHDFS: function (hdfsObj) {
 		hdfsObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
@@ -133,21 +149,21 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 			}
 		}, this);
 		//var
-		this.services.pushObject(hdfsObj);
+		this.get('services').pushObject(hdfsObj);
 	},
 
 	loadNnValue: function (nnComponent) {
-		var nnHostName = App.db.getMasterComponentHosts().findProperty('component', nnComponent.display_name);
+		var nnHostName = this.get('content.masterComponentHosts').findProperty('component', nnComponent.display_name);
 		nnComponent.set('component_value', nnHostName.hostName);
 	},
 
 	loadSnnValue: function (snnComponent) {
-		var snnHostName = App.db.getMasterComponentHosts().findProperty('component', 'SNameNode');
+		var snnHostName = this.get('content.masterComponentHosts').findProperty('component', 'SNameNode');
 		snnComponent.set('component_value', snnHostName.hostName);
 	},
 
 	loadDnValue: function (dnComponent) {
-		var dnHosts = App.db.getSlaveComponentHosts().findProperty('displayName', 'DataNode');
+		var dnHosts = this.get('content.slaveComponentHosts').findProperty('displayName', 'DataNode');
 		var totalDnHosts = dnHosts.hosts.length;
 		var dnHostGroups = [];
 		dnHosts.hosts.forEach(function (_dnHost) {
@@ -164,6 +180,10 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 		dnComponent.set('component_value', totalDnHosts + ' hosts ' + '(' + totalGroups + ' ' + groupLabel + ')');
 	},
 
+  /**
+   * Load all info about mapReduce service
+   * @param mrObj
+   */
 	loadMapReduce: function (mrObj) {
 		mrObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
@@ -180,12 +200,12 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadJtValue: function (jtComponent) {
-		var jtHostName = App.db.getMasterComponentHosts().findProperty('component', jtComponent.display_name);
+		var jtHostName = this.get('content.masterComponentHosts').findProperty('component', jtComponent.display_name);
 		jtComponent.set('component_value', jtHostName.hostName);
 	},
 
 	loadTtValue: function (ttComponent) {
-		var ttHosts = App.db.getSlaveComponentHosts().findProperty('displayName', 'TaskTracker');
+		var ttHosts = this.get('content.slaveComponentHosts').findProperty('displayName', 'TaskTracker');
 		var totalTtHosts = ttHosts.hosts.length;
 		var ttHostGroups = [];
 		ttHosts.hosts.forEach(function (_ttHost) {
@@ -201,6 +221,10 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 		ttComponent.set('component_value', totalTtHosts + ' hosts ' + '(' + totalGroups + ' ' + groupLabel + ')');
 	},
 
+  /**
+   * Load all info about Hive service
+   * @param hiveObj
+   */
 	loadHive: function (hiveObj) {
 		hiveObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
@@ -218,20 +242,18 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadHiveMetaStoreValue: function (metaStoreComponent) {
-		var hiveHostName = App.db.getMasterComponentHosts().findProperty('component', 'Hive Metastore');
+		var hiveHostName = this.get('content.masterComponentHosts').findProperty('component', 'Hive Metastore');
 		metaStoreComponent.set('component_value', hiveHostName.hostName);
 	},
 
 	loadHiveDbValue: function (dbComponent) {
-    var hiveDb = App.db.getServiceConfigProperties().findProperty('name', 'hive_database');
-    if (hiveDb.value === 'New PostgreSQL Database') {
-      dbComponent.set('component_value', 'PostgreSQL (New Database)');
-    } else {
-      var db =    App.db.getServiceConfigProperties().findProperty('name', 'hive_existing_database');
-      dbComponent.set('component_value', db.value +' (' + hiveDb.value + ')');
-    }
+		dbComponent.set('component_value', 'MySQL');
 	},
 
+  /**
+   * Load all info about Hbase
+   * @param hbaseObj
+   */
 	loadHbase: function (hbaseObj) {
 		hbaseObj.service_components.forEach(function (_component) {
 			switch (_component.display_name) {
@@ -248,12 +270,12 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadMasterValue: function (hbaseMaster) {
-		var hbaseHostName = App.db.getMasterComponentHosts().findProperty('component', 'HBase Master');
+		var hbaseHostName = this.get('content.masterComponentHosts').findProperty('component', 'HBase Master');
 		hbaseMaster.set('component_value', hbaseHostName.hostName);
 	},
 
 	loadRegionServerValue: function (rsComponent) {
-		var rsHosts = App.db.getSlaveComponentHosts().findProperty('displayName', 'RegionServer');
+		var rsHosts = this.get('content.slaveComponentHosts').findProperty('displayName', 'RegionServer');
 		var totalRsHosts = rsHosts.hosts.length;
 		var rsHostGroups = [];
 		rsHosts.hosts.forEach(function (_ttHost) {
@@ -269,6 +291,10 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 		rsComponent.set('component_value', totalRsHosts + ' hosts '  + '(' + totalGroups + ' ' + groupLabel + ')');
 	},
 
+  /**
+   * Load all info about ZooKeeper service
+   * @param zkObj
+   */
 	loadZk: function (zkObj) {
 		zkObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
@@ -282,7 +308,7 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadZkServerValue: function (serverComponent) {
-		var zkHostNames = App.db.getMasterComponentHosts().filterProperty('component', 'ZooKeeper').length;
+		var zkHostNames = this.get('content.masterComponentHosts').filterProperty('component', 'ZooKeeper').length;
 		var hostSuffix;
 		if (zkHostNames === 1) {
 			hostSuffix = 'host';
@@ -292,15 +318,16 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 		serverComponent.set('component_value', zkHostNames + ' ' + hostSuffix);
 	},
 
+  /**
+   * Load all info about Oozie services
+   * @param oozieObj
+   */
 	loadOozie: function (oozieObj) {
 		oozieObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
 				case 'Server':
 					this.loadOozieServerValue(_component);
 					break;
-        case 'Database':
-          this.loadOozieDbValue(_component);
-          break;
 				default:
 			}
 		}, this);
@@ -308,20 +335,14 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadOozieServerValue: function (oozieServer) {
-		var oozieServerName = App.db.getMasterComponentHosts().findProperty('component', 'Oozie Server');
+		var oozieServerName = this.get('content.masterComponentHosts').findProperty('component', 'Oozie Server');
 		oozieServer.set('component_value', oozieServerName.hostName);
 	},
 
-  loadOozieDbValue: function(dbComponent) {
-    var oozieDb = App.db.getServiceConfigProperties().findProperty('name', 'oozie_database');
-    if (oozieDb.value === 'New PostgreSQL Database') {
-      dbComponent.set('component_value', 'PostgreSQL (New Database)');
-    } else {
-      var db = App.db.getServiceConfigProperties().findProperty('name', 'oozie_existing_database');
-      dbComponent.set('component_value', db.value +' (' + oozieDb.value + ')');
-    }
-  },
-
+  /**
+   * Load all info about Nagios service
+   * @param nagiosObj
+   */
 	loadNagios: function (nagiosObj) {
 		nagiosObj.service_components.forEach(function (_component) {
 			switch (_component.display_name) {
@@ -338,16 +359,21 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadNagiosServerValue: function (nagiosServer) {
-		var nagiosServerName = App.db.getMasterComponentHosts().findProperty('component', 'Nagios Server');
+		var nagiosServerName = this.get('content.masterComponentHosts').findProperty('component', 'Nagios Server');
 		nagiosServer.set('component_value', nagiosServerName.hostName);
 	},
 
 	loadNagiosAdminValue: function (nagiosAdmin) {
-		var adminLoginName = App.db.getServiceConfigProperties().findProperty('name', 'nagios_web_login');
-		var adminEmail = App.db.getServiceConfigProperties().findProperty('name', 'nagios_contact');
+    var config = this.get('content.serviceConfigProperties');
+		var adminLoginName = config.findProperty('name', 'nagios_web_login');
+		var adminEmail = config.findProperty('name', 'nagios_contact');
 		nagiosAdmin.set('component_value', adminLoginName.value + ' / (' + adminEmail.value +')');
 	},
 
+  /**
+   * Load all info about ganglia
+   * @param gangliaObj
+   */
 	loadGanglia: function (gangliaObj) {
 		gangliaObj.get('service_components').forEach(function (_component) {
 			switch (_component.get('display_name')) {
@@ -361,17 +387,20 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	loadGangliaServerValue: function (gangliaServer) {
-		var gangliaServerName = App.db.getMasterComponentHosts().findProperty('component', 'Ganglia Collector');
+		var gangliaServerName = this.get('content.masterComponentHosts').findProperty('component', 'Ganglia Collector');
 		gangliaServer.set('component_value', gangliaServerName.hostName);
 	},
 
+
+  /**
+   * Onclick handler for <code>next</code> button
+   */
 	submit: function () {
 		this.createCluster();
 		this.createSelectedServices();
 		this.createComponents();
 		this.createHostComponents();
 		App.router.send('next');
-
 	},
 
 
@@ -379,7 +408,7 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 
 	createCluster: function () {
 		var self = this;
-		var clusterName = this.findProperty('config_name', 'cluster').config_value;
+		var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
 		var url = '/api/clusters/' + clusterName;
 		$.ajax({
 			type: 'PUT',
@@ -406,15 +435,14 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	createSelectedServices: function () {
-		var services = App.db.getSelectedServiceNames();
+    var services = this.get('selectedServices').mapProperty('serviceName');
 		services.forEach(function (_service) {
 			this.createService(_service);
 		}, this);
 	},
 
 	createService: function (service) {
-		var self = this;
-		var clusterName = this.findProperty('config_name', 'cluster').config_value;
+		var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
 		var url = '/api/clusters/' + clusterName + '/services/' + service;
 		$.ajax({
 			type: 'PUT',
@@ -441,7 +469,7 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 
 	createComponents: function () {
 		var serviceComponents = require('data/service_components');
-		var services = App.db.getSelectedServiceNames();
+		var services = this.get('selectedServices').mapProperty('serviceName');
 		services.forEach(function (_service) {
 			var components = serviceComponents.filterProperty('service_name', _service);
 			components.forEach(function (_component) {
@@ -453,8 +481,7 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	createComponent: function (service, component) {
-		var self = this;
-		var clusterName = this.findProperty('config_name', 'cluster').config_value;
+		var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
 		var url = '/api/clusters/' + clusterName + '/services/' + service + '/components/' + component;
 		$.ajax({
 			type: 'PUT',
@@ -479,8 +506,9 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	createHostComponents: function () {
-		var masterHosts = App.db.getMasterComponentHosts();
-		var slaveHosts = App.db.getSlaveComponentHosts();
+    var masterHosts = this.get('content.masterComponentHosts');
+    var slaveHosts = this.get('content.slaveComponentHosts');
+
 		masterHosts.forEach(function (_masterHost) {
 			this.createHostComponent(_masterHost);
 		}, this);
@@ -495,8 +523,7 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 	},
 
 	createHostComponent: function (hostComponent) {
-		var self = this;
-		var clusterName = this.findProperty('config_name', 'cluster').config_value;
+		var clusterName = this.get('clusterInfo').findProperty('config_name', 'cluster').config_value;
 		var url = '/api/clusters/' + clusterName + '/hosts/' + hostComponent.hostName + '/host_components/' + hostComponent.component;
 
 		$.ajax({
@@ -526,5 +553,5 @@ App.InstallerStep8Controller = Em.ArrayController.extend({
 
 
 
-
-
+  
+  

+ 71 - 89
ambari-web/app/controllers/installer/step9_controller.js → ambari-web/app/controllers/wizard/step9_controller.js

@@ -16,37 +16,36 @@
  * limitations under the License.
  */
 var App = require('app');
-
-App.InstallerStep9Controller = Em.ArrayController.extend({
-  name: 'installerStep9Controller',
-  content: [],
-  progress: '0',
-  isStepCompleted: false,
-  isSubmitDisabled: function () {
+App.WizardStep9Controller = Em.Controller.extend({
+  name:'wizardStep9Controller',
+  hosts:[],
+  progress:'0',
+  isStepCompleted:false,
+  isSubmitDisabled:function () {
     return !this.get('isStepCompleted');
   }.property('isStepCompleted'),
 
-  mockHostData: require('data/mock/step9_hosts'),
-  pollData_1: require('data/mock/step9_pollData_1'),
-  pollData_2: require('data/mock/step9_pollData_2'),
-  pollDataCounter: 0,
+  mockHostData:require('data/mock/step9_hosts'),
+  pollData_1:require('data/mock/step9_pollData_1'),
+  pollData_2:require('data/mock/step9_pollData_2'),
+  pollDataCounter:0,
 
-  status: function () {
-    if (this.everyProperty('status', 'success')) {
+  status:function () {
+    if (this.hosts.everyProperty('status', 'success')) {
       return 'success';
-    } else if (this.someProperty('status', 'failed')) {
+    } else if (this.hosts.someProperty('status', 'failed')) {
       return 'failed';
-    } else if (this.someProperty('status', 'warning')) {
+    } else if (this.hosts.someProperty('status', 'warning')) {
       return 'warning';
     } else {
       return 'info';
     }
-  }.property('@each.status'),
+  }.property('hosts.@each.status'),
 
-  navigateStep: function () {
+  navigateStep:function () {
     this.loadStep();
     //TODO: uncomment following line after the hook up with the API call
-    if (App.db.getClusterStatus().isCompleted === false) {
+    if (this.get('content.cluster.isCompleted') === false) {
       //this.startPolling();
     } else {
       this.set('isStepCompleted', true);
@@ -54,22 +53,22 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
     }
   },
 
-  clearStep: function () {
-    this.clear();
+  clearStep:function () {
+    this.hosts.clear();
     this.set('status', 'info');
     this.set('progress', '0');
     this.set('isStepCompleted', false);
   },
 
-  loadStep: function () {
+  loadStep:function () {
     console.log("TRACE: Loading step9: Install, Start and Test");
     this.clearStep();
     this.renderHosts(this.loadHosts());
   },
 
-  loadHosts: function () {
+  loadHosts:function () {
     var hostInfo = [];
-    hostInfo = App.db.getHosts();
+    hostInfo = this.get('content.hostsInfo');
     var hosts = new Ember.Set();
     for (var index in hostInfo) {
       hosts.add(hostInfo[index]);
@@ -78,58 +77,58 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
     return hosts.filterProperty('bootStatus', 'success');
   },
 
-  renderHosts: function (hostsInfo) {
+  renderHosts:function (hostsInfo) {
     var self = this;
     hostsInfo.forEach(function (_hostInfo) {
       var hostInfo = App.HostInfo.create({
-        name: _hostInfo.name,
-        status: _hostInfo.status,
-        message: _hostInfo.message,
-        progress: _hostInfo.progress
+        name:_hostInfo.name,
+        status:_hostInfo.status,
+        message:_hostInfo.message,
+        progress:_hostInfo.progress
       });
 
       console.log('pushing ' + hostInfo.name);
-      self.content.pushObject(hostInfo);
+      self.hosts.pushObject(hostInfo);
     });
   },
 
-  onSuccessPerHost: function (actions, contentHost) {
+  onSuccessPerHost:function (actions, contentHost) {
     if (actions.everyProperty('status', 'completed')) {
       contentHost.set('status', 'success');
     }
   },
 
-  onWarningPerHost: function (actions, contentHost) {
+  onWarningPerHost:function (actions, contentHost) {
     if (actions.findProperty('status', 'failed') || actions.findProperty('status', 'aborted')) {
       contentHost.set('status', 'warning');
       this.set('status', 'warning');
     }
   },
 
-  onInProgressPerHost: function (actions, contentHost) {
+  onInProgressPerHost:function (actions, contentHost) {
     var runningAction = actions.findProperty('status', 'inprogress');
     if (runningAction !== null && runningAction !== undefined) {
       contentHost.set('message', runningAction.message);
     }
   },
 
-  progressPerHost: function (actions, contentHost) {
+  progressPerHost:function (actions, contentHost) {
     var totalProgress = 0;
     var actionsPerHost = actions.length;
     var completedActions = actions.filterProperty('status', 'completed').length
-      + actions.filterProperty('status', 'failed').length +
-      actions.filterProperty('status', 'aborted').length;
+        + actions.filterProperty('status', 'failed').length +
+        actions.filterProperty('status', 'aborted').length;
     var progress = Math.floor((completedActions / actionsPerHost) * 100);
     console.log('INFO: progressPerHost is: ' + progress);
     contentHost.set('progress', progress.toString());
     return progress;
   },
 
-  isSuccess: function (polledData) {
+  isSuccess:function (polledData) {
     return polledData.everyProperty('status', 'success');
   },
 
-  isStepFailed: function (polledData) {
+  isStepFailed:function (polledData) {
     var self = this;
     var result = false;
     polledData.forEach(function (_polledData) {
@@ -145,7 +144,7 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
     return result;
   },
 
-  getFailedHostsForFailedRoles: function (polledData) {
+  getFailedHostsForFailedRoles:function (polledData) {
     var hostArr = new Ember.Set();
     polledData.forEach(function (_polledData) {
       var successFactor = _polledData.sf;
@@ -164,17 +163,17 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
     return hostArr;
   },
 
-  setHostsStatus: function (hosts, status) {
+  setHostsStatus:function (hosts, status) {
     var self = this;
     hosts.forEach(function (_host) {
-      var host = self.findProperty('name', _host);
+      var host = self.hosts.findProperty('name', _host);
       host.set('status', status);
     });
   },
 
   // polling from ui stops only when no action has 'pending', 'queued' or 'inprogress' status
 
-  finishStep: function (polledData) {
+  finishStep:function (polledData) {
     var self = this;
     if (!polledData.someProperty('status', 'pending') && !polledData.someProperty('status', 'queued') && !polledData.someProperty('status', 'inprogress')) {
       this.set('progress', '100');
@@ -190,13 +189,12 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
     }
   },
 
-
-  parseHostInfo: function (polledData) {
+  parseHostInfo:function (polledData) {
     console.log('TRACE: Entering host info function');
     var self = this;
     var result = false;
     var totalProgress = 0;
-    this.forEach(function (_content) {
+    this.hosts.forEach(function (_content) {
       var actions = polledData.filterProperty('name', _content.name);
       if (actions.length === 0) {
         alert('For testing with mockData follow the sequence: hit referesh,"mockData btn", "pollData btn", again "pollData btn"');
@@ -209,7 +207,7 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
         totalProgress = totalProgress + self.progressPerHost(actions, _content);
       }
     }, this);
-    totalProgress = Math.floor(totalProgress / this.content.length);
+    totalProgress = Math.floor(totalProgress / this.hosts.length);
     this.set('progress', totalProgress.toString());
     console.log("INFO: right now the progress is: " + this.get('progress'));
     this.finishStep(polledData);
@@ -217,28 +215,28 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
   },
 
 
-  retry: function () {
+  retry:function () {
     if (this.get('isSubmitDisabled')) {
       return;
     }
-    this.clear();
+    this.hosts.clear();
     this.renderHosts(this.loadHosts());
     //this.startPolling();
   },
 
-  startPolling: function () {
+  startPolling:function () {
     this.set('isSubmitDisabled', true);
     this.doPolling();
   },
 
-  doPolling: function () {
+  doPolling:function () {
     var self = this;
     $.ajax({
-      type: 'GET',
-      url: '/ambari_server/api/polling',
-      async: false,
-      timeout: 5000,
-      success: function (data) {
+      type:'GET',
+      url:'/ambari_server/api/polling',
+      async:false,
+      timeout:5000,
+      success:function (data) {
         console.log("TRACE: In success function for the GET bootstrap call");
         var result = self.parseHostInfo(data);
         if (result !== true) {
@@ -248,76 +246,60 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
         }
       },
 
-      error: function () {
+      error:function () {
         console.log("ERROR");
         self.stopPolling();
       },
 
-      statusCode: {
-        404: function () {
+      statusCode:{
+        404:function () {
           console.log("URI not found.");
         }
       },
 
-      dataType: 'application/json'
+      dataType:'application/json'
     });
 
   },
 
-  stopPolling: function () {
+  stopPolling:function () {
     //TODO: uncomment following line after the hook up with the API call
     // this.set('isStepCompleted',true);
   },
 
-  saveHostInfoToDb: function () {
-    var hostInfo = App.db.getHosts();
-    for (var index in hostInfo) {
-      hostInfo[index].status = "pending";
-      if (this.someProperty('name', hostInfo[index].name)) {
-        var host = this.findProperty('name', hostInfo[index].name);
-        hostInfo[index].status = host.status;
-        hostInfo[index].message = host.message;
-        hostInfo[index].progress = host.progress;
-      }
-      console.log("TRACE: host name is: " + hostInfo[index].name);
-    }
-    App.db.setHosts(hostInfo);
-  },
-
-
-  submit: function () {
+  submit:function () {
     if (!this.get('isSubmitDisabled')) {
-      this.saveHostInfoToDb();
-      App.db.setClusterStatus({status: this.get('status'), isCompleted: true});
-      App.get('router').transitionTo('step10');
+      this.set('content.cluster.status', this.get('status'));
+      this.set('content.cluster.isCompleted', true);
+      App.router.send('next');
     }
   },
 
-  back: function () {
+  back:function () {
     if (!this.get('isSubmitDisabled')) {
       App.router.send('back');
     }
   },
 
-  hostLogPopup: function (event) {
+  hostLogPopup:function (event) {
     App.ModalPopup.show({
-      header: Em.I18n.t('installer.step3.hostLog.popup.header'),
-      onPrimary: function () {
+      header:Em.I18n.t('installer.step3.hostLog.popup.header'),
+      onPrimary:function () {
         this.hide();
       },
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/installer/step3HostLogPopup')
+      bodyClass:Ember.View.extend({
+        templateName:require('templates/installer/step3HostLogPopup')
       })
     });
   },
-  mockBtn: function () {
+  mockBtn:function () {
     this.set('isSubmitDisabled', false);
-    this.clear();
+    this.hosts.clear();
     var hostInfo = this.mockHostData;
     this.renderHosts(hostInfo);
 
   },
-  pollBtn: function () {
+  pollBtn:function () {
     this.set('isSubmitDisabled', false);
     var data1 = this.pollData_1;
     var data2 = this.pollData_2;
@@ -335,4 +317,4 @@ App.InstallerStep9Controller = Em.ArrayController.extend({
 
   }
 
-});
+});

+ 3 - 0
ambari-web/app/messages.js

@@ -235,6 +235,9 @@ Em.I18n.translations = {
   'hosts.decommission.popup.header': 'Confirmation',
   'hosts.delete.popup.body': 'Are you sure?',
   'hosts.delete.popup.header': 'Confirmation',
+  'hosts.cant.do.popup.header': 'Operation not allowed',
+  'hosts.cant.do.popup.masterList.body': 'You cannot delete this host because it is hosting following master services: {0}.',
+  'hosts.cant.do.popup.workingList.body': 'You cannot delete this host because following slave services are not fully stopped or decommissioned: {0}.',
 
   'charts.horizon.chart.showText': 'show',
   'charts.horizon.chart.hideText': 'hide',

+ 3 - 3
ambari-web/app/models/host.js

@@ -63,7 +63,7 @@ App.Host.FIXTURES = [
     disk_usage: '20',
     load_avg: '0.2, 1.2, 2.4',
     ip: '255.255.255.255',
-    health_status: 'DEAD',
+    health_status: 'DEAD-ORANGE',
     cpu_usage: 36,
     memory_usage: 29,
     network_usage: 56,
@@ -74,7 +74,7 @@ App.Host.FIXTURES = [
     host_name: 'n_host3',
     cluster_id: 2,
     components:[4, 5, 7],
-    health_status: 'LIVE',
+    health_status: 'DEAD-YELLOW',
     cpu_usage: 23,
     memory_usage: 16,
     network_usage: 16,
@@ -111,7 +111,7 @@ App.Host.FIXTURES = [
     id: 6,
     host_name: 'a_host6',
     cluster_id: 1,
-    components:[4, 5],
+    components:[5],
     cpu: '2x2.5GHz',
     memory: '8GB',
     disk_usage: '20',

+ 114 - 107
ambari-web/app/models/service.js

@@ -20,18 +20,17 @@
 var App = require('app');
 
 App.ServiceInfo = Ember.Object.extend({
-  elementId: 'service',
-  serviceName: '',
-  displayName: '',
-  isMaster: '',
-  isClient: '',
-  isDisabled: '',
-  isHidden: '',
-  isSelected: 'true',
-  description: ''
+  elementId:'service',
+  serviceName:'',
+  displayName:'',
+  isMaster:'',
+  isClient:'',
+  isDisabled:'',
+  isHidden:'',
+  isSelected:'true',
+  description:''
 });
 
-
 /*App.User = Em.Object.extend({
  username: null
  });*/
@@ -56,21 +55,29 @@ App.ServiceModel = Em.Object.extend({
  });
  */
 
+
 App.Component = DS.Model.extend({
   componentName:DS.attr('string'),
   label:DS.attr('string'),
   type:DS.attr('boolean'),
   service:DS.belongsTo('App.Service'),
   host:DS.belongsTo('App.Host'),
-  workStatus: DS.attr('boolean')
+  workStatus:DS.attr('boolean'),
+  isMaster: function(){
+    return this.get('type');
+  }.property('type'),
+  isSlave: function(){
+    return !this.get('type');
+  }.property('type'),
+  // checkedForHostFilter: true // this is for host page to set checkboxes checked
 });
 
 App.Component.FIXTURES = [
   {
     id:1,
     component_name:'NameNode',
-    label: 'NN',
-    type: true,
+    label:'NN',
+    type:true,
     service_id:1,
     host_id:1,
     work_status:false
@@ -78,8 +85,8 @@ App.Component.FIXTURES = [
   {
     id:2,
     component_name:'SNameNode',
-    label: 'SNN',
-    type: true,
+    label:'SNN',
+    type:true,
     service_id:1,
     host_id:2,
     work_status:true
@@ -87,17 +94,17 @@ App.Component.FIXTURES = [
   {
     id:3,
     component_name:'DataNode',
-    label: 'DN',
+    label:'DN',
     service_id:1,
-    type: false,
+    type:false,
     host_id:2,
     work_status:true
   },
   {
     id:4,
     component_name:'JobTracker',
-    label: 'JT',
-    type: true,
+    label:'JT',
+    type:true,
     service_id:2,
     host_id:4,
     work_status:true
@@ -105,8 +112,8 @@ App.Component.FIXTURES = [
   {
     id:5,
     component_name:'TaskTracker',
-    label: 'TT',
-    type: false,
+    label:'TT',
+    type:false,
     service_id:2,
     host_id:4,
     work_status:true
@@ -114,8 +121,8 @@ App.Component.FIXTURES = [
   {
     id:6,
     component_name:'HBase Master',
-    label: 'HBM',
-    type: true,
+    label:'HBM',
+    type:true,
     service_id:3,
     host_id:4,
     work_status:true
@@ -123,8 +130,8 @@ App.Component.FIXTURES = [
   {
     id:7,
     component_name:'Region Server',
-    label: 'RS',
-    type: false,
+    label:'RS',
+    type:false,
     service_id:3,
     host_id:2,
     work_status:true
@@ -141,21 +148,21 @@ App.Component.FIXTURES = [
 ];
 
 App.Service = DS.Model.extend({
-  serviceName: DS.attr('string'),
-  label: DS.attr('string'),
-  components: DS.hasMany('App.Component'),
-  serviceAudit: DS.hasMany('App.ServiceAudit'),
-  healthStatus: DS.attr('string'),
-  workStatus: DS.attr('boolean'),
-  alerts: DS.hasMany('App.Alert'),
-  quickLinks: DS.hasMany('App.QuickLinks')
+  serviceName:DS.attr('string'),
+  label:DS.attr('string'),
+  components:DS.hasMany('App.Component'),
+  serviceAudit:DS.hasMany('App.ServiceAudit'),
+  healthStatus:DS.attr('string'),
+  workStatus:DS.attr('boolean'),
+  alerts:DS.hasMany('App.Alert'),
+  quickLinks:DS.hasMany('App.QuickLinks')
 });
 
 App.Service.Health = {
-  live: "LIVE",
-  dead: "DEAD",
-  start: "STARTING",
-  stop: "STOPPING"
+  live:"LIVE",
+  dead:"DEAD",
+  start:"STARTING",
+  stop:"STOPPING"
 }
 
 App.Service.FIXTURES = [
@@ -163,134 +170,134 @@ App.Service.FIXTURES = [
     id:1,
     service_name:'hdfs',
     label:'HDFS',
-    components: [1, 2, 3],
-    service_audit: [1, 2, 3],
-    health_status: App.Service.Health.live,
-    work_status: true,
-    alerts: [1, 2],
-    quick_links: [1, 2, 3, 4]
+    components:[1, 2, 3],
+    service_audit:[1, 2, 3],
+    health_status:App.Service.Health.live,
+    work_status:true,
+    alerts:[1, 2],
+    quick_links:[1, 2, 3, 4]
   },
   {
     id:2,
     service_name:'mapreduce',
     label:'MapReduce',
-    components: [4, 5],
-    service_audit: [4, 5, 6],
-    health_status: App.Service.Health.start,
-    work_status: true,
-    alerts: [3, 4],
-    quick_links: [5, 6, 7, 8, 9, 10]
+    components:[4, 5],
+    service_audit:[4, 5, 6],
+    health_status:App.Service.Health.start,
+    work_status:true,
+    alerts:[3, 4],
+    quick_links:[5, 6, 7, 8, 9, 10]
   },
   {
     id:3,
     service_name:'hbase',
     label:'HBase',
-    components: [6, 7],
-    health_status: App.Service.Health.dead,
-    work_status: false,
-    alerts: [5, 6],
-    quick_links: [11, 12, 13, 14]
+    components:[6, 7],
+    health_status:App.Service.Health.dead,
+    work_status:false,
+    alerts:[5, 6],
+    quick_links:[11, 12, 13, 14]
   },
   {
     id:4,
     service_name:'zookeeper',
     label:'Zookeeper',
-    health_status: App.Service.Health.stop,
-    work_status: false,
-    alerts: [7, 8]
+    health_status:App.Service.Health.stop,
+    work_status:false,
+    alerts:[7, 8]
   },
   {
     id:5,
     service_name:'oozie',
     label:'Oozie',
-    health_status: App.Service.Health.dead,
-    work_status: false,
-    alerts: [9, 10]
+    health_status:App.Service.Health.dead,
+    work_status:false,
+    alerts:[9, 10]
   },
   {
     id:6,
     service_name:'hive',
     label:'Hive',
-    health_status: App.Service.Health.dead,
-    work_status: false,
-    alerts: [11, 12]
+    health_status:App.Service.Health.dead,
+    work_status:false,
+    alerts:[11, 12]
   }
 ];
 
 App.QuickLinks = DS.Model.extend({
-  label: DS.attr('string'),
-  url: DS.attr('string')
+  label:DS.attr('string'),
+  url:DS.attr('string')
 });
 
 App.QuickLinks.FIXTURES = [
   {
-    id: 1,
-    label: 'NameNode UI',
-    url: ''
+    id:1,
+    label:'NameNode UI',
+    url:''
   },
   {
-    id: 2,
-    label: 'NameNode logs',
-    url: ''
+    id:2,
+    label:'NameNode logs',
+    url:''
   },
   {
-    id: 3,
-    label: 'NameNode JMX',
-    url: ''
+    id:3,
+    label:'NameNode JMX',
+    url:''
   },
   {
-    id: 4,
-    label: 'Thread Stacks',
-    url: ''
+    id:4,
+    label:'Thread Stacks',
+    url:''
   },
   {
-    id: 5,
-    label: 'JobTracker UI',
-    url: ''
+    id:5,
+    label:'JobTracker UI',
+    url:''
   },
   {
-    id: 6,
-    label: 'Scheduling Info',
-    url: ''
+    id:6,
+    label:'Scheduling Info',
+    url:''
   },
   {
-    id: 7,
-    label: 'Running Jobs',
-    url: ''
+    id:7,
+    label:'Running Jobs',
+    url:''
   },
   {
-    id: 8,
-    label: 'Retired Jobs',
-    url: ''
+    id:8,
+    label:'Retired Jobs',
+    url:''
   },
   {
-    id: 9,
-    label: 'JobHistory Server',
-    url: ''
+    id:9,
+    label:'JobHistory Server',
+    url:''
   },
   {
-    id: 10,
-    label: 'JobTracker Logs',
-    url: ''
+    id:10,
+    label:'JobTracker Logs',
+    url:''
   },
   {
-    id: 11,
-    label: 'HBase Master UI',
-    url: ''
+    id:11,
+    label:'HBase Master UI',
+    url:''
   },
   {
-    id: 12,
-    label: 'HBase Logs',
-    url: ''
+    id:12,
+    label:'HBase Logs',
+    url:''
   },
   {
-    id: 13,
-    label: 'Zookeeper Info',
-    url: ''
+    id:13,
+    label:'Zookeeper Info',
+    url:''
   },
   {
-    id: 14,
-    label: 'HBase Master JMX',
-    url: ''
+    id:14,
+    label:'HBase Master JMX',
+    url:''
   }
 ];

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

@@ -91,7 +91,7 @@ App.Router = Em.Router.extend({
   getWizardCurrentStep: function (wizardType) {
     var loginName = this.getLoginName();
     var currentStep = App.db.getWizardCurrentStep(wizardType);
-    console.log('getInstallerCurrentStep: loginName=' + loginName + ", currentStep=" + currentStep);
+    console.log('getWizardCurrentStep: loginName=' + loginName + ", currentStep=" + currentStep);
     if (!currentStep) {
       currentStep = '1';
     }

+ 75 - 4
ambari-web/app/routes/add_host_routes.js

@@ -35,6 +35,26 @@ module.exports = Em.Route.extend({
     router.get('mainController').connectOutlet('addHost');
   },
 
+  step0: Em.Route.extend({
+    route: '/step0',
+    connectOutlets: function (router) {
+      console.log('in addHost.step0:connectOutlets');
+      var controller = router.get('addHostController');
+      controller.setCurrentStep('0', false);
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep1', controller.get('content'));
+    },
+
+    next: function (router) {
+      var addHostController = router.get('addHostController');
+      var wizardStep1Controller = router.get('wizardStep1Controller');
+
+      addHostController.saveClusterInfo(wizardStep1Controller);
+
+      router.transitionTo('step1');
+    }
+  }),
+
   step1: Em.Route.extend({
     route: '/step1',
     connectOutlets: function (router) {
@@ -129,7 +149,7 @@ module.exports = Em.Route.extend({
     next: function (router, context) {
       var addHostController = router.get('addHostController');
       var wizardStep5Controller = router.get('wizardStep5Controller');
-      addHostController.saveMasterComponentHosts( wizardStep5Controller );
+      addHostController.saveMasterComponentHosts(wizardStep5Controller);
       router.transitionTo('step5');
     }
   }),
@@ -144,17 +164,68 @@ module.exports = Em.Route.extend({
       controller.connectOutlet('wizardStep6', controller.get('content'));
     },
     back: Em.Router.transitionTo('step4'),
-    next: function(router){
+    next: function (router) {
       var addHostController = router.get('addHostController');
       var wizardStep6Controller = router.get('wizardStep6Controller');
 
-      if(wizardStep6Controller.validate()){
+      if (wizardStep6Controller.validate()) {
         addHostController.saveSlaveComponentHosts(wizardStep6Controller);
-        router.transitionTo('step7');
+        router.transitionTo('step6');
       }
     }
   }),
 
+  step6: Em.Route.extend({
+    route: '/step6',
+    connectOutlets: function (router) {
+      console.log('in addhost.step6:connectOutlets');
+      var controller = router.get('addHostController');
+      controller.setCurrentStep('6', false);
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep7', controller.get('content'));
+    },
+    back: Em.Router.transitionTo('step5'),
+    next: function (router) {
+      var addHostController = router.get('addHostController');
+      var wizardStep7Controller = router.get('wizardStep7Controller');
+      addHostController.saveServiceConfigProperties(wizardStep7Controller);
+      router.transitionTo('step7');
+    }
+  }),
+
+  step7: Em.Route.extend({
+    route: '/step7',
+    connectOutlets: function (router, context) {
+      console.log('in addHost.step7:connectOutlets');
+      var controller = router.get('addHostController');
+      controller.setCurrentStep('7', false);
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep8', controller.get('content'));
+    },
+    back: Em.Router.transitionTo('step6'),
+    next: Em.Router.transitionTo('step8')
+  }),
+
+  step8: Em.Route.extend({
+    route: '/step8',
+    connectOutlets: function (router, context) {
+      console.log('in addHost.step8:connectOutlets');
+      var controller = router.get('addHostController');
+      controller.setInfoForStep9();
+      controller.setCurrentStep('8', false);
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep9', controller.get('content'));
+    },
+    back: Em.Router.transitionTo('step7'),
+    next: function (router) {
+      var addHostController = router.get('addHostController');
+      var wizardStep9Controller = router.get('wizardStep9Controller');
+      addHostController.saveClusterInfo(wizardStep9Controller);
+      addHostController.saveInstalledHosts(wizardStep9Controller);
+      router.transitionTo('step9');
+    }
+  }),
+
 //  step6: Em.Route.extend({
 //    route: '/step6',
 //    connectOutlets: function (router, context) {

+ 27 - 22
ambari-web/app/routes/installer.js

@@ -25,6 +25,7 @@ module.exports = Em.Route.extend({
     console.log('in /installer:enter');
 
     if (router.getAuthenticated()) {
+      router.get('mainController').stopLoadOperationsPeriodically();
       console.log('In installer with successful authenticated');
       // router.loadAllPriorSteps(router.getInstallerCurrentStep());
       Ember.run.next(function () {
@@ -159,47 +160,51 @@ module.exports = Em.Route.extend({
   step7: Em.Route.extend({
     route: '/step7',
     connectOutlets: function (router, context) {
-      router.setNavigationFlow('step7');
+      var controller = router.get('installerController');
       router.setInstallerCurrentStep('7', false);
-      router.get('installerController').connectOutlet('installerStep7');
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep7', controller.get('content'));
     },
     back: Em.Router.transitionTo('step6'),
-    next: Em.Router.transitionTo('step8')
+    next: function(router){
+      var installerController = router.get('installerController');
+      var wizardStep7Controller = router.get('wizardStep7Controller');
+      installerController.saveServiceConfigProperties( wizardStep7Controller );
+      router.transitionTo('step8');
+    }
   }),
 
   step8: Em.Route.extend({
     route: '/step8',
-
     connectOutlets: function (router, context) {
-      router.setNavigationFlow('step8');
+      console.log('in installer.step8:connectOutlets');
+      var controller = router.get('installerController');
       router.setInstallerCurrentStep('8', false);
-      router.get('installerController').connectOutlet('installerStep8');
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep8', controller.get('content'));
     },
     back: Em.Router.transitionTo('step7'),
-
-    next: function (router, context) {
-      App.db.setClusterStatus({status: 'pending', isCompleted: false});
-      var hostInfo = App.db.getHosts();
-      for (var index in hostInfo) {
-        hostInfo[index].status = "pending";
-        hostInfo[index].message = 'Information';
-        hostInfo[index].progress = '0';
-      }
-      App.db.setHosts(hostInfo);
-      console.log("TRACE: host name is: " + hostInfo[index].name);
-      router.transitionTo('step9');
-    }
+    next: Em.Router.transitionTo('step9')
   }),
 
   step9: Em.Route.extend({
     route: '/step9',
     connectOutlets: function (router, context) {
-      router.setNavigationFlow('step9');
+      console.log('in installer.step9:connectOutlets');
+      var controller = router.get('installerController');
+      controller.setInfoForStep9();
       router.setInstallerCurrentStep('9', false);
-      router.get('installerController').connectOutlet('installerStep9');
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep9', controller.get('content'));
     },
     back: Em.Router.transitionTo('step8'),
-    next: Em.Router.transitionTo('step10')
+    next: function (router) {
+      var addHostController = router.get('installerController');
+      var wizardStep9Controller = router.get('wizardStep9Controller');
+      addHostController.saveClusterInfo(wizardStep9Controller);
+      addHostController.saveInstalledHosts(wizardStep9Controller);
+      router.transitionTo('step10');
+    }
   }),
 
   step10: Em.Route.extend({

+ 1 - 0
ambari-web/app/routes/main.js

@@ -21,6 +21,7 @@ module.exports = Em.Route.extend({
   enter:function (router) {
     console.log('in /main:enter');
     if (router.getAuthenticated()) {
+      router.get('mainController').startLoadOperationsPeriodically();
       // TODO: redirect to last known state
       /*
        Ember.run.next(function () {

+ 44 - 4
ambari-web/app/styles/application.less

@@ -127,10 +127,9 @@ footer {
   .brand {
     font-size: 17px;
     border-right: 1px solid rgba(0, 0, 0, 0.08);
-    .operations-count{
-      margin-top: 5px;
-      background-color: #006DCC;
-    }
+  }
+  .operations-count{
+    background-color: #006DCC;
   }
   li {
     font-size: 15px;
@@ -340,6 +339,8 @@ a:focus {
 
 @status-live-marker: url("/img/health-status-live.png");
 @status-dead-marker: url("/img/health-status-dead.png");
+@status-dead-orange-marker: url("/img/health-status-dead-orange.png");
+@status-dead-yellow-marker: url("/img/health-status-dead-yellow.png");
 @status-ok-marker: url("/img/status-ok.jpg");
 @status-corrupt-marker: url("/img/status-corrupt.jpg");
 @arrow-right: url("/img/arrow-right.png");
@@ -418,6 +419,14 @@ a:focus {
     .tab-marker-position;
     background-image: @status-dead-marker;
   }
+  .health-status-DEAD-ORANGE {
+    .tab-marker-position;
+    background-image: @status-dead-orange-marker;
+  }
+  .health-status-DEAD-YELLOW {
+    .tab-marker-position;
+    background-image: @status-dead-yellow-marker;
+  }
   dt {
     text-align: left;
     width: 120px;
@@ -723,6 +732,12 @@ a:focus {
   .health-status-DEAD {
     background-image: @status-dead-marker;
   }
+  .health-status-DEAD-ORANGE {
+    background-image: @status-dead-orange-marker;
+  }
+  .health-status-DEAD-YELLOW {
+    background-image: @status-dead-yellow-marker;
+  }
   .box-header {
     .btn-group {
       float: left;
@@ -806,6 +821,17 @@ a:focus {
     }
     .page-listing {
       width: 100px;
+
+      .table {
+        th.name {
+          width: 300px;
+          a.filter-label {
+            width: 57px;
+            display: block;
+            float: left;
+          }
+        }
+      }
     }
   }
 }
@@ -821,6 +847,16 @@ a:focus {
     background-repeat: no-repeat;
     background-position: 0px 4px;
   }
+  .health-status-DEAD-ORANGE {
+    background-image: @status-dead-orange-marker;
+    background-repeat: no-repeat;
+    background-position: 0px 4px;
+  }
+  .health-status-DEAD-YELLOW {
+    background-image: @status-dead-yellow-marker;
+    background-repeat: no-repeat;
+    background-position: 0px 4px;
+  }
   .back {
     display: block;
     width: 105px;
@@ -1507,3 +1543,7 @@ ul.inline li {
   width: 105px;
   margin-bottom: 10px;
 }
+
+#step8-content{
+  max-height: 570px;
+}

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

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

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

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

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

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

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

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

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

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

+ 0 - 100
ambari-web/app/templates/installer/step7.hbs

@@ -1,100 +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.
--->
-
-<div id="serviceConfig">
-  <h2>{{t installer.step7.header}}</h2>
-
-  <div class="alert alert-info">
-    {{t installer.step7.body}}
-  </div>
-  <ul class="nav nav-tabs">
-    {{#each service in controller}}
-    {{#view App.ServiceConfigTab}}
-    <a class="active" href="#{{unbound service.serviceName}}"
-       data-toggle="tab" {{action selectService service target="view"}}>{{service.displayName}}{{#if service.errorCount}}<span
-            class="badge badge-important">{{service.errorCount}}</span>{{/if}}</a>
-    {{/view}}
-    {{/each}}
-  </ul>
-  <div class="accordion">
-    {{#each category in selectedService.configCategories}}
-    <div class="accordion-group">
-      <div class="accordion-heading">
-        <a class="accordion-toggle">
-          {{category.name}}
-        </a>
-      </div>
-      {{#view App.ServiceConfigsByCategoryView categoryBinding="category" serviceConfigsBinding="selectedService.configs"}}
-      <div class="accordion-body collapse in">
-        <div class="accordion-inner">
-          {{#if category.isForSlaveComponent}}
-          <div class="slave-component-group-menu">
-            {{view App.SlaveComponentGroupsMenu}}
-          </div>
-          {{#view App.AddSlaveComponentGroupButton slaveComponentNameBinding="category.name"}}
-          <a
-                  class="btn add-slave-component-group btn-large" {{action addSlaveComponentGroup category.name target="App.router.slaveComponentGroupsController"}}><i
-                  class="icon-plus"></i></a>
-          {{/view}}
-          <div class="remove-group-error control-group warning">
-            <span class="help-inline">You cannot delete this group since there are hosts assigned to it. You must assign them to another group before you can delete this group.</span>
-          </div>
-          {{/if}}
-          <form class="form-horizontal">
-            {{#if category.isForSlaveComponent}}
-            {{#view App.SlaveComponentChangeGroupNameView}}
-            <label class="control-label">Group name</label>
-
-            <div class="controls">
-              <div class="span6">
-                <input class="span9" type="text" {{bindAttr value="view.content.name"}}>
-                <button class="btn" {{action changeGroupName target="view"}}>Save</button>
-              </div>
-              <span class="help-inline">{{view.errorMessage}}</span>
-            </div>
-            {{/view}}
-            {{/if}}
-            {{#each view.categoryConfigs}}
-            {{#if isVisible}}
-            <div {{bindAttr class="errorMessage:error: :control-group"}}>
-              <label class="control-label">{{displayName}}</label>
-              <div class="controls">
-                {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs"}}
-                <span class="help-inline">{{errorMessage}}</span>
-              </div>
-            </div>
-            {{/if}}
-            {{/each}}
-          </form>
-        </div>
-      </div>
-      {{/view}}
-    </div>
-    {{/each}}
-  </div>
-  {{#if isSubmitDisabled}}
-  <div class="alert">{{t installer.step7.attentionNeeded}}</div>
-  {{/if}}
-  <div class="btn-area">
-    <a class="btn" {{action back}}>&larr; Back</a>
-
-    <div style="float:right">
-      <a {{bindAttr class=":btn :btn-success"}} {{bindAttr disabled="isSubmitDisabled"}} {{action submit target="controller"}}>Next &rarr;</a>
-    </div>
-  </div>
-</div>

+ 2 - 4
ambari-web/app/templates/main.hbs

@@ -20,11 +20,9 @@
     <div class="navbar-inner">
       <a class="brand" href="#">
         My Cluster
-        {{#if view.isInHostsPath}}
-          {{#if view.backgroundOperationsCount}}
-            <span class="label operations-count" {{action "showBackgroundOperationsPopup" target="App.router.mainHostController"}}>{{view.backgroundOperationsCount}}</span>
+          {{#if backgroundOperationsCount}}
+            <span class="label operations-count" {{action "showBackgroundOperationsPopup" target="controller"}}>{{backgroundOperationsCount}}</span>
           {{/if}}
-        {{/if}}
       </a>
       {{view App.MainMenuView}}
     </div>

+ 44 - 0
ambari-web/app/templates/main/background_operations_popup.hbs

@@ -0,0 +1,44 @@
+<!--
+* 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.
+-->
+{{#each operation in backgroundOperations.tasks}}
+{{#view App.MainBackgroundOperation contentBinding="operation"}}
+<a class="open-details" {{action openDetails target="view"}} href="#">
+  <i {{bindAttr class="view.iconClass"}}></i>
+</a>
+{{operation.command}} {{operation.role}} on {{operation.hostname}}
+<div class="operation-details">
+  {{#if operation.details.length}}
+    {{#each detail in operation.details}}
+    <div>{{detail.startTime}}: {{detail.name}}</div>
+    {{/each}}
+  {{/if}}
+  <a {{action showOperationLog target="view"}} href="#">
+    {{#if view.isOpenShowLog}}Hide{{else}}Show{{/if}} operation log
+  </a>
+  {{#if view.isOpenShowLog}}
+  <div class="operation-log">
+    <dl class="dl-horizontal">
+      <dt>exitcode:</dt><dd>{{view.logDetails.exitcode}}</dd>
+      <dt>stdout:</dt><dd>{{view.logDetails.stdout}}</dd>
+      <dt>stderror:</dt><dd>{{view.logDetails.stderror}}</dd>
+    </dl>
+  </div>
+  {{/if}}
+</div>
+{{/view}}
+{{/each}}

+ 57 - 16
ambari-web/app/templates/main/host.hbs

@@ -19,24 +19,28 @@
   <div class="box-header">
     <div class="button-section">
       <div class="btn-group">
-        <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
+        <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary dropdown-toggle"
+                                                              data-toggle="dropdown">
           Rack
           <span class="caret"></span>
         </button>
         <ul class="dropdown-menu">
           {{#each clusters}}
           <li>
-            <a href="javascript:void(null)" data-toggle="modal" {{action "assignedToRackPopup" this target="controller"}}>
+            <a href="javascript:void(null)"
+               data-toggle="modal" {{action "assignedToRackPopup" this target="controller"}}>
               {{clusterName}}
             </a>
           </li>
           {{/each}}
         </ul>
       </div>
-      <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary decommission" data-toggle="modal" {{action "decommissionButtonPopup" target="controller"}}>
+      <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary decommission"
+                                                            data-toggle="modal" {{action "decommissionButtonPopup" target="controller"}}>
         Decommission
       </button>
-      <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary" data-toggle="modal" {{action "deleteButtonPopup" target="controller"}}>
+      <button {{bindAttr disabled="controller.isDisabled"}} class="btn btn-primary"
+                                                            data-toggle="modal" {{action "deleteButtonPopup" target="controller"}}>
         Delete
       </button>
       <button class="btn btn-inverse add-host-button" {{action addHost}}>
@@ -53,11 +57,16 @@
           {{view Ember.Checkbox checkedBinding="allChecked" class="checkbox"}}
         </label>
       </th>
-      <th>
-        <a href="#" {{action sortByName target="controller" }}>Name</a>
-        {{#if controller.isSort}}
-        <i class="icon-arrow-up"{{bindAttr class="controller.sortClass"}}></i>
-        {{/if}}
+      <th class="name">
+        <a class="filter-label" href="#" {{action sortByName target="controller" }}>Name
+          {{#if controller.isSort}}
+          <i class="icon-arrow-up"{{bindAttr class="controller.sortClass"}}></i>
+          {{/if}}
+        </a>
+
+        <div class="span1">
+          {{view Ember.TextField placeholder="search" valueBinding="view.filterByName"}}
+        </div>
       </th>
       <th>Rack</th>
       <th>CPU</th>
@@ -71,19 +80,47 @@
             <span class="caret"></span>
           </button>
           <ul class="dropdown-menu filter-components" id="filter-dropdown">
-            {{#each component in componentsForFilter}}
             <li>
-              <label>
-                {{view view.ComponentCheckboxView contentBinding="component"}}                                {{unbound component.componentName}}
+              <label class="checkbox">
+                {{view Ember.Checkbox checkedBinding="view.allComponentsChecked"}} All
               </label>
             </li>
-            {{/each}}
+
+            <li>
+              <label class="checkbox">
+                {{view Ember.Checkbox checkedBinding="view.masterComponentsChecked"}} Master Components:
+              </label>
+              <ul>
+                {{#each component in masterComponents}}
+                <li>
+                  <label class="checkbox">
+                    {{view view.ComponentCheckboxView contentBinding="component"}} {{unbound component.componentName}}
+                  </label>
+                </li>
+                {{/each}}
+              </ul>
+            </li>
+
+            <li>
+              <label class="checkbox">
+                {{view Ember.Checkbox checkedBinding="view.slaveComponentsChecked"}} Slave Components:
+              </label>
+              <ul>
+                {{#each component in slaveComponents}}
+                <li>
+                  <label class="checkbox">
+                    {{view view.ComponentCheckboxView contentBinding="component" }} {{unbound component.componentName}}
+                  </label>
+                </li>
+                {{/each}}
+              </ul>
+            </li>
+
           </ul>
           <button {{bindAttr disabled="view.isApplyDisabled"}} class="btn" {{action "applyFilters" target="view"}}>
             Apply
           </button>
         </div>
-
       </th>
     </tr>
     </thead>
@@ -103,7 +140,11 @@
       <td>{{host.cluster.clusterName}}</td>
       <td>{{host.cpu}}</td>
       <td>{{host.memory}}</td>
-      <td>{{host.diskUsage}}</td>
+      <td>
+        <div class="progress progress-info">
+          <div class="bar" {{bindAttr style="view.usageStyle"}} style="width: 20%"></div>
+        </div>
+      </td>
       <td>{{host.loadAvg}}</td>
       <td>
         {{view.labels}}
@@ -114,7 +155,7 @@
     </tbody>
   </table>
   <div class="box-footer">
-    <hr />
+    <hr/>
     <div class="footer-pagination">
       <ul class="nav nav-pills">
         <li class="disabled">Show Hosts</li>

+ 6 - 7
ambari-web/app/templates/main/host/background_operations_popup.hbs

@@ -15,13 +15,12 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 -->
-
-{{#each operation in backgroundOperations.tasks}}
-{{#view App.MainBackgroundOperation contentBinding="operation"}}
+{{#each operation in hostOperations}}
+{{#view App.MainHostDetailsOperations contentBinding="operation"}}
 <a class="open-details" {{action openDetails target="view"}} href="#">
   <i {{bindAttr class="view.iconClass"}}></i>
 </a>
-{{operation.command}} {{operation.role}} on {{operation.hostname}}
+{{operation.command}} {{operation.role}} on {{operation.hostName}}
 <div class="operation-details">
   {{#if operation.details.length}}
     {{#each detail in operation.details}}
@@ -34,9 +33,9 @@
   {{#if view.isOpenShowLog}}
   <div class="operation-log">
     <dl class="dl-horizontal">
-      <dt>exitcode:</dt><dd>{{view.logDetails.exitcode}}</dd>
-      <dt>stdout:</dt><dd>{{view.logDetails.stdout}}</dd>
-      <dt>stderror:</dt><dd>{{view.logDetails.stderror}}</dd>
+      <dt>Exit Code:</dt><dd>{{operation.logs.exitcode}}</dd>
+      <dt>Std Out:</dt><dd>{{operation.logs.stdout}}</dd>
+      <dt>Error:</dt><dd>{{operation.logs.stderror}}</dd>
     </dl>
   </div>
   {{/if}}

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

@@ -29,7 +29,7 @@
       <!-- dropdown menu links -->
         {{#each option in view.maintenance}}
         <li>
-        <a {{action deleteButtonPopup target="controller"}} href="#">{{option.label}}</a>
+        <a {{action validateDeletion target="controller"}} href="#">{{option.label}}</a>
         </li>
         {{/each}}
       </ul>

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

@@ -37,6 +37,18 @@
           <span class="caret"></span>
         </button>
         <ul class="dropdown-menu">
+          {{#if view.isDataNode}}
+            <li>
+              <a href="javascript:void(null)" data-toggle="modal" {{action "decommission" view.content target="controller"}}>
+                Decommission
+              </a>
+            </li>
+            <li>
+              <a href="javascript:void(null)" data-toggle="modal" {{action "recommission" view.content target="controller"}}>
+                Recommission
+              </a>
+            </li>
+          {{/if}}
             <li {{bindAttr class="view.content.workStatus::hidden"}}>
               <a href="javascript:void(null)" data-toggle="modal" {{action "stopComponent" view.content target="controller"}}>
                 Stop

+ 30 - 0
ambari-web/app/templates/main/menu_item.hbs

@@ -0,0 +1,30 @@
+<!--
+* 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.
+-->
+<a href="#/main/{{unbound view.content.routing}}">
+  {{unbound view.content.label}}
+  {{#if view.alertsCount}}
+    <span class="label label-important alerts-count">
+      {{view.alertsCount}}
+    </span>
+  {{/if}}
+  {{#if view.hostDetailsOperationsCount}}
+    <span class="label operations-count" {{action "showBackgroundOperationsPopup" target="App.router.mainHostDetailsController"}}>
+      {{view.hostDetailsOperationsCount}}
+    </span>
+  {{/if}}
+</a>

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

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

+ 19 - 17
ambari-web/app/templates/main/service/item.hbs

@@ -33,23 +33,25 @@
     </ul>
   </div>
   {{#if controller.content.workStatus}}
-    <a href="javascript:void(null)" class="btn disabled">
-      <i class="icon-play"></i>
-      Start
-    </a>
-    <a href="javascript:void(null)" class="btn btn-danger" data-toggle="modal" {{action "stopConfirmPopup" target="controller"}}>
-      <i class="icon-stop icon-white"></i>
-      Stop
-    </a>
+  <a href="javascript:void(null)" class="btn disabled">
+    <i class="icon-play"></i>
+    Start
+  </a>
+  <a href="javascript:void(null)" class="btn btn-danger"
+     data-toggle="modal" {{action "stopConfirmPopup" target="controller"}}>
+    <i class="icon-stop icon-white"></i>
+    Stop
+  </a>
   {{else}}
-    <a href="javascript:void(null)" class="btn btn-success" data-toggle="modal" {{action "startConfirmPopup" target="controller"}}>
-      <i class="icon-play icon-white"></i>
-      Start
-    </a>
-    <a href="javascript:void(null)" class="btn disabled">
-      <i class="icon-stop"></i>
-      Stop
-    </a>
+  <a href="javascript:void(null)" class="btn btn-success"
+     data-toggle="modal" {{action "startConfirmPopup" target="controller"}}>
+    <i class="icon-play icon-white"></i>
+    Start
+  </a>
+  <a href="javascript:void(null)" class="btn disabled">
+    <i class="icon-stop"></i>
+    Stop
+  </a>
   {{/if}}
 </div>
-{{outlet}}
+    {{outlet}}

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

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

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

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

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

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

+ 2 - 2
ambari-web/app/templates/installer/slave_component_hosts_popup.hbs → ambari-web/app/templates/wizard/slave_component_hosts_popup.hbs

@@ -14,10 +14,10 @@
       </td>
       <td>
         {{#view App.SlaveComponentDropDownGroupView contentBinding="host"}}
-        <select>
+        <select {{action changeGroup target="view" on="change"}}>
           {{#each groupName in controller.getGroupsForDropDown}}
             {{#view view.optionTag contentBinding="groupName"}}
-              <option value="{{unbound groupName}}" {{bindAttr selected="view.selected"}} {{action changeGroup target="view"}}>
+              <option value="{{unbound groupName}}" {{bindAttr selected="view.selected"}}>
                 {{groupName}}
               </option>
             {{/view}}

+ 12 - 0
ambari-web/app/templates/wizard/slave_hosts.hbs

@@ -0,0 +1,12 @@
+{{#if view.hasNoHosts}}
+  No host assigned
+{{else}}
+  <a href="#" {{action showEditSlaveComponentGroups view.serviceConfig.category target="controller"}}>
+    {{#if view.hasOneHost}}
+      {{hosts.firstObject.hostname}}
+    {{/if}}
+    {{#if view.hasMultipleHosts}}
+      {{hosts.firstObject.hostname}} and {{view.otherLength}}
+    {{/if}}
+  </a>
+{{/if}}

+ 42 - 0
ambari-web/app/templates/wizard/step1.hbs

@@ -0,0 +1,42 @@
+<!--
+* 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.
+-->
+
+<h2>{{t installer.step1.body.header}}</h2>
+<p class="alert alert-info">
+  {{t installer.step1.body}}
+</p>
+<div {{bindAttr class="view.onError:error :control-group"}}>
+  <label class="control-label" for="cluster-name">{{t installer.step1.clusterName}}
+    <a href="javascript:void(null)"
+       rel="popover"
+      {{translateAttr title="installer.step1.clusterName.tooltip.title"
+       data-content="installer.step1.clusterName.tooltip.content"}}>Learn more</a>
+  </label>
+
+  <div class="controls">
+    {{view Ember.TextField id="cluster-name" valueBinding="content.name" placeholder="cluster name" target="controller"}}
+    <p class="help-inline">{{clusterNameError}}</p>
+  </div>
+</div>
+
+<div class="btn-area">
+  <a class="btn btn-success pull-right" {{bindAttr disabled="invalidClusterName"}} {{action "submit" target="controller"}}>Next &rarr;</a>
+</div>
+
+
+

+ 129 - 0
ambari-web/app/templates/wizard/step7.hbs

@@ -0,0 +1,129 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<div id="serviceConfig">
+  <h2>{{t installer.step7.header}}</h2>
+
+  <div class="alert alert-info">
+    {{t installer.step7.body}}
+  </div>
+
+  <ul class="nav nav-tabs">
+    {{#each service in controller.stepConfigs}}
+      {{#view App.WizardStep7.ServiceConfigTab}}
+        <a class="active" href="#{{unbound service.serviceName}}"
+           data-toggle="tab" {{action selectService service target="view"}}>
+          {{service.displayName}}{{#if service.errorCount}}<span
+          class="badge badge-important">{{service.errorCount}}</span>{{/if}}</a>
+      {{/view}}
+    {{/each}}
+  </ul>
+
+  <div class="accordion">
+    {{#each category in selectedService.configCategories}}
+    <div class="accordion-group">
+      <div class="accordion-heading">
+        <a class="accordion-toggle">
+          {{category.name}}
+        </a>
+      </div>
+
+      {{#unless category.isForSlaveComponent}}
+        {{#view App.WizardStep7.ServiceConfigsByCategoryView categoryBinding="category" serviceConfigsBinding="selectedService.configs"}}
+          <form class="form-horizontal">
+
+            {{#each view.categoryConfigs}}
+              {{#if isVisible}}
+              <div {{bindAttr class="errorMessage:error: :control-group"}}>
+                <label class="control-label">{{displayName}}</label>
+
+                <div class="controls">
+                  {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs"}}
+                  <span class="help-inline">{{errorMessage}}</span>
+                </div>
+              </div>
+              {{/if}}
+            {{/each}}
+
+          </form>
+        {{/view}}
+      {{/unless}}
+
+      {{#if category.isForSlaveComponent}}
+        {{#view App.WizardStep7.ServiceConfigsByCategoryView categoryBinding="category" serviceConfigsBinding="selectedService.configs" controllerBinding="App.router.slaveComponentGroupsController"}}
+            <div class="slave-component-group-menu">
+              {{view App.SlaveComponentGroupsMenu}}
+            </div>
+
+            {{#view App.AddSlaveComponentGroupButton slaveComponentNameBinding="category.name"}}
+            <a
+              class="btn add-slave-component-group btn-large" {{action addSlaveComponentGroup target="controller"}}><i
+              class="icon-plus"></i></a>
+            {{/view}}
+            <div class="remove-group-error control-group warning">
+              <span class="help-inline">You cannot delete this group since there are hosts assigned to it. You must assign them to another group before you can delete this group.</span>
+            </div>
+
+            <form class="form-horizontal">
+
+              {{#view App.SlaveComponentChangeGroupNameView}}
+                <label class="control-label">Group name</label>
+
+                <div class="controls">
+                  <div class="span6">
+                    <input class="span9" type="text" {{bindAttr value="view.content.name"}}>
+                    <button class="btn" {{action changeGroupName target="view"}}>Save</button>
+                  </div>
+                  <span class="help-inline">{{view.errorMessage}}</span>
+                </div>
+              {{/view}}
+
+
+              {{#each view.categoryConfigs}}
+                {{#if isVisible}}
+                <div {{bindAttr class="errorMessage:error: :control-group"}}>
+                  <label class="control-label">{{displayName}}</label>
+
+                  <div class="controls">
+                    {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs"}}
+                    <span class="help-inline">{{errorMessage}}</span>
+                  </div>
+                </div>
+                {{/if}}
+              {{/each}}
+
+            </form>
+
+
+        {{/view}}
+      {{/if}}
+    </div>
+    {{/each}}
+  </div>
+
+  {{#if isSubmitDisabled}}
+  <div class="alert">{{t installer.step7.attentionNeeded}}</div>
+  {{/if}}
+
+  <div class="btn-area">
+    <a class="btn" {{action back href="true"}}>&larr; Back</a>
+
+    <a class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}}
+      {{action submit target="controller"}}>Next &rarr;</a>
+  </div>
+</div>

+ 9 - 9
ambari-web/app/templates/installer/step8.hbs → ambari-web/app/templates/wizard/step8.hbs

@@ -19,14 +19,14 @@
 <h2>{{t installer.step8.header}}</h2>
 
 <div class="alert alert-info">
-	{{t installer.step8.body}}
+    {{t installer.step8.body}}
 </div>
-<div id="step8-content" class="well pre-scrollable" style="max-height: 570px;">
-	{{#each item in controller}}
-	<p>
+<div id="step8-content" class="well pre-scrollable">
+{{#each item in controller.clusterInfo}}
+<p>
 		<b>{{item.display_name}}</b> : {{item.config_value}}
-	</p>
-	{{/each}}
+</p>
+{{/each}}
 
 	<div>
 		<p><b>Services</b></p>
@@ -45,6 +45,6 @@
 	</div>
 </div>
 <div class="btn-area">
-	<a class="btn pull-left" {{action back}}>&larr; Back</a>
-	<a class="btn btn-success pull-right" {{action submit target="controller"}}>Next &rarr;</a>
-</div>
+    <a class="btn pull-left" {{action back href="true"}}>&larr; Back</a>
+    <a class="btn btn-success pull-right" {{action submit target="controller"}}>Next &rarr;</a>
+</div>

+ 2 - 2
ambari-web/app/templates/installer/step9.hbs → ambari-web/app/templates/wizard/step9.hbs

@@ -74,7 +74,7 @@
 				</thead>
 
 				<tbody>
-				{{#each host in controller}}
+				{{#each host in controller.hosts}}
 				{{#view App.HostStatusView objBinding="host"}}
 				<td>
 					{{host.name}}
@@ -120,4 +120,4 @@
 			</div>
 		</div>
 
-	</div>
+	</div>

+ 5 - 3
ambari-web/app/views.js

@@ -86,12 +86,14 @@ require('views/installer/step3_view');
 require('views/installer/step4_view');
 require('views/installer/step5_view');
 require('views/installer/step6_view');
-require('views/installer/step7_view');
-require('views/installer/step8_view');
-require('views/installer/step9_view');
 require('views/installer/step10_view');
+require('views/wizard/controls_view');
+require('views/wizard/step1_view');
 require('views/wizard/step2_view');
 require('views/wizard/step3_view');
 require('views/wizard/step4_view');
 require('views/wizard/step5_view');
 require('views/wizard/step6_view');
+require('views/wizard/step7_view');
+require('views/wizard/step8_view');
+require('views/wizard/step9_view');

+ 4 - 1
ambari-web/app/views/common/modal_popup.js

@@ -24,7 +24,10 @@ App.ModalPopup = Ember.View.extend({
     '<div class="modal-backdrop"></div><div class="modal" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal-label" aria-hidden="true">',
     '<div class="modal-header">',
     '<a class="close" {{action onClose target="view"}}>x</a>',
-    '<h3 id="modal-label">{{view.header}}</h3>',
+    '<h3 id="modal-label">',
+    '{{#if headerClass}}{{view headerClass}}',
+    '{{else}}{{header}}{{/if}}',
+    '</h3>',
     '</div>',
     '<div class="modal-body">',
     '{{#if bodyClass}}{{view bodyClass}}',

+ 27 - 13
ambari-web/app/views/main.js

@@ -20,18 +20,32 @@
 var App = require('app');
 
 App.MainView = Em.View.extend({
-  templateName: require('templates/main'),
-  isInHostsPath: function(){
-    var isInHostsPath = App.router.get('currentState.name') === 'hosts';
-    if (isInHostsPath){
-      App.router.get('mainHostController').startLoadOperationsPeriodically()
-    } else {
-      App.router.get('mainHostController').stopLoadOperationsPeriodically()
-    }
-    return isInHostsPath;
-  }.property('App.router.currentState.name'),
-  backgroundOperationsCount:function () {
-    return App.router.get('mainHostController.backgroundOperationsCount');
-  }.property('App.router.mainHostController.backgroundOperationsCount')
+  templateName: require('templates/main')
+});
 
+App.MainBackgroundOperation = Em.View.extend({
+  content: null,
+  classNames: ['background-operations'],
+  classNameBindings: ['isOpen'],
+  isOpen: false,
+  logDetails: null,
+  isOpenShowLog: false,
+  iconClass: function(){
+    return this.get('isOpen') ? 'icon-minus' : 'icon-plus';
+  }.property('isOpen'),
+  openDetails: function(){
+    this.set('isOpen', !this.get('isOpen'))
+  },
+  showOperationLog:function(){
+    var operation = this.get('content');
+    var self = this;
+    if (!this.get('isOpenShowLog') && !this.get('logDetails')) {
+      jQuery.getJSON('data/hosts/background_operations/logs/task' +operation.taskId + '.json',
+        function (data) {
+          self.set('logDetails', data);
+        }
+      );
+    }
+    this.set('isOpenShowLog', !this.get('isOpenShowLog'))
+  }
 });

+ 86 - 61
ambari-web/app/views/main/host.js

@@ -19,39 +19,70 @@
 var App = require('app');
 
 App.MainHostView = Em.View.extend({
-  templateName: require('templates/main/host'),
-  content:function(){
+  templateName:require('templates/main/host'),
+  filterByName:"",
+  controller: function(){
+    return App.router.get('mainHostController');
+  }.property(),
+  content:function () {
     return App.router.get('mainHostController.content');
   }.property('App.router.mainHostController.content'),
   componentsIds: [1, 2, 3, 4, 5, 6, 7, 8],
-  isFilterOpen: false,
-  isApplyDisabled: function(){
+
+  isFilterOpen:false,
+  isApplyDisabled:function () {
     return !this.get('isFilterOpen')
   }.property('isFilterOpen'),
-  btnGroupClass: function(){
+  btnGroupClass:function () {
     return this.get('isFilterOpen') ? 'btn-group open' : 'btn-group';
   }.property('isFilterOpen'),
 
-  applyFilters: function(){
+  applyFilters:function () {
     this.set('isFilterOpen', false);
-    App.router.get('mainHostController').filterByComponentsIds(this.get('componentsIds'));
+    App.router.get('mainHostController').filterByComponentsIds();
   },
 
-  clickFilterButton: function(){
+  allComponentsChecked: true,
+  toggleAllComponents: function(){
+    this.set('masterComponentsChecked', this.get('allComponentsChecked'));
+    this.set('slaveComponentsChecked', this.get('allComponentsChecked'));
+  }.observes('allComponentsChecked'),
+
+  masterComponentsChecked: false,
+  toggleMasterComponents: function(){
+    var checked = this.get('masterComponentsChecked');
+    this.get('controller.masterComponents').forEach(function(comp){
+      comp.set('checkedForHostFilter', checked);
+    });
+  }.observes('masterComponentsChecked'),
+
+  slaveComponentsChecked:false,
+  toggleSlaveComponents: function(){
+    var checked = this.get('slaveComponentsChecked');
+    this.get('controller.slaveComponents').forEach(function(comp){
+      comp.set('checkedForHostFilter', checked);
+    });
+  }.observes('slaveComponentsChecked'),
+
+  applyHostFilter:function () {
+    App.router.get('mainHostController').filterHostsBy('hostName', this.get('filterByName'));
+  }.observes('filterByName'),
+
+  clickFilterButton:function () {
     var self = this;
     this.set('isFilterOpen', !this.get('isFilterOpen'));
-    if (this.get('isFilterOpen')){
+    if (this.get('isFilterOpen')) {
       var filters = App.router.get('mainHostController.filters.components');
       $('.filter-component').each(function () {
-        var componentId = parseInt($(this).attr('id').replace('component-',''));
+        var componentId = parseInt($(this).attr('id').replace('component-', ''));
         var index = filters.indexOf(componentId);
         $(this).attr('checked', index == -1);
       });
-      this.set('componentsIds', filters.toArray());
+//      this.set('componentsIds', filters.toArray());
 
       var dropDown = $('#filter-dropdown');
       var firstClick = true;
-      $(document).bind('click', function(e) {
+      $(document).bind('click', function (e) {
         if (!firstClick && $(e.target).closest(dropDown).length == 0) {
           self.set('isFilterOpen', false);
           $(document).unbind('click');
@@ -60,66 +91,60 @@ App.MainHostView = Em.View.extend({
       });
     }
   },
-  HostView: Em.View.extend({
-    content: null,
-    labels: '',
-    init: function(){
+  HostView:Em.View.extend({
+    content:null,
+    labels:'',
+    init:function () {
       this._super();
       var labels = this.get('content.components').getEach('label');
       this.set('labels', labels.join(', '));
     },
-    HostCheckboxView: Em.Checkbox.extend({
-      content: null,
-      isChecked: false,
-      change: function(event) {
+    usageStyle:function () {
+      return "width:" + this.get('content.diskUsage') + "%";
+    }.property('content.diskUsage'),
+    HostCheckboxView:Em.Checkbox.extend({
+      content:null,
+      isChecked:false,
+      change:function (event) {
         this.set('isChecked', !this.get('content.isChecked'));
         App.router.get('mainHostController').onHostChecked(this.get('content'));
       }
     })
   }),
-  ComponentCheckboxView: Em.Checkbox.extend({
-    content: null,
-    elementId: function(){
+
+  ComponentCheckboxView:Em.Checkbox.extend({
+    content:null,
+    elementId:function () {
       return 'component-' + this.get('content.id');
     }.property('content.id'),
-    classNames: ['filter-component'],
-    parentView: function(){
+    classNames:['filter-component'],
+    parentView:function () {
       return this._parentView.templateData.view;
     }.property(),
-    change: function(event) {
-      var parent = this._parentView.templateData.view;
-      var componentsIds = parent.get('componentsIds');
-      var componentId = this.get('content.id');
-      var index = componentsIds.indexOf(componentId);
-      if(index==-1) componentsIds.push(componentId);
-      else componentsIds.splice(index, 1);
-    }
-  })
-});
+    checkedBinding:"content.checkedForHostFilter"
 
-App.MainBackgroundOperation = Em.View.extend({
-  content: null,
-  classNames: ['background-operations'],
-  classNameBindings: ['isOpen'],
-  isOpen: false,
-  logDetails: null,
-  isOpenShowLog: false,
-  iconClass: function(){
-    return this.get('isOpen') ? 'icon-minus' : 'icon-plus';
-  }.property('isOpen'),
-  openDetails: function(){
-    this.set('isOpen', !this.get('isOpen'))
-  },
-  showOperationLog:function(){
-    var operation = this.get('content');
-    var self = this;
-    if (!this.get('isOpenShowLog') && !this.get('logDetails')) {
-      jQuery.getJSON('data/hosts/background_operations/logs/task' +operation.taskId + '.json',
-        function (data) {
-          self.set('logDetails', data);
-        }
-      );
-    }
-    this.set('isOpenShowLog', !this.get('isOpenShowLog'))
-  }
-});
+//    willInsertElement: function() {
+//      this._super();
+//      console.warn("CONTENT_HOST_CHECKED:", this.get('content.checkedForHostFilter'), " THIS CHECKED: ", this.get('checked'));
+//    },
+//
+//    didInsertElement: function(){
+//      this._super();
+//      this.propertyDidChange('content.checkedForHostFilter');
+//      console.warn("CONTENT_HOST_CHECKED:", this.get('content.checkedForHostFilter'), " THIS CHECKED: ", this.get('checked'));
+//    }
+
+//    test: function(){
+//      console.warn("Test:", this.get('content.checkedForHostFilter'));
+//    }.observes("content.checkedForHostFilter")
+
+//    change:function (event) {
+//      var parent = this._parentView.templateData.view;
+//      var componentsIds = parent.get('componentsIds');
+//      var componentId = this.get('content.id');
+//      var index = componentsIds.indexOf(componentId);
+//      if (index == -1) componentsIds.push(componentId);
+//      else componentsIds.splice(index, 1);
+//    }
+  })
+});

+ 18 - 0
ambari-web/app/views/main/host/details.js

@@ -27,4 +27,22 @@ App.MainHostDetailsView = Em.View.extend({
     var options = [{action: 'deleteHost', 'label': 'Delete Host'}];
     return options;
   }.property('controller.content')
+});
+
+App.MainHostDetailsOperations = Em.View.extend({
+  content: null,
+  classNames: ['background-operations'],
+  classNameBindings: ['isOpen'],
+  isOpen: false,
+  logDetails: null,
+  isOpenShowLog: false,
+  iconClass: function(){
+    return this.get('isOpen') ? 'icon-minus' : 'icon-plus';
+  }.property('isOpen'),
+  openDetails: function(){
+    this.set('isOpen', !this.get('isOpen'))
+  },
+  showOperationLog:function(){
+    this.set('isOpenShowLog', !this.get('isOpenShowLog'))
+  }
 });

+ 4 - 1
ambari-web/app/views/main/host/summary.js

@@ -33,7 +33,10 @@ App.MainHostSummaryView = Em.View.extend({
     }.property('content.id') ,
     buttonClass: function() {
       return this.get('content.workStatus') ? 'btn btn-success dropdown-toggle' : 'btn btn-danger dropdown-toggle';
-    }.property('content.workStatus')
+    }.property('content.workStatus'),
+    isDataNode: function() {
+      return this.get('content.componentName') === 'DataNode';
+    }.property('content')
   })
 
 });

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

@@ -1,4 +1,4 @@
-/**
+  /**
  * 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

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

@@ -69,6 +69,14 @@ App.MainMenuView = Em.CollectionView.extend({
       }
     }.property(),
 
-    template:Ember.Handlebars.compile('<a href="#/main/{{unbound view.content.routing}}">{{unbound view.content.label}}{{#if view.alertsCount}}<span class="label label-important alerts-count">{{view.alertsCount}}</span>{{/if}}</a>')
+    hostDetailsOperationsCount:function () {
+      if (this.get('content').routing == 'hosts') {
+        if (App.router.currentState.parentState.name == 'hostDetails') {
+          return App.router.get('mainHostDetailsController.hostOperationsCount');
+        }
+      }
+    }.property('App.router.currentState.parentState.name', 'App.router.mainHostDetailsController.hostOperationsCount'),
+
+    templateName: require('templates/main/menu_item')
   })
 });

+ 19 - 0
ambari-web/app/views/main/service/info/summary.js

@@ -26,6 +26,24 @@ App.MainServiceInfoSummaryView = Em.View.extend({
     mapreduce: false,
     hbase: false
   },
+
+  alerts: function(){
+    var serviceId = this.get('service.id');
+    if(serviceId) {
+      return App.Alert.find({'service_id':serviceId });
+    }
+    return [];
+//    return App.router.get('mainServiceInfoSummaryController.content.alerts');
+  }.property('App.router.mainServiceInfoSummaryController.content.alerts'),
+
+  controller: function(){
+    return App.router.get('mainServiceInfoSummaryController');
+  }.property(),
+
+  service: function(){
+    return this.get('controller.content');
+  }.property('controller.content'),
+
   isHide: true,
   moreStatsView: Em.View.extend({
     tagName: "a",
@@ -56,6 +74,7 @@ App.MainServiceInfoSummaryView = Em.View.extend({
         summaryView.set('serviceStatus.' + key, false);
       }
     });
+
     jQuery.getJSON('data/services/summary/' + serviceName + '.json',
       function (data) {
         if (data[serviceName]) {

+ 166 - 94
ambari-web/app/views/installer/step7_view.js → ambari-web/app/views/wizard/controls_view.js

@@ -16,47 +16,21 @@
  * limitations under the License.
  */
 
-
 var App = require('app');
 
-App.InstallerStep7View = Em.View.extend({
-
-  templateName: require('templates/installer/step7'),
-
-  didInsertElement: function () {
-    var controller = this.get('controller');
-    controller.loadStep();
-  }
-
-});
-
-App.ServiceConfigsByCategoryView = Ember.View.extend({
-  viewName: 'serviceConfigs',
-  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()
-});
-
-App.ServiceConfigTab = Ember.View.extend({
-
-  tagName: 'li',
+/**
+ * Abstract view for config fields.
+ * Add popover support to control
+ */
+App.ServiceConfigPopoverSupport = Ember.Mixin.create({
 
-  selectService: function (event) {
-    this.set('controller.selectedService', event.context);
-  },
+  /**
+   * Config object. It will instance of App.ServiceConfigProperty
+   */
+  serviceConfig: null,
 
-  didInsertElement: function () {
-    var serviceName = this.get('controller.selectedService.serviceName');
-    this.$('a[href="#' + serviceName + '"]').tab('show');
-  }
-});
+  isPopoverEnabled: true,
 
-App.ServiceConfigPopoverSupport = Ember.Mixin.create({
   didInsertElement: function () {
     if (this.get('isPopoverEnabled') !== 'false') {
       this.$().popover({
@@ -69,10 +43,12 @@ App.ServiceConfigPopoverSupport = Ember.Mixin.create({
   }
 });
 
+/**
+ * Default input control
+ * @type {*}
+ */
 App.ServiceConfigTextField = Ember.TextField.extend(App.ServiceConfigPopoverSupport, {
 
-  serviceConfig: null,
-  isPopoverEnabled: true,
   valueBinding: 'serviceConfig.value',
   classNameBindings: 'textFieldClassName',
 
@@ -91,8 +67,11 @@ App.ServiceConfigTextField = Ember.TextField.extend(App.ServiceConfigPopoverSupp
 
 });
 
+/**
+ * Customized input control with Utits type specified
+ * @type {*}
+ */
 App.ServiceConfigTextFieldWithUnit = Ember.View.extend(App.ServiceConfigPopoverSupport, {
-  serviceConfig: null,
   valueBinding: 'serviceConfig.value',
   classNames: [ 'input-append' ],
 
@@ -104,7 +83,12 @@ App.ServiceConfigTextFieldWithUnit = Ember.View.extend(App.ServiceConfigPopoverS
 
 });
 
+/**
+ * Password control
+ * @type {*}
+ */
 App.ServiceConfigPasswordField = Ember.TextField.extend({
+
   serviceConfig: null,
   type: 'password',
   valueBinding: 'serviceConfig.value',
@@ -121,9 +105,12 @@ App.ServiceConfigPasswordField = Ember.TextField.extend({
 
 });
 
+/**
+ * Textarea control
+ * @type {*}
+ */
 App.ServiceConfigTextArea = Ember.TextArea.extend(App.ServiceConfigPopoverSupport, {
 
-  serviceConfig: null,
   valueBinding: 'serviceConfig.value',
   rows: 4,
   classNames: ['span6'],
@@ -134,13 +121,20 @@ App.ServiceConfigTextArea = Ember.TextArea.extend(App.ServiceConfigPopoverSuppor
 
 });
 
+/**
+ * Textarea control with bigger height
+ * @type {*}
+ */
 App.ServiceConfigBigTextArea = App.ServiceConfigTextArea.extend({
   rows: 10
 });
 
+/**
+ * Checkbox control
+ * @type {*}
+ */
 App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSupport, {
 
-  serviceConfig: null,
   checkedBinding: 'serviceConfig.value',
 
   disabled: function () {
@@ -149,7 +143,6 @@ App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSuppor
 
 });
 
-
 <!-- {{bindAttr name="view.name" value="option"}}  '<input type="radio" {{bindAttr name = "view.name" value="view.obj"}}>',-->
 App.ServiceConfigRadioButtons = Ember.View.extend({
   template: Ember.Handlebars.compile([
@@ -208,7 +201,17 @@ App.ServiceConfigComboBox = Ember.Select.extend(App.ServiceConfigPopoverSupport,
   classNames: [ 'span3' ]
 });
 
+
+/**
+ * Base component for host config with popover support
+ */
 App.ServiceConfigHostPopoverSupport = Ember.Mixin.create({
+
+  /**
+   * Config object. It will instance of App.ServiceConfigProperty
+   */
+  serviceConfig: null,
+
   didInsertElement: function () {
     this.$().popover({
       title: this.get('serviceConfig.displayName'),
@@ -219,9 +222,13 @@ App.ServiceConfigHostPopoverSupport = Ember.Mixin.create({
   }
 });
 
+/**
+ * Master host component.
+ * Show hostname without ability to edit it
+ * @type {*}
+ */
 App.ServiceConfigMasterHostView = Ember.View.extend(App.ServiceConfigHostPopoverSupport, {
 
-  serviceConfig: null,
   classNames: ['master-host', 'span6'],
   valueBinding: 'serviceConfig.value',
 
@@ -229,9 +236,18 @@ App.ServiceConfigMasterHostView = Ember.View.extend(App.ServiceConfigHostPopover
 
 });
 
+/**
+ * Base component to display Multiple hosts
+ * @type {*}
+ */
 App.ServiceConfigMultipleHostsDisplay = Ember.Mixin.create(App.ServiceConfigHostPopoverSupport, {
 
   hasNoHosts: function () {
+    console.log('view', this.get('viewName')); //to know which View cause errors
+    console.log('controller', this.get('controller').name); //should be slaveComponentGroupsController
+    if(!this.get('value')){
+      debugger;
+    }
     return this.get('value').length === 0;
   }.property('value'),
 
@@ -254,29 +270,67 @@ App.ServiceConfigMultipleHostsDisplay = Ember.Mixin.create(App.ServiceConfigHost
 
 })
 
+
+/**
+ * Multiple master host component.
+ * Show hostnames without ability to edit it
+ * @type {*}
+ */
 App.ServiceConfigMasterHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
 
+  viewName : "serviceConfigMasterHostsView",
   valueBinding: 'serviceConfig.value',
 
   classNames: ['master-hosts', 'span6'],
-  templateName: require('templates/installer/master_hosts')
+  templateName: require('templates/wizard/master_hosts'),
+
+  /**
+   * Onclick handler for link
+   */
+  showHosts: function () {
+    var serviceConfig = this.get('serviceConfig');
+    App.ModalPopup.show({
+      header: serviceConfig.  category + ' Hosts',
+      bodyClass: Ember.View.extend({
+        serviceConfig: serviceConfig,
+        templateName: require('templates/wizard/master_hosts_popup')
+      }),
+      onPrimary:function(){
+        this.hide();
+      }
+    });
+  }
 
 });
 
-//App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
-//
-//  classNames: ['slave-hosts', 'span6'],
-//  templateName: require('templates/installer/slave_hosts'),
-//
-//  controllerBinding: 'App.router.slaveComponentGroupsController',
-//  valueBinding: 'App.router.slaveComponentGroupsController.hosts',
-//
-//  disabled: function () {
-//    return !this.get('serviceConfig.isEditable');
-//  }.property('serviceConfig.isEditable')
-//
-//});
+/**
+ * Show tabs list for slave hosts
+ * @type {*}
+ */
+App.SlaveComponentGroupsMenu = Em.CollectionView.extend({
+
+  content: function(){
+    return this.get('controller.componentGroups');
+  }.property('controller.componentGroups'),
+
+  tagName:'ul',
+  classNames: ["nav", "nav-tabs"],
+
+  itemViewClass:Em.View.extend({
+    classNameBindings:["active"],
 
+    active:function(){
+      return this.get('content.active');
+    }.property('content.active'),
+
+    template:Ember.Handlebars.compile('<a {{action showSlaveComponentGroup view.content target="controller"}} href="#"> {{view.content.name}}</a><i {{action removeSlaveComponentGroup view.content target="controller"}} class="icon-remove"></i>')
+  })
+});
+
+/**
+ * <code>Add group</code> button
+ * @type {*}
+ */
 App.AddSlaveComponentGroupButton = Ember.View.extend({
 
   tagName: 'span',
@@ -291,72 +345,90 @@ App.AddSlaveComponentGroupButton = Ember.View.extend({
       trigger: 'hover'
     });
   }
-});
-
-App.SlaveComponentGroupsMenu = Em.CollectionView.extend({
-  controllerBinding: 'App.router.slaveComponentGroupsController',
-  content: function () {
-    return this.get('controller.componentGroups');
-  }.property('controller.componentGroups'),
-  tagName: 'ul',
-  classNames: ["nav", "nav-tabs"],
 
-  itemViewClass: Em.View.extend({
-    classNameBindings: ["active"],
-    active: function () {
-      return this.get('content.active');
-    }.property('content.active'),
-    template: Ember.Handlebars.compile('<a {{action showSlaveComponentGroup view.content target="controller"}} href="#"> {{view.content.name}}</a><i {{action removeSlaveComponentGroup view.content target="controller"}} class="icon-remove"></i>')  })
 });
 
+/**
+ * Multiple Slave Hosts component
+ * @type {*}
+ */
 App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, {
+
+  viewName : 'serviceConfigSlaveHostsView',
+
   classNames: ['slave-hosts', 'span6'],
-  controllerBinding: 'App.router.slaveComponentGroupsController',
   valueBinding: 'hosts',
-  group: function () {
+
+  group: function(){
     return this.get('controller.activeGroup');
   }.property('controller.activeGroup'),
-  hosts: function () {
-    if (this.get('group') !== undefined)
+
+  hosts: function(){
+    if (this.get('group')){
       return this.get('controller').getHostsByGroup(this.get('group'))
+    }
   }.property('controller.hosts.@each.group', 'group'),
-  templateName: require('templates/installer/slave_component_hosts'),
+
+  templateName: require('templates/wizard/slave_component_hosts'),
+
   disabled: function () {
     return !this.get('serviceConfig.isEditable');
   }.property('serviceConfig.isEditable')
 });
 
+/**
+ * DropDown component for <code>select hosts for groups</code> popup
+ * @type {*}
+ */
 App.SlaveComponentDropDownGroupView = Ember.View.extend({
-  controllerBinding: 'App.router.slaveComponentGroupsController',
+
+  viewName : "slaveComponentDropDownGroupView",
+
+  /**
+   * On change handler for <code>select hosts for groups</code> popup
+   * @param event
+   */
+  changeGroup: function(event) {
+    var host = this.get('content');
+    var groupName = $('#'+this.get('elementId') + ' select').val();
+    this.get('controller').changeHostGroup(host, groupName);
+  },
+
   optionTag: Ember.View.extend({
-    selected: function () {
-      var parent = this._parentView.templateData.view;
-      return parent.get('content.group') === this.get('content');
-    }.property('content'),
-    changeGroup: function (event) {
-      var parent = this._parentView.templateData.view;
-      var groupName = this.get('content');
-      var host = parent.get('content');
-      parent.get('controller').changeHostGroup(host, groupName);
-    }
+
+    /**
+     * Whether current value(OptionTag value) equals to host value(assigned to SlaveComponentDropDownGroupView.content)
+     */
+    selected: function(){
+      return this.get('parentView.content.group') === this.get('content');
+    }.property('content')
   })
 });
 
+/**
+ * Show info about current group
+ * @type {*}
+ */
 App.SlaveComponentChangeGroupNameView = Ember.View.extend({
-  controllerBinding: 'App.router.slaveComponentGroupsController',
+
   contentBinding: 'controller.activeGroup',
   classNames: ['control-group'],
   classNameBindings: 'error',
   error: false,
-  setError: function () {
+  setError: function(){
     this.set('error', false);
   }.observes('controller.activeGroup'),
-  errorMessage: function () {
+  errorMessage: function(){
     return this.get('error') ? 'group with this name already exist' : '';
   }.property('error'),
-  changeGroupName: function (event) {
-    var inputVal = $('#' + this.get('elementId') + ' input[type="text"]').val();
-    if (inputVal !== this.get('content.name')) {
+
+  /**
+   * Onclick handler for saving updated group name
+   * @param event
+   */
+  changeGroupName: function(event) {
+    var inputVal = $('#'+this.get('elementId') + ' input[type="text"]').val();
+    if (inputVal !== this.get('content.name')){
       var result = this.get('controller').changeSlaveGroupName(this.get('content'), inputVal);
       this.set('error', result);
     }

+ 34 - 0
ambari-web/app/views/wizard/step1_view.js

@@ -0,0 +1,34 @@
+/**
+ * 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.WizardStep1View = Em.View.extend({
+
+  templateName: require('templates/wizard/step1'),
+
+  didInsertElement: function () {
+    $("[rel=popover]").popover({'placement': 'right', 'trigger': 'hover'});
+  },
+
+  onError: function () {
+    return this.get('controller.clusterNameError') !== '';
+  }.property('controller.clusterNameError')
+
+});

+ 65 - 0
ambari-web/app/views/wizard/step7_view.js

@@ -0,0 +1,65 @@
+/**
+ * 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.WizardStep7View = Em.View.extend({
+
+  templateName: require('templates/wizard/step7'),
+
+  didInsertElement: function () {
+    var controller = this.get('controller');
+    controller.loadStep();
+  }
+
+});
+
+/**
+ * Since we need to use local Views and Controllers we should put them into separate context
+ * @type {*|Object}
+ */
+App.WizardStep7 = App.WizardStep7 || {};
+
+App.WizardStep7.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>')
+});
+
+App.WizardStep7.ServiceConfigTab = Ember.View.extend({
+
+  tagName: 'li',
+
+  selectService: function (event) {
+    this.set('controller.selectedService', event.context);
+  },
+
+  didInsertElement: function () {
+    var serviceName = this.get('controller.selectedService.serviceName');
+    this.$('a[href="#' + serviceName + '"]').tab('show');
+  }
+});

+ 3 - 3
ambari-web/app/views/installer/step8_view.js → ambari-web/app/views/wizard/step8_view.js

@@ -19,12 +19,12 @@
 
 var App = require('app');
 
-App.InstallerStep8View = Em.View.extend({
+App.WizardStep8View = Em.View.extend({
 
-  templateName: require('templates/installer/step8'),
+  templateName: require('templates/wizard/step8'),
 
   didInsertElement: function () {
     var controller = this.get('controller');
     controller.loadStep();
   }
-});
+});

+ 2 - 2
ambari-web/app/views/installer/step9_view.js → ambari-web/app/views/wizard/step9_view.js

@@ -19,9 +19,9 @@
 
 var App = require('app');
 
-App.InstallerStep9View = Em.View.extend({
+App.WizardStep9View = Em.View.extend({
 
-  templateName: require('templates/installer/step9'),
+  templateName: require('templates/wizard/step9'),
   barColor: '',
   resultMsg: '',
   resultMsgColor: '',

+ 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);

+ 2 - 2
ambari-web/vendor/styles/jquery-ui-bootstrap/jquery-ui-1.8.16.custom.css

@@ -1079,9 +1079,9 @@ textarea,
 .uneditable-input {
   display: inline-block;
   padding: 4px;
-  font-size: 13px;
+  /* font-size: 13px; */
   line-height: 18px;
-  color: #808080;
+  /* color: #808080; */
   border: 1px solid #ccc;
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;