소스 검색

AMBARI-802. Enhance Cluster Management pages. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1393835 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 년 전
부모
커밋
8c63c5a7d4
70개의 변경된 파일1982개의 추가작업 그리고 827개의 파일을 삭제
  1. 2 0
      AMBARI-666-CHANGES.txt
  2. 1 0
      ambari-web/app/controllers.js
  3. 0 0
      ambari-web/app/controllers/installer/step3.js
  4. 22 9
      ambari-web/app/controllers/login_controller.js
  5. 2 0
      ambari-web/app/controllers/main.js
  6. 4 1
      ambari-web/app/controllers/main/admin.js
  7. 2 0
      ambari-web/app/controllers/main/admin/advanced.js
  8. 13 9
      ambari-web/app/controllers/main/admin/authentication.js
  9. 2 0
      ambari-web/app/controllers/main/admin/item.js
  10. 9 1
      ambari-web/app/controllers/main/admin/user.js
  11. 2 0
      ambari-web/app/controllers/main/admin/user/edit.js
  12. 2 0
      ambari-web/app/controllers/main/alert.js
  13. 3 1
      ambari-web/app/controllers/main/dashboard.js
  14. 27 20
      ambari-web/app/controllers/main/host.js
  15. 57 18
      ambari-web/app/controllers/main/host/details.js
  16. 2 0
      ambari-web/app/controllers/main/service.js
  17. 3 12
      ambari-web/app/controllers/main/service/info/audit.js
  18. 2 0
      ambari-web/app/controllers/main/service/info/configs.js
  19. 2 0
      ambari-web/app/controllers/main/service/info/metrics.js
  20. 2 0
      ambari-web/app/controllers/main/service/info/summary.js
  21. 3 1
      ambari-web/app/controllers/main/service/item.js
  22. 245 0
      ambari-web/app/data/mock/service_components.js
  23. 43 42
      ambari-web/app/messages.js
  24. 41 19
      ambari-web/app/models/authentication.js
  25. 4 4
      ambari-web/app/models/cluster.js
  26. 30 34
      ambari-web/app/models/form.js
  27. 3 2
      ambari-web/app/models/pagination.js
  28. 13 9
      ambari-web/app/models/service.js
  29. 12 6
      ambari-web/app/models/service_audit.js
  30. 45 4
      ambari-web/app/models/user.js
  31. 10 1
      ambari-web/app/router.js
  32. 43 69
      ambari-web/app/routes/main.js
  33. 10 55
      ambari-web/app/styles/app.css
  34. 118 6
      ambari-web/app/styles/application.less
  35. 24 0
      ambari-web/app/templates/common/grid/filter.hbs
  36. 5 1
      ambari-web/app/templates/common/grid/header.hbs
  37. 24 0
      ambari-web/app/templates/common/grid/pager.hbs
  38. 2 2
      ambari-web/app/templates/installer/step3.hbs
  39. 2 0
      ambari-web/app/templates/main/admin.hbs
  40. 10 12
      ambari-web/app/templates/main/admin/audit.hbs
  41. 2 3
      ambari-web/app/templates/main/admin/authentication.hbs
  42. 1 1
      ambari-web/app/templates/main/admin/security.hbs
  43. 1 1
      ambari-web/app/templates/main/admin/user.hbs
  44. 5 3
      ambari-web/app/templates/main/admin/user/edit.hbs
  45. 10 8
      ambari-web/app/templates/main/dashboard.hbs
  46. 115 106
      ambari-web/app/templates/main/host.hbs
  47. 22 20
      ambari-web/app/templates/main/host/details.hbs
  48. 36 20
      ambari-web/app/templates/main/host/summary.hbs
  49. 0 148
      ambari-web/app/templates/main/hosts.hbs
  50. 0 51
      ambari-web/app/templates/main/service/info/audit.hbs
  51. 2 8
      ambari-web/app/templates/main/service/info/summary.hbs
  52. 15 0
      ambari-web/app/utils/db.js
  53. 13 1
      ambari-web/app/utils/helper.js
  54. 16 1
      ambari-web/app/utils/validator.js
  55. 309 0
      ambari-web/app/views/common/grid.js
  56. 0 0
      ambari-web/app/views/installer/step1.js
  57. 0 0
      ambari-web/app/views/installer/step2.js
  58. 0 0
      ambari-web/app/views/installer/step3.js
  59. 13 83
      ambari-web/app/views/main/admin/audit.js
  60. 13 5
      ambari-web/app/views/main/admin/menu.js
  61. 68 12
      ambari-web/app/views/main/host.js
  62. 7 3
      ambari-web/app/views/main/host/summary.js
  63. 51 3
      ambari-web/app/views/main/service/info/audit.js
  64. 13 12
      ambari-web/test/login_test.js
  65. 34 0
      ambari-web/test/main/dashboard_test.js
  66. 49 0
      ambari-web/test/main/host/details_test.js
  67. 84 0
      ambari-web/test/main/host_test.js
  68. 32 0
      ambari-web/test/main/item_test.js
  69. 164 0
      ambari-web/test/utils/form_field_test.js
  70. 61 0
      ambari-web/test/utils/validator_test.js

+ 2 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,8 @@ AMBARI-666 branch (unreleased changes)
 
   NEW FEATURES
 
+  AMBARI-802. Enhance Cluster Management pages. (yusaku)
+
   AMBARI-800. Hack to add a stage for testing in in-memory db. (jitendra)
 
   AMBARI-801. Fix heartbeat message from the agent which is causing NPE at the

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

@@ -26,6 +26,7 @@ require('controllers/installer/step1_controller');
 require('controllers/installer/step2_controller');
 require('controllers/installer/step3_controller');
 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');

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


+ 22 - 9
ambari-web/app/controllers/login_controller.js

@@ -20,29 +20,42 @@ var App = require('app');
 
 App.LoginController = Em.Object.extend({
 
-  name: 'loginController',
-  loginName: '',
-  password: '',
-  errorMessage: '',
+  name:'loginController',
+  loginName:'',
+  password:'',
+  errorMessage:'',
 
   submit: function (e) {
     console.log('Login: ' + this.get('loginName') + ' Password: ' + this.get('password'));
 
     this.set('errorMessage', '');
 
-    if (this.validateCredentials()) {
-      console.log('Logging in as: ' + this.get('loginName'));
-      App.get('router').login(this.get('loginName'));
+    var user = this.validateCredentials();
+    if (user) {
+      App.get('router').login(this.get('loginName'), user);
     } else {
       console.log('Failed to login as: ' + this.get('loginName'));
       this.set('errorMessage', Em.I18n.t('login.error'));
     }
-
   },
 
+  /**
+   *
+   * @return {number} user by credentials || {undefined}
+   */
   validateCredentials: function () {
     //TODO: REST api that validates the login
-    return (this.get('loginName').trim() !== '' && this.get('loginName') === this.get('password'));
+    var thisController = this;
+
+    var user = App.store.filter(App.User, function (data) {
+      return data.get('user_name') == thisController.get('loginName') && data.get('password') == thisController.get('password');
+    });
+
+    var clientId = user.content[0];
+
+    if (user.content[0] !== undefined) {
+      return App.store.findByClientId(App.User, clientId);
+    }
   }
 
 });

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

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainController = Em.Controller.extend({
   name: 'mainController'
 })

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

@@ -16,6 +16,9 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminController = Em.Controller.extend({
-  name:'mainAdminController'
+  name:'mainAdminController',
+  category:'user'
 })

+ 2 - 0
ambari-web/app/controllers/main/admin/advanced.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminAdvancedController = Em.Controller.extend({
   name:'mainAdminAdvancedController',
   uninstall: function(event){

+ 13 - 9
ambari-web/app/controllers/main/admin/authentication.js

@@ -16,19 +16,23 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminAuthenticationController = Em.Controller.extend({
   name:'mainAdminAuthenticationController',
   save:function (event) {
     var form = event.context;
-    form.save();
-    App.ModalPopup.show({
-      header: Em.I18n.t('admin.authentication.form.testConfiguration'),
-      body:form.get('resultText'),
-      secondary:false,
-      onPrimary: function(){
-        this.hide();
-      }
-    });
+    if (form.isValid()) {
+      form.save();
+      App.ModalPopup.show({
+        header:Em.I18n.t('admin.authentication.form.testConfiguration'),
+        body:form.get('resultText'),
+        secondary:false,
+        onPrimary:function () {
+          this.hide();
+        }
+      });
+    }
   },
   content:App.Authentication.find(1)
 })

+ 2 - 0
ambari-web/app/controllers/main/admin/item.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminItemController = Em.Controller.extend({
   name:'mainAdminItemController'
 })

+ 9 - 1
ambari-web/app/controllers/main/admin/user.js

@@ -16,10 +16,18 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminUserController = Em.Controller.extend({
   name:'mainAdminUserController',
   deleteRecord:function (event) {
-    if (Em.I18n.t('question.sure')) {
+
+    if (event.context.get('userName') == App.get('router').getLoginName()) {
+      alert(Em.I18n.t('admin.users.deleteYourselfMessage'));
+      return;
+    }
+
+    if (confirm(Em.I18n.t('question.sure'))) {
       event.context.deleteRecord();
       App.store.commit();
     }

+ 2 - 0
ambari-web/app/controllers/main/admin/user/edit.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAdminUserEditController = Em.Controller.extend({
   name:'mainAdminUserEditController',
   content:false

+ 2 - 0
ambari-web/app/controllers/main/alert.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainAlertController = Em.ArrayController.extend({
   name:'mainAlertController'
 })

+ 3 - 1
ambari-web/app/controllers/main/dashboard.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainDashboardController = Em.Controller.extend({
   name:'mainDashboardController',
   alerts: App.Alert.find(),
@@ -33,6 +35,6 @@ App.MainDashboardController = Em.Controller.extend({
     }
   },
   alertsCount: function() {
-    return this.alerts.get('content').length;
+    return this.alerts.filterProperty('status', 'corrupt').length;
   }.property()
 })

+ 27 - 20
ambari-web/app/controllers/main/host.js

@@ -16,13 +16,14 @@
  * limitations under the License.
  */
 
+var App = require('app');
 
 App.MainHostController = Em.ArrayController.extend(App.Pagination, {
   name:'mainHostController',
   content: [],
   fullContent: App.Host.find(),
   clusters: App.Cluster.find(),
-  allComponents: App.Component.find(),
+  componentsForFilter: App.Component.find(),
   totalBinding: 'fullContent.length',
   filters: {components:[]},
   pageSize: 3,
@@ -31,7 +32,10 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
   allChecked: false,
   selectedHostsIds: [],
   sortingAsc: true,
-
+  isSort: false,
+  sortClass: function(){
+    return this.get('sortingAsc')? 'icon-arrow-down' : 'icon-arrow-up';
+  }.property('sortingAsc'),
   isDisabled:true,
 
   onAllChecked: function () {
@@ -42,11 +46,12 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
     this.set('selectedHostsIds', selectedHostsIds);
   }.observes('allChecked'),
 
-  onHostChecked: function (checked, hostId) {
+  onHostChecked: function (host) {
     var selected = this.get('selectedHostsIds');
-    if (checked) selected.push(hostId);
+    host.set('isChecked', !host.get('isChecked'));
+    if (host.get('isChecked')) selected.push(host.get('id'));
     else {
-      var index = selected.indexOf(hostId);
+      var index = selected.indexOf(host.get('id'));
       if(index!=-1) selected.splice(index, 1);
     }
     this.set('isDisabled', selected.length == 0);
@@ -63,19 +68,20 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
     this.set('selectedHostsIds', selectedHosts.getEach('id'));
   },
 
-  setFilters: function(checked, componentId) {
-    var filters = this.get('filters.components');
-    if (checked){
-      filters.push(componentId);
-    } else {
-      var index = filters.indexOf(componentId);
-      if(index!=-1) filters.splice(index, 1);
-    }
+  filterByComponentsIds: function(componentsIds) {
+    this.set('filters.components', componentsIds);
+    this.get('componentsForFilter').forEach(function(component) {
+      if (componentsIds.indexOf(component.get('id')) == -1){
+        component.set('isChecked', false);
+      } else component.set('isChecked', true);
+    });
     this.changeContent();
   },
 
-  filterByComponentId: function(componentId) {
-    this.set('filters.components', [componentId]);
+  filterByComponent: function(component) {
+    this.get('componentsForFilter').setEach('isChecked', false);
+    component.set('isChecked', true);
+    this.set('filters.components', [component.get('id')]);
     this.changeContent();
   },
 
@@ -84,10 +90,10 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
     var filters = this.get('filters.components');
     if (filters.length){
       this.get('fullContent').forEach(function(item) {
-        var inFilters = true;
-        $.each(filters, function (i, componentId) {
-          if (item.get('components').getEach('id').indexOf(componentId) == -1){
-            inFilters = false;
+        var inFilters = false;
+        item.get('components').forEach(function(component) {
+          if (filters.indexOf(component.get('id')) != -1){
+            inFilters = true;
           }
         });
         if (inFilters){
@@ -102,7 +108,7 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
     var content = items.slice(this.get('rangeStart'), this.get('rangeStop'));
     this.replace(0, this.get('length'), content);
     this.changeSelectedHosts();
-  }.observes('rangeStart', 'rangeStop', 'filters.components', 'total'),
+  }.observes('rangeStart', 'rangeStop', 'total'),
 
   showNextPage: function() {
     this.nextPage();
@@ -187,6 +193,7 @@ App.MainHostController = Em.ArrayController.extend(App.Pagination, {
       return 0;
     });
     this.set('fullContent', objects);
+    this.set('isSort', true);
     this.set('sortingAsc', !this.get('sortingAsc'));
     this.changeContent();
   }

+ 57 - 18
ambari-web/app/controllers/main/host/details.js

@@ -16,25 +16,26 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainHostDetailsController = Em.Controller.extend({
   name: 'mainHostDetailsController',
   content: null,
-  startComponents: function(){
+
+  isFromHosts: false,
+
+  isStarting: function(){
     return this.get('content.workStatus');
   }.property('content.workStatus'),
-  stopComponents: function(){
-    return !this.get('startComponents');
-  }.property('startComponents'),
-  changeWorkStatus: function(){
-    if (this.get('startComponents')) {
-      this.set('iconClass', 'play');
-    } else {
-      this.set('iconClass', 'stop');
-    }
-  }.observes('startComponents'),
-  iconClass: '',
+  isStopping: function(){
+    return !this.get('isStarting');
+  }.property('isStarting'),
 
-  startConfirmPopup: function (event) {
+  setBack: function(isFromHosts){
+    this.set('isFromHosts', isFromHosts);
+  },
+
+  startComponent: function(event){
     var self = this;
     App.ModalPopup.show({
       header: Em.I18n.t('hosts.host.start.popup.header'),
@@ -42,11 +43,50 @@ App.MainHostDetailsController = Em.Controller.extend({
       primary: 'Yes',
       secondary: 'No',
       onPrimary: function() {
-        console.log(self.get('content.components').getEach('workStatus'));
-        self.get('content.components').setEach('workStatus', self.get('content.workStatus'));
+        var component = event.context;
+        component.set('workStatus', true);
+        var stopped = self.get('content.components').filterProperty('workStatus', false);
+        if (stopped.length == 0)
+          self.set('content.workStatus', true);
+        this.hide();
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    });
+  },
+  stopComponent: function(event){
+    var self = this;
+    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() {
+        var component = event.context;
+        component.set('workStatus', false);
+        var started = self.get('content.components').filterProperty('workStatus', true);
+        if (started.length == 0)
+          self.set('content.workStatus', false);
+        this.hide();
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    });
 
-        self.set('content.workStatus', !self.get('content.workStatus'));
+  },
 
+  startConfirmPopup: function (event) {
+    var self = this;
+    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() {
+        self.get('content.components').setEach('workStatus', true);
+        self.set('content.workStatus', !self.get('content.workStatus'));
         this.hide();
       },
       onSecondary: function() {
@@ -62,8 +102,7 @@ App.MainHostDetailsController = Em.Controller.extend({
       primary: 'Yes',
       secondary: 'No',
       onPrimary: function() {
-        console.log(self.get('content.components').getEach('workStatus'));
-        self.get('content.components').setEach('workStatus', self.get('content.workStatus'));
+        self.get('content.components').setEach('workStatus', false);
         self.set('content.workStatus', !self.get('content.workStatus'));
         this.hide();
       },

+ 2 - 0
ambari-web/app/controllers/main/service.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceController = Em.ArrayController.extend({
   name:'mainServiceController',
   content: App.Service.find()

+ 3 - 12
ambari-web/app/controllers/main/service/info/audit.js

@@ -16,17 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceInfoAuditController = Em.Controller.extend({
-  name: 'mainServiceInfoAuditController',
-  users: App.User.find(),
-  performedBy: function() {
-    var auditItems = this.content.get('serviceAudit');
-    var itemUsers = auditItems.getEach('user');
-    var result = [];
-    this.users.forEach(function(item, index, self) {
-      if (res = itemUsers.findProperty ('id', item.get('id')))
-        result.push(res);
-    });
-    return result;
-  }.property('content')
+  name: 'mainServiceInfoAuditController'
 })

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

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceInfoConfigsController = Em.Controller.extend({
   name: 'mainServiceInfoConfigsController',
   content: 'Configs'

+ 2 - 0
ambari-web/app/controllers/main/service/info/metrics.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceInfoMetricsController = Em.Controller.extend({
   name: 'mainServiceInfoMetricsController',
   content: 'Metrics'

+ 2 - 0
ambari-web/app/controllers/main/service/info/summary.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceInfoSummaryController = Em.Controller.extend({
   name: 'mainServiceInfoSummaryController'
 })

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

@@ -16,9 +16,11 @@
  * limitations under the License.
  */
 
+var App = require('app');
+
 App.MainServiceItemController = Em.Controller.extend({
   name: 'mainServiceItemController',
-//  content: App.Service.find(1),
+  content: App.Service.find(1),
   showRebalancer: function() {
     if(this.content.get('serviceName') == 'hdfs') {
       return true;

+ 245 - 0
ambari-web/app/data/mock/service_components.js

@@ -0,0 +1,245 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module.exports = new Ember.Set([
+
+      {
+        service_name: 'HDFS',
+        component_name: 'NAMENODE',
+        display_name: 'NameNode',
+        isMaster: true,
+        isClient: false,
+        description: 'Master server that manages the file system namespace and regulates access to files by clients'
+      },
+      {
+        service_name: 'HDFS',
+        component_name: 'SNAMENODE',
+        display_name: 'SNameNode',
+        isMaster: true,
+        isClient: false,
+        description: 'Helper to the primary NameNode that is responsible for supporting periodic checkpoints of the HDFS metadata'
+      },
+      {
+        service_name: 'HDFS',
+        component_name: 'DATANODE',
+        display_name: 'Datanode',
+        isMaster: false,
+        isClient: false,
+        description: 'The slave for HDFS'
+      },
+      {
+        service_name: 'HDFS',
+        component_name: 'HDFS_CLIENT',
+        display_name: 'HDFS Client',
+        isMaster: false,
+        isClient: true,
+        description: 'Client component for HDFS'
+      },
+      {
+        service_name: 'MAPREDUCE',
+        component_name: 'JOBTRACKER',
+        display_name: 'JobTracker',
+        isMaster: true,
+        isClient: false,
+        description: 'Central Master service that pushes work (MR tasks) out to available TaskTracker nodes in the cluster'
+      },
+      {
+        service_name: 'MAPREDUCE',
+        component_name: 'TASKTRACKER',
+        display_name: 'TaskTracker',
+        isMaster: false,
+        isClient: false,
+        description: 'The slave for MapReduce'
+      },
+      {
+        service_name: 'MAPREDUCE',
+        component_name: 'MAPREDUCE_CLIENT',
+        display_name: 'MapReduce Client',
+        isMaster: false,
+        isClient: true,
+        description: 'Client component for MapReduce'
+      },
+      {
+        service_name: 'ZOOKEEPER',
+        component_name: 'ZOOKEEPER_SERVER',
+        display_name: 'ZooKeeper',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'ZOOKEEPER',
+        component_name: 'ZOOKEEPER_CLIENT',
+        display_name: 'ZooKeeper Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'HBASE',
+        component_name: 'HBASE_MASTER',
+        display_name: 'HBase Master',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'HBASE',
+        component_name: 'HBASE_REGIONSERVER',
+        display_name: 'HBase Region Server',
+        isMaster: false,
+        isClient: false,
+        description: 'The slave for HBase'
+      },
+      {
+        service_name: 'HBASE',
+        component_name: 'HBASE_CLIENT',
+        display_name: 'HBase Client',
+        isMaster: false,
+        isClient: true,
+        description: 'The slave for HBase'
+      },
+      {
+        service_name: 'PIG',
+        component_name: 'PIG_CLIENT',
+        display_name: 'Pig Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'SQOOP',
+        component_name: 'SQOOP_CLIENT',
+        display_name: 'Sqoop Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'OOZIE',
+        component_name: 'OOZIE_SERVER',
+        display_name: 'Oozie Server',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'OOZIE',
+        component_name: 'OOZIE_CLIENT',
+        display_name: 'Oozie Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'HIVE',
+        component_name: 'HIVE_SERVER',
+        display_name: 'Hive Metastore',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'HIVE',
+        component_name: 'HIVE_CLIENT',
+        display_name: 'Hive Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'HIVE',
+        component_name: 'HIVE_MYSQL',
+        display_name: 'MySql Server for Hive',
+        isMaster: false,
+        isClient: false,
+        description: 'The slave for HBase'
+      },
+      {
+        service_name: 'TEMPLETON',
+        component_name: 'TEMPLETON_SERVER',
+        display_name: 'Templeton Server',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'TEMPLETON',
+        component_name: 'TEMPLETON_CLIENT',
+        display_name: 'Templeton Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'DASHBOARD',
+        component_name: 'DASHBOARD',
+        display_name: 'Monitoring Dashboard',
+        isMaster: false,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'NAGIOS',
+        component_name: 'NAGIOS_SERVER',
+        display_name: 'Nagios Server',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'GANGLIA',
+        component_name: 'GANGLIA_MONITOR_SERVER',
+        display_name: 'Ganglia Collector',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'GANGLIA',
+        component_name: 'GANGLIA_MONITOR',
+        display_name: 'Ganglia Slave',
+        isMaster: false,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'KERBEROS',
+        component_name: 'KERBEROS_SERVER',
+        display_name: 'Kerberos Server',
+        isMaster: true,
+        isClient: false,
+        description: ''
+      },
+      {
+        service_name: 'KERBEROS',
+        component_name: 'KERBEROS_ADMIN_CLIENT',
+        display_name: 'Kerberos Admin Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      },
+      {
+        service_name: 'KERBEROS',
+        component_name: 'KERBEROS_CLIENT',
+        display_name: 'Kerberos Client',
+        isMaster: false,
+        isClient: true,
+        description: ''
+      }
+]);

+ 43 - 42
ambari-web/app/messages.js

@@ -42,8 +42,7 @@ Em.I18n.translations = {
   'installer.header': 'Cluster Install Wizard',
   'installer.step1.header': 'Welcome',
   'installer.step1.body.header': 'Welcome to Apache Ambari!',
-  'installer.step1.body':
-    'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
+  'installer.step1.body': 'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
     'We will walk you through the cluster installation process with this step-by-step wizard.',
   'installer.step1.clusterName': 'Name your cluster',
   'installer.step1.clusterName.tooltip.title': 'Cluster Name',
@@ -115,54 +114,57 @@ Em.I18n.translations = {
   'installer.step9.host.status.success': 'success',
   'installer.step9.host.status.warning': 'tolerable failures encountered',
   'installer.step9.host.status.failed': 'failures encountered',
+
   'installer.step10.header': 'Summary',
 
   'form.create': 'Create',
   'form.save': 'Save',
   'form.cancel': 'Cancel',
-  'form.password':'Password',
-  'form.passwordRetype':'Retype Password',
-  'form.saveSuccess':'Successfully saved.',
-  'form.saveError':'Sorry, errors occured.',
+  'form.password': 'Password',
+  'form.passwordRetype': 'Retype Password',
+  'form.saveSuccess': 'Successfully saved.',
+  'form.saveError': 'Sorry, errors occured.',
 
-  'form.validator.invalidIp':'Please enter valid ip address',
+  'form.validator.invalidIp': 'Please enter valid ip address',
 
-  'admin.advanced.title':'Advanced',
-  'admin.advanced.caution':'This section is for advanced user only.<br/>Proceed with caution.',
-  'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including all data.',
-  'admin.advanced.button.uninstallKeepData':'Uninstall cluster but keep data.',
+  'admin.advanced.title': 'Advanced',
+  'admin.advanced.caution': 'This section is for advanced user only.<br/>Proceed with caution.',
+  'admin.advanced.button.uninstallIncludingData': 'Uninstall cluster including all data.',
+  'admin.advanced.button.uninstallKeepData': 'Uninstall cluster but keep data.',
 
-  'admin.advanced.popup.header':'Uninstall Cluster',
+  'admin.advanced.popup.header': 'Uninstall Cluster',
   /*'admin.advanced.popup.text':'Uninstall Cluster',*/
 
-  'admin.audit.grid.date':"Date/Time",
-  'admin.audit.grid.category':"Category",
-  'admin.audit.grid.operationName':"Operation",
-  'admin.audit.grid.performedBy':"Performed By",
-
-  'admin.authentication.form.method.database':'Use Ambari Database to authenticate users',
-  'admin.authentication.form.method.ldap':'Use LDAP/Active Directory to authenticate',
-  'admin.authentication.form.primaryServer':'Primary Server',
-  'admin.authentication.form.secondaryServer':'Secondary Server',
-  'admin.authentication.form.useSsl':'Use SSL',
-  'admin.authentication.form.bind.anonymously':"Bind Anonymously",
-  'admin.authentication.form.bind.useCrenedtials':"Use Credentials To Bind",
-  'admin.authentication.form.bindUserDN':'Bind User DN',
-  'admin.authentication.form.searchBaseDN':'Search Base DN',
-  'admin.authentication.form.usernameAttribute':'Username Attribute',
-
-  'admin.authentication.form.userDN':'User DN',
-  'admin.authentication.form.password':'Password',
-  'admin.authentication.form.configurationTest':'Configuration Test',
-  'admin.authentication.form.testConfiguration':'Test Configuration',
-
-  'admin.authentication.form.test.success':'The configuration passes the test',
-  'admin.authentication.form.test.fail':'The configuration fails the test',
-
-  'admin.security.title':'Kerberos Security has not been enabled on this cluster.',
-  'admin.security.button.enable':'Kerberos Security has not been enabled on this cluster.',
-
-  'admin.users.ldapAuthentionUsed':'LDAP Authentication is being used to authenticate users',
+  'admin.audit.grid.date': "Date/Time",
+  'admin.audit.grid.category': "Category",
+  'admin.audit.grid.operationName': "Operation",
+  'admin.audit.grid.performedBy': "Performed By",
+  'admin.audit.grid.service': "Category",
+
+  'admin.authentication.form.method.database': 'Use Ambari Database to authenticate users',
+  'admin.authentication.form.method.ldap': 'Use LDAP/Active Directory to authenticate',
+  'admin.authentication.form.primaryServer': 'Primary Server',
+  'admin.authentication.form.secondaryServer': 'Secondary Server',
+  'admin.authentication.form.useSsl': 'Use SSL',
+  'admin.authentication.form.bind.anonymously': "Bind Anonymously",
+  'admin.authentication.form.bind.useCrenedtials': "Use Credentials To Bind",
+  'admin.authentication.form.bindUserDN': 'Bind User DN',
+  'admin.authentication.form.searchBaseDN': 'Search Base DN',
+  'admin.authentication.form.usernameAttribute': 'Username Attribute',
+
+  'admin.authentication.form.userDN': 'User DN',
+  'admin.authentication.form.password': 'Password',
+  'admin.authentication.form.configurationTest': 'Configuration Test',
+  'admin.authentication.form.testConfiguration': 'Test Configuration',
+
+  'admin.authentication.form.test.success': 'The configuration passes the test',
+  'admin.authentication.form.test.fail': 'The configuration fails the test',
+
+  'admin.security.title': 'Kerberos Security has not been enabled on this cluster.',
+  'admin.security.button.enable': 'Enable Kerberos Security on this cluster',
+
+  'admin.users.ldapAuthentionUsed': 'LDAP Authentication is being used to authenticate users',
+  'admin.users.deleteYourselfMessage': 'You can\'t delete yourself',
   'admin.users.addButton': 'Add User',
   'admin.users.delete': 'delete',
   'admin.users.edit': 'edit',
@@ -190,5 +192,4 @@ Em.I18n.translations = {
   'hosts.decommission.popup.header': 'Confirmation',
   'hosts.delete.popup.body': 'Are you sure?',
   'hosts.delete.popup.header': 'Confirmation'
-};
-
+};

+ 41 - 19
ambari-web/app/models/authentication.js

@@ -24,7 +24,7 @@ App.Authentication = DS.Model.extend({
   bindMethod:DS.attr('boolean'), // use credentials
   bindUser:DS.attr('string'),
   password:DS.attr('string'),
-  retypePassword:DS.attr('string'),
+  passwordRetype:DS.attr('string'),
   searchBaseDn:DS.attr('string'),
   usernameAttribute:DS.attr('string')
 });
@@ -32,16 +32,16 @@ App.Authentication = DS.Model.extend({
 App.Authentication.FIXTURES = [
   {
     id:1,
-    method:false,
-    primary_server:"1.2.3.4:78",
-    secondary_server:"225.225.255.255:12",
+    method:0,
+    primary_server:"",
+    secondary_server:"",
     use_ssl:false,
-    bind_method:false,
-    bind_user:"hadoop\Administrator",
-    password:"1234",
-    retype_password:"1234",
-    search_base_dn:"DC=hadoop,DC=abc,DC=com",
-    username_attribute:"sAMAccountName"
+    bind_method:0,
+    bind_user:"",
+    password:"",
+    password_retype:"",
+    search_base_dn:"",
+    username_attribute:""
   }
 ]
 
@@ -55,19 +55,40 @@ App.AuthenticationForm = App.Form.extend({
         {value:1, label:Em.I18n.t("admin.authentication.form.method.ldap")}
       ]
     },
-    { name:"primaryServer", displayName:Em.I18n.t("admin.authentication.form.primaryServer"), validator:'ipaddress'},
-    { name:"secondaryServer", displayName:Em.I18n.t("admin.authentication.form.secondaryServer"), validator:'ipaddress'},
+    { name:"primaryServer", displayName:Em.I18n.t("admin.authentication.form.primaryServer"), /*validator:'ipaddress',*/
+      isRequired:function () {
+        return this.get('form.field.method.value');
+      }.property('form.field.method.value')
+    },
+    { name:"secondaryServer", displayName:Em.I18n.t("admin.authentication.form.secondaryServer"), /*validator:'ipaddress',*/ isRequired:false},
     { name:"useSsl", displayName:Em.I18n.t("admin.authentication.form.useSsl"), displayType:"checkbox", isRequired:false },
     { name:"bindMethod", displayName:'', displayType:"select", isRequired:false,
       values:[
         {value:0, label:Em.I18n.t("admin.authentication.form.bind.anonymously")},
         {value:1, label:Em.I18n.t("admin.authentication.form.bind.useCrenedtials")}
       ]},
-    { name:"bindUser", displayName:Em.I18n.t('admin.authentication.form.bindUserDN')},
-    { name:"password", displayName:Em.I18n.t('form.password'), displayType:"password" },
-    { name:"passwordRetype", displayName:Em.I18n.t('form.passwordRetype'), displayType:"passwordRetype"},
-    { name:"searchBaseDn", displayName:Em.I18n.t('admin.authentication.form.searchBaseDN')},
-    { name:"usernameAttribute", displayName:Em.I18n.t('admin.authentication.form.usernameAttribute')},
+    { name:"bindUser", displayName:Em.I18n.t('admin.authentication.form.bindUserDN'), isRequired:function () {
+      return this.get('form.field.bindMethod.value');
+    }.property('form.field.bindMethod.value')},
+    { name:"password", displayName:Em.I18n.t('form.password'), displayType:"password",
+      isRequired:function () {
+        return this.get('form.field.bindMethod.value');
+      }.property('form.field.bindMethod.value') },
+    { name:"passwordRetype", displayName:Em.I18n.t('form.passwordRetype'), displayType:"password",
+      validator: "passwordRetype",
+      isRequired:function () {
+        return this.get('form.field.bindMethod.value');
+      }.property('form.field.bindMethod.value')},
+    { name:"searchBaseDn", displayName:Em.I18n.t('admin.authentication.form.searchBaseDN'),
+      isRequired:function () {
+        return this.get('form.field.method.value');
+      }.property('form.field.method.value')
+    },
+    { name:"usernameAttribute", displayName:Em.I18n.t('admin.authentication.form.usernameAttribute'),
+      isRequired:function () {
+        return this.get('form.field.method.value');
+      }.property('form.field.method.value')
+    },
 
     { name:"userDN", displayName:Em.I18n.t('admin.authentication.form.userDN') },
     { name:"userPassword", displayName:Em.I18n.t('admin.authentication.form.password'), displayType:'password'}
@@ -83,5 +104,6 @@ App.AuthenticationForm = App.Form.extend({
   }.property('testResult'),
   testConfigurationClass:function () {
     return this.get('testResult') ? "text-success" : "text-error";
-  }.property('testConfigurationMessage')
-});
+  }.property('testConfigurationMessage'),
+})
+;

+ 4 - 4
ambari-web/app/models/cluster.js

@@ -60,7 +60,6 @@ App.Host = DS.Model.extend({
   loadAvg: DS.attr('string'),
   os: DS.attr('string'),
   ip: DS.attr('string'),
-  isChecked: false,
   healthStatus: DS.attr('string'),
   workStatus: DS.attr('boolean')
 });
@@ -70,7 +69,7 @@ App.Host.FIXTURES = [
     id: 1,
     host_name: 'z_host1',
     cluster_id: 1,
-    components:[1, 2, 3, 4],
+    components:[1, 2, 4],
     cpu: '2x2.5GHz',
     memory: '8GB',
     disk_usage: '40',
@@ -104,6 +103,7 @@ App.Host.FIXTURES = [
     id: 4,
     host_name: 'b_host4',
     cluster_id: 2,
+    components:[1, 2, 4, 5],
     health_status: 'DEAD',
     work_status: false
   },
@@ -111,7 +111,7 @@ App.Host.FIXTURES = [
     id: 5,
     host_name: 'host5',
     cluster_id: 1,
-    components:[4, 5],
+    components:[3, 4, 5],
     cpu: '2x2.5GHz',
     memory: '8GB',
     disk_usage: '20',
@@ -138,7 +138,7 @@ App.Host.FIXTURES = [
     id: 7,
     host_name: 'host7',
     cluster_id: 1,
-    components:[4, 5],
+    components:[3, 4, 7],
     cpu: '2x2.5GHz',
     memory: '8GB',
     disk_usage: '20',

+ 30 - 34
ambari-web/app/models/form.js

@@ -45,9 +45,8 @@ App.Form = Em.View.extend({
           thisForm.set("field." + field.get('name'), field);
         });
   },
-  getField:function (name) {
-//    return this.fields[name];
 
+  getField:function (name) {
     var field = false;
     $.each(this.fields, function () {
       if (this.get('name') == name) {
@@ -55,14 +54,15 @@ App.Form = Em.View.extend({
       }
     });
     return field;
-
   },
+
   isValid:function () {
     var isValid = true;
     $.each(this.fields, function () {
       this.validate();
       if (!this.get('isValid')) {
         isValid = false;
+        console.warn(this.get('name') + " IS INVALID : " + this.get('errorMessage'));
       }
     })
 
@@ -78,7 +78,7 @@ App.Form = Em.View.extend({
     var object = this.get('object');
     if (object instanceof Em.Object) {
       $.each(this.fields, function () {
-        this.set('value', object.get(this.get('name')));
+        this.set('value', this.get('displayType') == 'password' ? '' : object.get(this.get('name')));
       });
     } else {
       this.clearValues();
@@ -92,7 +92,8 @@ App.Form = Em.View.extend({
   getValues:function () {
     var values = {};
     $.each(this.fields, function () {
-      values[this.get('name')] = this.get('value');
+      if(!(this.get('displayType') == 'password') && validator.empty(this.get('value'))) // if this is not empty password field
+        values[this.get('name')] = this.get('value');
     });
     return values;
   },
@@ -163,7 +164,7 @@ App.FormField = Em.Object.extend({ // try to realize this as view
   form:false,
   isRequired:true, // by default a config property is required
   unit:'',
-  value:null,
+  value:'',
 
   isValid:function () {
     return this.get('errorMessage') === '';
@@ -181,15 +182,12 @@ App.FormField = Em.Object.extend({ // try to realize this as view
         element = Em.Select;
         options.content = this.get('values');
         options.valueBinding = "value";
-        options.optionValuePath="content.value";
-        options.optionLabelPath="content.label";
+        options.optionValuePath = "content.value";
+        options.optionLabelPath = "content.label";
         break;
       case 'password':
         options['type'] = 'password';
         break;
-      case 'passwordRetype':
-        options['type'] = 'password';
-        break;
       case 'textarea':
         element = Em.TextArea;
         break;
@@ -201,32 +199,42 @@ App.FormField = Em.Object.extend({ // try to realize this as view
   validate:function () {
     var digitsRegex = /^\d+$/;
     var numberRegex = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
-
     var value = this.get('value');
-
     var isError = false;
+    this.set('errorMessage', '');
 
-    if (!(!this.get('form.isObjectNew') && this.get('disableRequiredOnExistent')) && this.get('isRequired')) {
-      if (typeof value === 'string' && value.trim().length === 0) {
-        this.set('errorMessage', 'This is required');
-        isError = true;
-      }
+    if (this.get('isRequired') && (typeof value === 'string' && value.trim().length === 0)) {
+      this.set('errorMessage', 'This is required');
+      isError = true;
     }
 
-    if (!isError) {
+    if (typeof value === 'string' && value.trim().length === 0) { // this is not to validate empty field.
+      isError = true;
+    }
 
+    if (!isError) {
       switch (this.get('validator')) {
         case 'ipaddress':
-          if (!validator.isIpAddress(value)) {
+          if (!validator.isIpAddress(value) && !validator.isDomainName(value)) {
             isError = true;
             this.set('errorMessage', Em.I18n.t("form.validator.invalidIp"));
-          };
+          }
+          break;
+        case 'passwordRetype':
+          var form = this.get('form');
+          var passwordField = form.getField('password');
+          if (passwordField.get('isValid')
+            && (passwordField.get('value') != this.get('value'))
+            && passwordField.get('value') && this.get('value')
+            ) {
+            this.set('errorMessage', "Passwords are different");
+            isError = true;
+          }
           break;
         default:
           break;
       }
 
-
       switch (this.get('displayType')) {
         case 'digits':
           if (!digitsRegex.test(value)) {
@@ -245,18 +253,6 @@ App.FormField = Em.Object.extend({ // try to realize this as view
         case 'custom':
           break;
         case 'password':
-          break;
-        case 'passwordRetype':
-          var form = this.get('form');
-          var passwordField = form.getField('password');
-          if (passwordField.get('isValid')
-            && (passwordField.get('value') != this.get('value'))
-            && passwordField.get('value') && this.get('value')
-            ) {
-            this.set('errorMessage', "Passwords are different");
-            isError = true;
-          }
-
           break;
       }
     }

+ 3 - 2
ambari-web/app/models/pagination.js

@@ -27,7 +27,6 @@ App.Pagination = Em.Mixin.create({
   total: 0,
   rangeStart: 0,
   pageSize: 0,
-//  didRequestRange: Em.K,
 
   rangeStop: function() {
     var rangeStop = this.get('rangeStart') + this.get('pageSize'),
@@ -63,8 +62,10 @@ App.Pagination = Em.Mixin.create({
   }.property('rangeStop', 'pageSize').cacheable(),
 
   startPosition: function() {
+    if (this.get('total') == 0)
+      return 0;
     return this.get('rangeStart')  + 1;
-  }.property('rangeStart').cacheable(),
+  }.property('rangeStart', 'total').cacheable(),
 
   totalPages: function() {
     return Math.ceil(this.get('total') / this.get('pageSize'));

+ 13 - 9
ambari-web/app/models/service.js

@@ -62,7 +62,7 @@ App.Component = DS.Model.extend({
   type:DS.attr('boolean'),
   service:DS.belongsTo('App.Service'),
   host:DS.belongsTo('App.Host'),
-  workStatus:DS.attr('string')
+  workStatus: DS.attr('boolean')
 });
 
 App.Component.FIXTURES = [
@@ -73,7 +73,7 @@ App.Component.FIXTURES = [
     type: true,
     service_id:1,
     host_id:1,
-    work_status:true
+    work_status:false
   },
   {
     id:2,
@@ -82,7 +82,7 @@ App.Component.FIXTURES = [
     type: true,
     service_id:1,
     host_id:2,
-    work_status:false
+    work_status:true
   },
   {
     id:3,
@@ -90,12 +90,13 @@ App.Component.FIXTURES = [
     label: 'DN',
     service_id:1,
     type: false,
-    host_id:2
+    host_id:2,
+    work_status:true
   },
   {
     id:4,
     component_name:'JobTracker',
-    label: 'TT',
+    label: 'JT',
     type: true,
     service_id:2,
     host_id:4,
@@ -104,10 +105,11 @@ App.Component.FIXTURES = [
   {
     id:5,
     component_name:'TaskTracker',
-    label: 'JT',
+    label: 'TT',
     type: false,
     service_id:2,
-    host_id:4
+    host_id:4,
+    work_status:true
   },
   {
     id:6,
@@ -115,7 +117,8 @@ App.Component.FIXTURES = [
     label: 'HBM',
     type: true,
     service_id:3,
-    host_id:4
+    host_id:4,
+    work_status:true
   },
   {
     id:7,
@@ -123,7 +126,8 @@ App.Component.FIXTURES = [
     label: 'RS',
     type: false,
     service_id:3,
-    host_id:2
+    host_id:2,
+    work_status:true
   }
 ];
 

+ 12 - 6
ambari-web/app/models/service_audit.js

@@ -30,36 +30,42 @@ App.ServiceAudit.FIXTURES = [
     id: 1,
     date: 'September 12, 2012 17:00',
     operation_name: 'Reconfigure',
-    user_id: 2
+    user_id: 2,
+    service_id: 1
   },
   {
     id: 2,
     date: 'September 13, 2012 17:00',
     operation_name: 'Start',
-    user_id: 1
+    user_id: 1,
+    service_id: 1
   },
   {
     id: 3,
     date: 'September 14, 2012 17:00',
     operation_name: 'Install',
-    user_id: 1
+    user_id: 1,
+    service_id: 1
   },
   {
     id: 4,
     date: 'September 12, 2012 17:00',
     operation_name: 'Reconfigure',
-    user_id: 2
+    user_id: 2,
+    service_id: 2
   },
   {
     id: 5,
     date: 'September 13, 2012 17:00',
     operation_name: 'Start',
-    user_id: 1
+    user_id: 1,
+    service_id: 2
   },
   {
     id: 6,
     date: 'September 14, 2012 17:00',
     operation_name: 'Install',
-    user_id: 1
+    user_id: 1,
+    service_id: 2
   }
 ];

+ 45 - 4
ambari-web/app/models/user.js

@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+var validator = require('utils/validator');
 
 App.UserModel = Em.Object.extend({
   userName:null,
@@ -34,22 +35,62 @@ App.UserForm = App.Form.extend({
   className:App.User,
   fieldsOptions:[
     { name:"userName", displayName:"Username" },
-    { name:"password", displayName:"Password", displayType:"password", disableRequiredOnExistent:true },
-    { name:"passwordRetype", displayName:"Retype Password", displayType:"passwordRetype", disableRequiredOnExistent:true },
+    { name:"password", displayName:"Password", displayType:"password", isRequired: function(){ return this.get('form.isObjectNew'); }.property('form.isObjectNew') },
+    { name:"passwordRetype", displayName:"Retype Password", displayType:"password", validator:"passwordRetype", isRequired: false },
     { name:"admin", displayName:"Admin", displayType:"checkbox", isRequired:false }
   ],
   fields:[],
   disableUsername:function () {
     var field = this.getField("userName");
     if (field) field.set("disabled", this.get('isObjectNew') ? false : "disabled");
-  }.observes('isObjectNew')
+
+  }.observes('isObjectNew'),
+  disableAdminCheckbox:function () {
+    if (!this.get('isObjectNew')) {
+      var object = this.get('object');
+      var field = this.getField("admin");
+      if (field) {
+        field.set("disabled", object.get('userName') == App.get('router').getLoginName() ? "disabled" : false);
+      }
+    }
+  }.observes('isObjectNew'),
+
+  isValid: function(){
+    var isValid = this._super();
+    thisForm = this;
+
+    var passField = this.get('field.password');
+    var passRetype = this.get('field.passwordRetype');
+
+    if(!validator.empty(passField.get('value'))) {
+      if(passField.get('value') != passRetype.get('value')) {
+        passRetype.set('errorMessage', "Passwords are different");
+        isValid = false;
+      }
+    }
+
+    if(isValid && this.get('isObjectNew')) {
+      var users = App.User.find();
+      var userNameField = this.getField('userName');
+      var userName = userNameField.get('value');
+
+      users.forEach(function(user){
+        if(userName == user.get('userName')) {
+          userNameField.set('errorMessage', 'User with the same name is already exists');
+          return isValid = false;
+        }
+      });
+    }
+    
+    return isValid;
+  }
 });
 
 App.User.FIXTURES = [
   {
     id:1,
     user_name:'admin',
-    password: 'admin',
+    password:'admin',
     admin:1
   },
   {

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

@@ -66,11 +66,20 @@ App.Router = Em.Router.extend({
     //localStorage.setItem('Ambari' + 'loginName', loginName);
   },
 
-  login: function (loginName) {
+  // that works incorrectly
+  setUser: function(user){ App.db.setUser(user); },
+  // that works incorrectly
+  getUser: function(){ return App.db.getUser(); },
+
+  login: function (loginName, user) {
     // TODO: this needs to be hooked up with server authentication
     console.log("In login function");
     this.setAuthenticated(true);
     this.setLoginName(loginName);
+
+//    refactor to get user attributes
+//    this.setUser(user);
+
     this.transitionTo(this.getSection());
 
   },

+ 43 - 69
ambari-web/app/routes/main.js

@@ -34,9 +34,9 @@ module.exports = Em.Route.extend({
     }
   },
 
-  index: Ember.Route.extend({
-    route: '/',
-    redirectsTo: 'dashboard'
+  index:Ember.Route.extend({
+    route:'/',
+    redirectsTo:'dashboard'
   }),
 
   connectOutlets:function (router, context) {
@@ -56,7 +56,10 @@ module.exports = Em.Route.extend({
       router.get('mainController').connectOutlet('mainHost');
     },
 
-    showDetails:Em.Router.transitionTo('hostDetails.index')
+    showDetails:function (router, event) {
+      router.get('mainHostDetailsController').setBack(true);
+      router.transitionTo('hostDetails.index', event.context)
+    }
 
   }),
 
@@ -106,32 +109,41 @@ module.exports = Em.Route.extend({
 
   admin:Em.Route.extend({
     route:'/admin',
-    enter:function (router) {
-      Ember.run.next(function () {
-        router.transitionTo('adminUser');
-      });
+
+    enter: function(router) {
+      if(router.get('currentState.name') != 'main') { // is user comes from main -> navigate to users
+        Em.run.next(function () {
+          router.transitionTo('adminUser');
+        });
+      }
+    },
+
+    connectOutlets:function (router, context) {
+      router.get('mainController').connectOutlet('mainAdmin');
     },
 
     adminUser:Em.Route.extend({
       route:'/user',
       enter:function (router) {
-        Ember.run.next(function () {
-          router.transitionTo('all');
+        router.set('mainAdminController.category', "user");
+        Em.run.next(function () {
+          router.transitionTo('allUsers');
         });
       },
-      connectOutlets:function (router) {
-        console.log("ADMIN USER RECONNECTED");
-      },
 
-      all:Em.Route.extend({
+      // events
+      gotoUsers:Em.Router.transitionTo("allUsers"),
+      gotoCreateUser:Em.Router.transitionTo("createUser"),
+      gotoEditUser:function (router, event) { router.transitionTo("editUser", event.context) },
+
+      // states
+      allUsers:Em.Route.extend({
         route:'/',
         connectOutlets:function (router) {
           router.get('mainAdminController').connectOutlet('mainAdminUser');
         }
       }),
 
-      gotoUsers:Em.Router.transitionTo("all"),
-
       createUser:Em.Route.extend({
         route:'/create',
         connectOutlets:function (router) {
@@ -139,8 +151,6 @@ module.exports = Em.Route.extend({
         }
       }),
 
-      gotoCreateUser:Em.Router.transitionTo("createUser"),
-
       editUser:Em.Route.extend({
         route:'/edit/:userName',
         connectOutlets:function (router, user) {
@@ -148,15 +158,12 @@ module.exports = Em.Route.extend({
           router.get('mainAdminController').connectOutlet('mainAdminUserEdit', user);
         }
       }),
-
-      gotoEditUser:function (router, event) {
-        router.transitionTo("editUser", event.context);
-      }
     }),
 
     adminAuthentication:Em.Route.extend({
       route:'/authentication',
       connectOutlets:function (router) {
+        router.set('mainAdminController.category', "authentication");
         router.get('mainAdminController').connectOutlet('mainAdminAuthentication');
       }
     }),
@@ -164,6 +171,7 @@ module.exports = Em.Route.extend({
     adminSecurity:Em.Route.extend({
       route:'/security',
       connectOutlets:function (router) {
+        router.set('mainAdminController.category', "security");
         router.get('mainAdminController').connectOutlet('mainAdminSecurity');
       }
     }),
@@ -171,6 +179,7 @@ module.exports = Em.Route.extend({
     adminAdvanced:Em.Route.extend({
       route:'/advanced',
       connectOutlets:function (router) {
+        router.set('mainAdminController.category', "advanced");
         router.get('mainAdminController').connectOutlet('mainAdminAdvanced');
       }
     }),
@@ -178,33 +187,14 @@ module.exports = Em.Route.extend({
     adminAudit:Em.Route.extend({
       route:'/audit',
       connectOutlets:function (router) {
+        router.set('mainAdminController.category', "audit");
         router.get('mainAdminController').connectOutlet('mainAdminAudit');
       }
     }),
 
-    connectOutlets:function (router, context) {
-      router.get('mainController').connectOutlet('mainAdmin');
-    },
-
-//    adminDetails:Em.Route.extend({
-//      route:'/:route',
-//      connectOutlets:function (router, menu) {
-//        router.get('mainAdminController').connectOutlet('mainAdmin' + menu.route.capitalize(), menu);
-//      }
-//    }),
-
-//    advanced:Em.Route.extend({
-//      route:'/:name',
-//      connectOutlets:function (router, service) {
-//        router.get('mainServiceController').connectOutlet('mainServiceItem', service);
-//      }
-//    }),
-
     adminNavigate:function (router, object) {
-      object.view._parentView.activateView(object.context.route);
-      console.log(object.context.route);
       Em.run.next(function () {
-        router.transitionTo('admin' + object.context.route.capitalize());
+        router.transitionTo('admin' + object.context.capitalize());
       });
     }
   }),
@@ -213,12 +203,6 @@ module.exports = Em.Route.extend({
     route:'/dashboard',
     connectOutlets:function (router, context) {
       router.get('mainController').connectOutlet('mainDashboard');
-    },
-    selectService: Em.Route.transitionTo('services.service'),
-    selectHost: Em.Router.transitionTo('hostDetails.index'),
-    filterHosts: function(router, component) {
-      router.get('mainHostController').set('filters.components', [component.context.get('id')]);
-      router.transitionTo('hosts');
     }
   }),
 
@@ -233,7 +217,7 @@ module.exports = Em.Route.extend({
         if (!service) {
           service = App.Service.find(1); // getting the first service to display
         }
-        router.transitionTo('service', service);
+        router.transitionTo('service.summary', service);
       });
     },
     connectOutlets:function (router, context) {
@@ -279,12 +263,6 @@ module.exports = Em.Route.extend({
           router.get('mainServiceItemController').connectOutlet('mainServiceInfoAudit', item);
         }
       }),
-      selectService: Em.Route.transitionTo('services.service'),
-      selectHost: Em.Router.transitionTo('hostDetails.index'),
-      filterHosts: function(router, component) {
-        router.get('mainHostController').set('filters.components', [component.context.get('id')]);
-        router.transitionTo('hosts');
-      },
       showInfo:function (router, event) {
         var parent = event.view._parentView;
         parent.deactivateChildViews();
@@ -292,25 +270,21 @@ module.exports = Em.Route.extend({
         router.transitionTo(event.context);
       }
     }),
-    connectOutlets:function (router, context) {
-      router.get('mainController').connectOutlet('mainService');
-    },
     showService:Em.Router.transitionTo('service')
   }),
 
+  selectService:Em.Route.transitionTo('services.service'),
+  selectHost:function (router, event) {
+    router.get('mainHostDetailsController').setBack(false);
+    router.transitionTo('hostDetails.index', event.context);
+  },
+  filterHosts:function (router, component) {
+    router.get('mainHostController').filterByComponent(component.context);
+    router.transitionTo('hosts');
+  },
   navigate:function (router, event) {
     var parent = event.view._parentView;
     parent.deactivateChildViews(event.context);
     router.transitionTo(event.context.routing);
   }
-
-  // TODO: create new routes here
-  // dashboard
-  // charts
-  // hosts
-  // hosts/:hostname
-  // admin
-  // etc...
-
-
 });

+ 10 - 55
ambari-web/app/styles/app.css

@@ -16,55 +16,6 @@
  * limitations under the License.
  */
 
-/*Box styles*/
-.box {
-    border: 1px solid #5fa3c3;
-    margin-bottom: 20px;
-}
-.box-header,
-.box-footer {
-    padding: 10px;
-    background: #dedede;
-}
-.box-footer hr {
-    margin: 0px;
-}
-.box-header:after,
-.box-footer:after {
-    content: "";
-    display: table;
-    clear: both;
-}
-.box-header .btn-group {
-    float: right;
-}
-.box-header h4 {
-    float: left;
-}
-
-.alerts {
-    margin: 0px;
-}
-.alerts li {
-    border-bottom: 1px solid #b7b9bb;
-    list-style: none;
-    padding: 5px 10px 5px 43px;
-}
-.alerts li.status-ok {
-    background: url("../img/status-ok.jpg") no-repeat 14px 9px;
-}
-.alerts li.status-corrupt {
-    background: url("../img/status-corrupt.jpg") no-repeat 14px 9px;
-}
-.alerts li .date-time {
-    float: right;
-}
-.go-to {
-    float: right;
-    background: url("../img/arrow-right.png") no-repeat right center;
-    padding-right: 40px;
-    margin-top: 20px;
-}
 /*Services*/
 .service-summary {
     background: #F6FAFD;
@@ -106,7 +57,7 @@
 .service-button {
     text-align: right;
     margin-bottom: 5px;
-    margin-top: -57px;
+    margin-top: -55px;
 }
 .add-service-button {
     position: relative;
@@ -143,18 +94,22 @@
 #hosts .table td.first label{
     padding-top: 3px;
 }
-#hosts .table td.first label span{
+#hosts .table td.name span{
     display: block;
+    float: left;
     height: 13px;
-    margin: 3px 0 0 3px;
+    margin: 4px 5px 0 0;
     width: 13px;
 }
-#hosts .table ul.filter-components li{
+#hosts .open-group > .dropdown-menu{
+    display: block;
+}
+#hosts .table ul#filter-dropdown li{
     display: block;
     padding: 3px 0 3px 5px;
     line-height: 20px;
 }
-#hosts .table ul.filter-components li input[type="checkbox"]{
+#hosts .table ul#filter-dropdown li input[type="checkbox"]{
     margin-top: 0;
     margin-right: 2px;
     margin-bottom: 2px;
@@ -232,7 +187,7 @@
     border: 1px solid #DEDEDE;
     background: #fff;
 }
-#host-details .host-components .btn{
+#host-details .host-components .btn-group{
     margin:0 5px 10px 0;
 }
 /*End Hosts*/

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

@@ -16,6 +16,13 @@
  * limitations under the License.
  */
 
+.gradient(@color: #FAFAFA, @start: #FFFFFF, @stop: #F2F2F2) {
+  background: @color;
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0, @start), color-stop(1, @stop));
+  background: -ms-linear-gradient(top, @start, @stop);
+  background: -moz-linear-gradient(center top, @start 0%, @stop 100%);
+}
+
 html, body {
   height: 100%;
 }
@@ -226,9 +233,51 @@ a:focus {
 
 @status-live-marker: url("../img/health-status-live.png");
 @status-dead-marker: url("../img/health-status-dead.png");
+@status-ok-marker: url("../img/status-ok.jpg");
+@status-corrupt-marker: url("../img/status-corrupt.jpg");
+@arrow-right: url("../img/arrow-right.png");
+
+/*****start styles for boxes*****/
+.box {
+  border: 1px solid #D4D4D4;
+  border-radius: 4px;
+  margin-bottom: 20px;
+
+  .box-header {
+    border-bottom: 1px solid #D4D4D4;
+    border-radius: 4px 4px 0 0;
+  }
+  .box-header,
+  .box-footer {
+    padding: 10px;
+    /*background: #dedede;*/
+    .gradient(#dedede, #ffffff, #dedede)
+  }
+  .box-header:after,
+  .box-footer:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+  .box-header {
+    .btn-group {
+      float: right;
+    }
+    h4 {
+      float: left;
+    }
+  }
+  .box-footer {
+    hr {
+      margin: 0px;
+    }
+  }
+}
+/*****end styles for boxes*****/
 
-/*Dashboard*/
+/*****start styles for dashboard page*****/
 
+/*start services summary*/
 .services {
   margin-left: 5px;
   .tab-marker-position {
@@ -245,7 +294,50 @@ a:focus {
     .tab-marker-position;
     background-image: @status-dead-marker;
   }
+  dt {
+    text-align: left;
+    width: 120px;
+  }
+  dd{
+    margin-left: 145px;
+  }
+}
+/*end services summary*/
+
+/*start alerts summary*/
+.alerts {
+  margin: 0px;
+  li {
+    border-bottom: 1px solid #b7b9bb;
+    list-style: none;
+    padding: 5px 10px 5px 43px;
+    background-position: 14px 9px;
+    background-repeat: no-repeat;
+    .date-time {
+      float: right;
+    }
+    p {
+      margin-bottom: 2px;
+    }
+  }
+  li.status-ok {
+    background-image: @status-ok-marker;
+  }
+  li.status-corrupt {
+    background-image: @status-corrupt-marker;
+  }
+}
+.go-to {
+  float: right;
+  background-position: right center;
+  background-repeat: no-repeat;
+  background-image: @arrow-right;
+  padding-right: 40px;
+  margin-top: 10px;
 }
+/*end alerts summary*/
+
+/*****end styles for dashboard page*****/
 
 /*Services*/
 .nav-tabs {
@@ -332,17 +424,18 @@ a:focus {
     border: solid 1px #cccccc;
     padding: 8px;
     margin-bottom: 10px;
-    background-color: #FFFAFA;
+    background-color: #fafafa;
   }
 
   .host-assignments .assignedService {
-    padding: 5px;
+    padding: 2px 8px;
     border: solid 1px #cccccc;
     margin: 2px;
-    background-color: #69BE28;
+    background-color: @green;
     color: white;
     white-space: nowrap;
     font-size: 0.9em;
+    display: inline-block;
   }
 
   .form-horizontal .controls {
@@ -364,7 +457,7 @@ a:focus {
   }
 
   .controls .badge {
-    background-color: #ADC299;
+    background-color: @green;
     color: #ffffff;
     cursor: pointer;
     font-weight: bold;
@@ -372,7 +465,7 @@ a:focus {
   }
 
   .assign-master  .controls .badge:hover {
-    background-color: #99B280;
+    background-color: @green;
   }
 
   .alertFlag {
@@ -388,4 +481,23 @@ a:focus {
 /*Dashboard*/
 .alerts-count {
   margin-left: 5px;
+}
+
+ul.filter {
+  background: #ffffff;
+  list-style: none;
+  position: absolute;
+  padding: 10px;
+}
+
+#main-admin-menu {
+  margin-left: 0;
+
+  ul {
+    margin-bottom: 0;
+  }
+}
+
+#user-auth-method select {
+  width: 320px;
 }

+ 24 - 0
ambari-web/app/templates/common/grid/filter.hbs

@@ -0,0 +1,24 @@
+<!--
+* 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 filter in view.filters}}
+<li>
+  <label class="checkbox">
+    {{view Em.Checkbox checkedBinding="filter.checked"}} {{filter.label}}
+  </label>
+</li>
+{{/each}}

+ 5 - 1
ambari-web/app/templates/common/grid/header.hbs

@@ -15,4 +15,8 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 -->
-{{view.label}}<i {{action doFilter target="view"}} class="icon-filter"></i>
+{{view.label}}<i {{action toggleFilter target="view"}} class="icon-filter"></i>
+{{#if view.showFilter}}
+{{view view.filter}}
+  <a {{action applyFilter target="view"}}>apply</a>
+{{/if}}

+ 24 - 0
ambari-web/app/templates/common/grid/pager.hbs

@@ -0,0 +1,24 @@
+<!--
+* 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.
+-->
+<ul>
+    <li {{bindAttr class="view.prevPageDisabled"}}><a {{action activatePrevPage target="view"}} href="#">Prev</a></li>
+  {{#each page in view.pages}}
+    <li {{bindAttr class="page.activeClass"}} ><a {{action activatePage page target="view" }} href="#">{{page.number}}</a></li>
+  {{/each}}
+    <li {{bindAttr class="view.nextPageDisabled"}}><a {{action activateNextPage target="view"}} href="#">Next</a></li>
+</ul>

+ 2 - 2
ambari-web/app/templates/installer/step3.hbs

@@ -57,7 +57,6 @@
     <tr>
       <th>
         {{view Ember.Checkbox checkedBinding="allChecked"}}
-        {{controller.category}}
       </th>
       <th>Status</th>
       <!--  given by the parsing function that parses data from bootstrap call -->
@@ -65,8 +64,9 @@
       <!-- retrieved from local storage initially -->
       <th>Message</th>
       <!-- given by the parsing function that parses data from bootstrap call, dynamically assign the color -->
-      <th>Delete</th>
+      <th>Action</th>
       <!-- trash icon -->
+      <!-- retry icon -->
     </tr>
     </thead>
 

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

@@ -16,7 +16,9 @@
 * limitations under the License.
 -->
 
+<div id="main-admin-menu" class="well span2">
 {{view App.MainAdminMenuView}}
+</div>
 <div class="span9">
   {{outlet}}
 </div>

+ 10 - 12
ambari-web/app/templates/main/admin/audit.hbs

@@ -16,22 +16,20 @@
 * limitations under the License.
 -->
 
-<table class="table">
+<table class="table table-striped">
   <thead>
   <tr>
     {{#each column in view.columns}}
-      {{view column}}
+    {{view column}}
     {{/each}}
   </tr>
   </thead>
+  <tbody>
+  {{#each row in view.rows}}
+  {{view row}}
+  {{/each}}
+  </tbody>
 </table>
-<div class="pagination">
-  <ul>
-    <li><a href="#">Prev</a></li>
-    <li><a href="#">1</a></li>
-    <li><a href="#">2</a></li>
-    <li><a href="#">3</a></li>
-    <li><a href="#">4</a></li>
-    <li><a href="#">Next</a></li>
-  </ul>
-</div>
+{{#if view.pager}}
+  {{view view.pager}}
+{{/if}}

+ 2 - 3
ambari-web/app/templates/main/admin/authentication.hbs

@@ -16,7 +16,7 @@
 * limitations under the License.
 -->
 
-{{view App.FormFieldTemplate fieldBinding="view.form.field.method"}}
+{{view App.FormFieldTemplate fieldBinding="view.form.field.method" id="user-auth-method"}}
 
 {{#if view.ldapChecked}}
   {{view App.FormFieldTemplate fieldBinding="view.form.field.primaryServer"}}
@@ -42,8 +42,7 @@
       <p {{bindAttr class="view.form.testConfigurationClass"}}>{{view.form.testConfigurationMessage}}</p>
     {{/if}}
 {{/if}}
-<div class="span3"></div>
-<div>
+<div style="margin:40px 0">
   <button {{action updateValues target="view.form"}} class="btn">{{t form.cancel}}</button>
   <button {{action save view.form target="App.router.mainAdminAuthenticationController"}} class="btn btn-primary">{{t form.save}}</button>
 </div>

+ 1 - 1
ambari-web/app/templates/main/admin/security.hbs

@@ -17,7 +17,7 @@
 -->
 
 <h5>{{t admin.security.title}}<i class="icon-question-sign"></i></h5>
-<div class="row">
+<div>
   <button class="btn">
     <i class="icon-lock"></i>
     {{t admin.security.button.enable}}

+ 1 - 1
ambari-web/app/templates/main/admin/user.hbs

@@ -19,7 +19,7 @@
 {{#if view.ldapUser}}
   <p class="text-info">{{t admin.users.ldapAuthentionUsed}}.</p>
 {{else}}
-<table class="table table-bordered span5">
+<table class="table table-bordered table-striped span5">
   <thead>
   <tr>
     <th>ID (test)</th>

+ 5 - 3
ambari-web/app/templates/main/admin/user/edit.hbs

@@ -27,8 +27,10 @@
   </div>
   {{/each}}
   <div class="control-group">
-    <button type="submit" class="btn" {{action gotoUsers}}>{{t form.cancel}}</button>
-    <button type="submit"
-            class="btn btn-primary" {{action create target="view"}}>{{view.userForm.saveButtonText}}</button>
+    <div class="controls">
+      <button type="submit" class="btn" {{action gotoUsers}}>{{t form.cancel}}</button>
+      <button type="submit"
+              class="btn btn-primary" {{action create target="view"}}>{{view.userForm.saveButtonText}}</button>
+    </div>
   </div>
 </form>

+ 10 - 8
ambari-web/app/templates/main/dashboard.hbs

@@ -49,20 +49,22 @@
           <div class="box-header">
             <h4>Services</h4>
           </div>
-          <ul class="services">
+          <dl class="dl-horizontal services">
             {{#each service in controller.services}}
-              <li class="health-status-{{unbound service.healthStatus}}">
-                <a {{action selectService service href=true}} href="javascript:void(null)">{{service.label}}</a> -
+              <dt class="health-status-{{unbound service.healthStatus}}">
+                <a {{action selectService service href=true}}>{{service.label}}</a> -
+              </dt>
+              <dd>
                 {{#each component in service.components}}
                   {{#if component.type}}
-                    <a {{action selectHost component.host href=true}} href="javascript:void(null)">{{component.componentName}}</a>,
+                    <a href="#" {{action selectHost component.host}}>{{component.componentName}}</a>,
                   {{else}}
-                    <a {{action filterHosts component}} href="javascript:void(null)">{{component.componentName}}</a>
+                    <a href="#" {{action filterHosts component}}>{{component.componentName}}</a>
                   {{/if}}
                 {{/each}}
-              </li>
+              </dd>
             {{/each}}
-          </ul>
+          </dl>
         </div>
       </div>
     </div>
@@ -84,7 +86,7 @@
       <ul class="alerts">
         {{#each controller.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 class="title">{{title}}</span> <a {{action selectService service href=true}}>{{service.label}}</a><span class="date-time">{{date}}</span></p>
           <p><span>{{status}}:</span> <span>{{message}}</span></p>
         </li>
         {{/each}}

+ 115 - 106
ambari-web/app/templates/main/host.hbs

@@ -16,114 +16,123 @@
 * limitations under the License.
 -->
 <div id="hosts" class="box">
-    <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">
-                    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"}}>
-                        {{clusterName}}
-                      </a>
-                    </li>
-                    {{/each}}
-                </ul>
-            </div>
-            <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"}}>
-              Delete
-            </button>
-            <button class="btn btn-inverse add-host-button" disabled="disabled">
-                <i class="icon-plus icon-white"></i>
-                Add New Host
-            </button>
-        </div>
+  <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">
+          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"}}>
+              {{clusterName}}
+            </a>
+          </li>
+          {{/each}}
+        </ul>
+      </div>
+      <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"}}>
+        Delete
+      </button>
+      <button class="btn btn-inverse add-host-button" disabled="disabled">
+        <i class="icon-plus icon-white"></i>
+        Add New Host
+      </button>
     </div>
-    <table class="table table-bordered table-striped">
-        <thead>
-        <tr>
-            <th class="first">
-                <label class="checkbox">
-                  {{view Ember.Checkbox checkedBinding="allChecked" class="checkbox"}}
-                </label>
-            </th>
-            <th>
-              <a href="#" {{action sortByName target="controller" }}>Name</a>
-            </th>
-            <th>Rack</th>
-            <th>CPU</th>
-            <th>RAM</th>
-            <th>Disk Usage</th>
-            <th>Load Avg</th>
-            <th>
-                <div class="btn-group">
-                    <button class="btn btn-info dropdown-toggle" data-toggle="dropdown">
-                        Components
-                        <span class="caret"></span>
-                    </button>
-                    <ul class="dropdown-menu filter-components">
-                        {{#each component in allComponents}}
-                        <li>
-                            <label>
-                                {{view view.ComponentCheckboxView checkedBinding="this.isChecked"contentBinding="component.id"}}                                {{component.componentName}}
-                            </label>
-                        </li>
-                        {{/each}}
-                    </ul>
-                </div>
-            </th>
-        </tr>
-        </thead>
-        <tbody>
-        {{#each host in controller}}
-        <tr>
-            <td class="first">
-                <label class="checkbox">
-                  {{view view.HostCheckboxView checkedBinding="host.isChecked" contentBinding="host"}}
-                  <span class="health-status-{{unbound host.healthStatus}}"></span>
-                </label>
-            </td>
-            <td><a href="#" {{action showDetails host}}>{{unbound host.hostName}}</a></td>
-            <td>{{host.cluster.clusterName}}</td>
-            <td>{{host.cpu}}</td>
-            <td>{{host.memory}}</td>
-            <td>{{host.diskUsage}}</td>
-            <td>{{host.loadAvg}}</td>
-            <td>
-                {{#each component in host.components}}
-                  {{component.label}},
-                {{/each}}
-            </td>
-        </tr>
-        {{/each}}
-        </tbody>
-    </table>
-    <div class="box-footer">
-        <hr />
-        <div class="footer-pagination">
-            <ul class="nav nav-pills">
-                <li class="disabled">Show Hosts</li>
-                <li class="dropdown">
-                    {{view Em.Select contentBinding="pageSizeRange"
+  </div>
+  <table class="table table-bordered table-striped">
+    <thead>
+    <tr>
+      <th class="first">
+        <label class="checkbox">
+          {{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>
+      <th>Rack</th>
+      <th>CPU</th>
+      <th>RAM</th>
+      <th>Disk Usage</th>
+      <th>Load Avg</th>
+      <th>
+        <div {{bindAttr class="view.btnGroupClass"}} >
+          <button class="btn btn-info" {{action "clickFilterButton" target="view"}}>
+            Components
+            <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>
+            </li>
+            {{/each}}
+          </ul>
+          <button {{bindAttr disabled="view.isApplyDisabled"}} class="btn" {{action "applyFilters" target="view"}}>
+            Apply
+          </button>
+        </div>
+
+      </th>
+    </tr>
+    </thead>
+    <tbody>
+    {{#each host in controller}}
+    {{#view view.HostView contentBinding="host"}}
+    <tr>
+      <td class="first">
+        <label class="checkbox">
+          {{view view.HostCheckboxView checkedBinding="host.isChecked" contentBinding="host"}}
+        </label>
+      </td>
+      <td class="name">
+        <span class="health-status-{{unbound host.healthStatus}}"></span>
+        <a href="#" {{action "showDetails" host}}>{{unbound host.hostName}}</a>
+      </td>
+      <td>{{host.cluster.clusterName}}</td>
+      <td>{{host.cpu}}</td>
+      <td>{{host.memory}}</td>
+      <td>{{host.diskUsage}}</td>
+      <td>{{host.loadAvg}}</td>
+      <td>
+        {{view.labels}}
+      </td>
+    </tr>
+    {{/view}}
+    {{/each}}
+    </tbody>
+  </table>
+  <div class="box-footer">
+    <hr />
+    <div class="footer-pagination">
+      <ul class="nav nav-pills">
+        <li class="disabled">Show Hosts</li>
+        <li class="dropdown">
+          {{view Em.Select contentBinding="pageSizeRange"
                     selectionBinding="pageSize"
                     optionValuePath="this"}}
-                </li>
-                <li class="disabled">{{startPosition}}-{{rangeStop}} of {{total}}</li>
-                <li class="disabled page-listing">
-                    {{#if hasPrevious}}
-                    <a href="#" {{action showPreviousPage target="controller"}}>previous</a>
-                    {{/if}}
-                    {{#if hasNext}}
-                    <a href="#" {{action showNextPage target="controller"}}>next</a>
-                    {{/if}}
-                </li>
-            </ul>
-        </div>
+        </li>
+        <li class="disabled">{{startPosition}}-{{rangeStop}} of {{total}}</li>
+        <li class="disabled page-listing">
+          {{#if hasPrevious}}
+          <a href="#" {{action showPreviousPage target="controller"}}>previous</a>
+          {{/if}}
+          {{#if hasNext}}
+          <a href="#" {{action showNextPage target="controller"}}>next</a>
+          {{/if}}
+        </li>
+      </ul>
     </div>
+  </div>
 </div>

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

@@ -16,25 +16,27 @@
 * limitations under the License.
 -->
 <div id="host-details" class="row">
-    <a class="btn back" {{action backToHostsList}}>← Back to Hosts</a>
-    <div class="box ">
-        <div class="box-header">
-            <div class="span1 host-title health-status-{{unbound view.content.healthStatus}}">{{unbound view.content.hostName}}</div>
-            <div class="button-section pull-right clearfix">
-              <button {{bindAttr disabled="controller.startComponents"}} class="btn btn-success" data-toggle="modal" {{action "startConfirmPopup" target="controller"}}>
-                <i class="icon-play"></i>
-                Start
-              </button>
-              <button {{bindAttr disabled="controller.stopComponents"}} class="btn btn-danger" data-toggle="modal" {{action "stopConfirmPopup" target="controller"}}>
-                <i class="icon-stop"></i>
-                Stop
-              </button>
-            </div>
-            <hr />
-        </div>
-        <div class="content">
-            {{view App.MainHostMenuView}}
-            {{outlet}}
-        </div>
+  {{#if controller.isFromHosts}}
+  <a class="btn back" {{action backToHostsList}}>← Back to Hosts</a>
+  {{/if}}
+  <div class="box ">
+    <div class="box-header">
+      <div class="span1 host-title health-status-{{unbound view.content.healthStatus}}">{{unbound view.content.hostName}}</div>
+      <div class="button-section pull-right clearfix">
+        <button {{bindAttr disabled="controller.isStarting"}} class="btn btn-success" data-toggle="modal" {{action "startConfirmPopup" target="controller"}}>
+          <i class="icon-play"></i>
+          Start
+        </button>
+        <button {{bindAttr disabled="controller.isStopping"}} class="btn btn-danger" data-toggle="modal" {{action "stopConfirmPopup" target="controller"}}>
+          <i class="icon-stop"></i>
+          Stop
+        </button>
+      </div>
+      <hr />
     </div>
+    <div class="content">
+      {{view App.MainHostMenuView}}
+      {{outlet}}
+    </div>
+  </div>
 </div>

+ 36 - 20
ambari-web/app/templates/main/host/summary.hbs

@@ -16,26 +16,42 @@
 * limitations under the License.
 -->
 <div class="row">
-    <div class="span5 host-configuration">
-        <dl class="dl-horizontal">
-            <dt>IP:</dt><dd>{{view.content.ip}}</dd>
-            <dt>CPU:</dt><dd>{{view.content.cpu}}</dd>
-            <dt>OS:</dt><dd>type</dd>
-            <dt>Disk Usage:</dt><dd>{{view.content.diskUsage}}</dd>
-            <dt>Memory:</dt><dd>{{view.content.memory}}</dd>
-            <dt>Load Avg:</dt><dd>{{view.content.loadAvg}}</dd>
-            <dt>Agent:</dt><dd>running</dd>
-        </dl>
+  <div class="span5 host-configuration">
+    <dl class="dl-horizontal">
+      <dt>IP:</dt><dd>{{view.content.ip}}</dd>
+      <dt>CPU:</dt><dd>{{view.content.cpu}}</dd>
+      <dt>OS:</dt><dd>type</dd>
+      <dt>Disk Usage:</dt><dd>{{view.content.diskUsage}}</dd>
+      <dt>Memory:</dt><dd>{{view.content.memory}}</dd>
+      <dt>Load Avg:</dt><dd>{{view.content.loadAvg}}</dd>
+      <dt>Agent:</dt><dd>running</dd>
+    </dl>
+  </div>
+  {{#if view.content.components.length}}
+  <div class="span3 host-components pull-right">
+    {{#each component in view.content.components}}
+    {{#view view.ComponentButtonView contentBinding="component"}}
+    <div class="btn-group">
+      <button {{bindAttr class="view.buttonClass"}} data-toggle="dropdown">
+        {{unbound view.content.componentName}}
+        <span class="caret"></span>
+      </button>
+      <ul class="dropdown-menu">
+        <li>
+          <a href="javascript:void(null)" data-toggle="modal" {{action "startComponent" view.content target="controller"}}>
+            Start
+          </a>
+        </li>
+        <li>
+          <a href="javascript:void(null)" data-toggle="modal" {{action "stopComponent" view.content target="controller"}}>
+            Stop
+          </a>
+        </li>
+      </ul>
     </div>
-    {{#if view.content.components.length}}
-    <div class="span3 host-components pull-right">
-        {{#each component in view.content.components}}
-          <button class="btn btn-{{bind view.buttonClass}}">
-            {{component.componentName}}
-            <i class="icon-{{unbound view.content.iconClass}}"></i>
-          </button>
-        {{/each}}
-    </div>
-    {{/if}}
+    {{/view}}
+    {{/each}}
+  </div>
+  {{/if}}
 </div>
 

+ 0 - 148
ambari-web/app/templates/main/hosts.hbs

@@ -1,148 +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="hosts" class="box">
-    <div class="box-header">
-        <div class="button-section">
-            <div class="btn-group">
-                <button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
-                    Rack
-                    <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu">
-                    <li><a href="#">Rack1</a></li>
-                    <li><a href="#">Rack2</a></li>
-                    <li><a href="#">Rack3</a></li>
-                </ul>
-            </div>
-            <div class="btn-group">
-                <button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
-                    Stop
-                    <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu">
-                    <li><a href="#">1</a></li>
-                    <li><a href="#">2</a></li>
-                </ul>
-            </div>
-            <a class="btn btn-primary decommission" href="#">
-                Decommission
-            </a>
-            <a class="btn btn-primary" href="#">
-                Delete
-            </a>
-            <a class="btn btn-inverse add-host-button" href="#">
-                <i class="icon-plus icon-white"></i>
-                Add New Host
-            </a>
-        </div>
-    </div>
-    <table class="table table-bordered table-striped">
-        <!--<colgroup>-->
-            <!--<col class="span1">-->
-            <!--<col class="span7">-->
-        <!--</colgroup>-->
-        <thead>
-            <tr>
-                <th>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </th>
-                <th>Name</th>
-                <th>Rack</th>
-                <th>Services</th>
-                <th>Components</th>
-            </tr>
-        </thead>
-        <tbody>
-            <tr>
-                <td>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </td>
-                <td>ip-12-12-12-1232.co2.ama.internal</td>
-                <td>Rack1</td>
-                <td>HDFS MapReduce</td>
-                <td>NameNode</td>
-            </tr>
-            <tr>
-                <td>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </td>
-                <td>ip-12-12-12-1232.co2.ama.internal</td>
-                <td>Rack1</td>
-                <td>HDFS MapReduce</td>
-                <td>NameNode</td>
-            </tr>
-            <tr>
-                <td>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </td>
-                <td>ip-12-12-12-1232.co2.ama.internal</td>
-                <td>Rack1</td>
-                <td>HDFS MapReduce</td>
-                <td>NameNode</td>
-            </tr>
-            <tr>
-                <td>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </td>
-                <td>ip-12-12-12-1232.co2.ama.internal</td>
-                <td>Rack1</td>
-                <td>HDFS MapReduce</td>
-                <td>NameNode</td>
-            </tr>
-            <tr>
-                <td>
-                    <label class="checkbox">
-                        <input type="checkbox">
-                    </label>
-                </td>
-                <td>ip-12-12-12-1232.co2.ama.internal</td>
-                <td>Rack1</td>
-                <td>HDFS MapReduce</td>
-                <td>NameNode</td>
-            </tr>
-        </tbody>
-    </table>
-    <div class="box-footer">
-        <hr />
-        <div class="footer-pagination">
-            <ul class="nav nav-pills">
-                <li class="disabled">Show Hosts</li>
-                <li class="dropdown">
-                    <a class="dropdown-toggle" data-toggle="dropdown" href="#">25<b class="caret"></b></a>
-                    <ul class="dropdown-menu">
-                        <li><a href="#">50</a></li>
-                        <li><a href="#">100</a></li>
-                        <li><a href="#">All</a></li>
-                    </ul>
-                </li>
-                <li class="disabled">1-25 of 400</li>
-            </ul>
-        </div>
-    </div>
-</div>

+ 0 - 51
ambari-web/app/templates/main/service/info/audit.hbs

@@ -1,51 +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.
--->
-
-<h5>Audit</h5>
-<table class="table table-bordered table-striped">
-  <thead>
-		<tr>
-      <th>Date/Time</th>
-			<th>Category</th>
-			<th>Operation</th>
-			<th>
-        <div class="btn-group">
-          <button class="btn dropdown-toggle" data-toggle="dropdown">Performed By <span class="caret"></span></button>
-          <ul class="dropdown-menu">
-            <li><label>{{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}All</label></li>
-            {{#each controller.performedBy}}
-              <li><label>{{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}{{userName}}</label></li>
-            {{/each}}
-            {{!view Ember.Checkbox checkedBinding="manualInstall"}}
-            {{!view Ember.Checkbox checkedBinding="localRepo"}}
-          </ul>
-        </div>
-      </th>
-		</tr>
-	</thead>
-	<tbody>
-    {{#each controller.content.serviceAudit}}
-      <tr>
-        <td>{{date}}</td>
-        <td>{{controller.content.label}}</td>
-        <td>{{controller.content.label}} {{operationName}}</td>
-        <td>{{user.userName}}</td>
-      </tr>
-    {{/each}}
-	</tbody>
-</table>

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

@@ -31,17 +31,11 @@
 		<div class="service-content">
 			{{#each component in controller.content.components}}
         {{#if component.type}}
-          <div class="service-links">{{component.componentName}}:<a {{action selectHost component.host href=true}} href="javascript:void(null)">{{component.host.hostName}}</a></div>
+          <div class="service-links">{{component.componentName}}: <a {{action selectHost component.host}} href="javascript:void(null)">{{component.host.hostName}}</a></div>
         {{else}}
-          <div class="service-links">{{component.componentName}}:<a {{action filterHosts component}} href="javascript:void(null)">{{component.componentName}}</a></div>
+          <div class="service-links">{{component.componentName}}: <a {{action filterHosts component}} href="javascript:void(null)">{{component.componentName}}</a></div>
         {{/if}}
 			{{/each}}
-			Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the
-			industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and
-			scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into
-			electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of
-			Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like
-			Aldus PageMaker including versions of Lorem Ipsum.
 		</div>
 	</div>
 	<div class="span6">

+ 15 - 0
ambari-web/app/utils/db.js

@@ -76,6 +76,14 @@ App.db.setLoginName = function(name) {
   localStorage.setObject('ambari',App.db.data);
 }
 
+// that works incorrectly
+App.db.setUser = function(user) {
+  console.log('TRACE: Entering db:setUser function');
+  App.db.data = localStorage.getObject('ambari');
+  App.db.data.app.user = user;
+  localStorage.setObject('ambari',App.db.data);
+}
+
 App.db.setAuthenticated = function(authenticated) {
   console.log('TRACE: Entering db:setAuthenticated function');
 
@@ -213,6 +221,13 @@ App.db.setServiceConfigProperties = function(configProperties) {
  *  getter methods
  */
 
+// that works incorrectly
+App.db.getUser = function() {
+  console.log('TRACE: Entering db:getUser function');
+  App.db.data = localStorage.getObject('ambari');
+  return App.db.data.app.user;
+}
+
 App.db.getLoginName = function() {
   console.log('Trace: Entering db:getLoginName function');
   App.db.data = localStorage.getObject('ambari');

+ 13 - 1
ambari-web/app/utils/helper.js

@@ -24,4 +24,16 @@ Em.CoreObject.reopen({
   t: function(key, attrs){
     return Em.I18n.t(key, attrs)
   }
-});
+});
+
+
+//Em.Object.getMixinProperties = function(){
+//  var properties = {};
+//  this.PrototypeMixin.mixins.forEach(function(i,mix){
+//    if(mix.properties) {
+//      properties = mix.properties;
+//    }
+//  });
+//
+//  return properties;
+//}

+ 16 - 1
ambari-web/app/utils/validator.js

@@ -36,11 +36,26 @@ module.exports = {
     return floatRegex.test(value);
   },
 
+  /**
+   * validate ip address with port
+   * @param value
+   * @return {Boolean}
+   */
   isIpAddress: function(value) {
-    var ipRegex = /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}$/;
+    var ipRegex = /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(\:[0-9]{1,5})?$/;
     return ipRegex.test(value);
   },
 
+  /**
+   * validate domain name with port
+   * @param value
+   * @return {Boolean}
+   */
+  isDomainName: function(value) {
+    var domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/;
+    return domainRegex.test(value);
+  },
+
   empty:function (e) {
     switch (e) {
       case "":

+ 309 - 0
ambari-web/app/views/common/grid.js

@@ -0,0 +1,309 @@
+/**
+ * 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 validator = require('utils/validator');
+
+App.GridFilterObject = Em.Object.extend({
+  checked:false
+});
+
+App.GridFilter = Em.View.extend({
+  tagName:"ul",
+  classNames:['filter'],
+  templateName:require('templates/common/grid/filter'),
+  attributeBindings:['style'],
+  getHeader:function () {
+    return this.get('header')
+  },
+  filters:function () {
+    return this.get('header._filters');
+  }.property('header._filters')
+});
+
+App.GridHeader = Em.View.extend({
+  templateName:require('templates/common/grid/header'),
+  tagName:'th',
+  filterable:true,
+  showFilter:false,
+  getGrid:function () {
+    return this.get('grid');
+  },
+  _filters:[],
+  doFilter:function () {
+    console.log(this.get('grid'));
+  },
+  toggleFilter:function () {
+    this.set('showFilter', 1 - this.get('showFilter'));
+  },
+  applyFilter:function () {
+    console.warn('APPLYING FILTERS');
+
+    var filters = this.get('_filters');
+    var filterValues = [];
+    $.each(filters, function(){
+      if(this.get('checked')) {
+        filterValues.push(this.get('value'));
+      }
+    });
+
+    var grid = this.get('grid');
+    grid.addFilters(this.get('name'), filterValues);
+    this.set('showFilter', false);
+  },
+  init:function () {
+    this._super();
+    if (!this.get('_filters').length) {
+      this.filterValues();
+      var thisHeader = this;
+      this.set('filter', App.GridFilter.extend({ header:thisHeader }));
+    }
+  },
+
+  filterValues:function () {
+    var gridFilters = this.get('grid._filters');
+    if (gridFilters && gridFilters[this.get('name')]) {
+      var filters = this.get('grid._filters')[this.get('name')];
+      // there should be something like filter preparing
+      var newFilters = [];
+      $.each(filters, function (i, v) {
+        newFilters.push(App.GridFilterObject.create({label:v, value:v}));
+      });
+
+      this.set('_filters', newFilters);
+    }
+  }.observes('grid._filters')
+});
+
+App.GridRow = Em.View.extend({
+  tagName:'tr',
+  init:function (options) {
+    var object = this.get('object');
+    var grid = this.get('grid');
+    var fieldNames = grid.get('fieldNames');
+    var template = '';
+
+    if (fieldNames) {
+      $.each(grid.get('fieldNames'), function (i, field) {
+        template += "<td>" + object.get(field) + "</td>";
+      });
+
+      this.set('template', Em.Handlebars.compile(template));
+    }
+    return this._super();
+  }
+});
+
+App.GridPage = Em.Object.extend({
+  activeClass:function () {
+    return this.get('active') ? "active" : "";
+  }.property('active'),
+  active:function () {
+    return parseInt(this.get('number')) == parseInt(this.get('pager.grid.currentPage'));
+  }.property('pager.grid.currentPage')
+});
+
+App.GridPager = Em.View.extend({
+
+  pages:[],
+  templateName:require('templates/common/grid/pager'),
+  classNames:['pagination'],
+
+  activatePrevPage:function () {
+    var current = this.get('grid.currentPage');
+    if (current > 1) this.set('grid.currentPage', current - 1);
+  },
+  activateNextPage:function () {
+    var current = this.get('grid.currentPage');
+    if (current < this.get('pages').length) this.set('grid.currentPage', current + 1);
+  },
+
+  prevPageDisabled:function () {
+    return this.get('grid.currentPage') > 1 ? false : "disabled";
+  }.property('grid.currentPage'),
+
+  nextPageDisabled:function () {
+    return this.get('grid.currentPage') < this.get('pages').length ? false : "disabled";
+  }.property('grid.currentPage'),
+
+  init:function () {
+    this._super();
+    this.clearPages()
+    this.pushPages();
+  },
+
+  activatePage:function (event) {
+    var page = event.context;
+    this.get('grid').set('currentPage', parseInt(event.context.get('number')));
+  },
+
+  clearPages:function () {
+    this.set('pages', []);
+  },
+
+  pushPages:function () {
+    var thisPager = this;
+    var pages = this.get('grid._pager.pages');
+    $.each(pages, function () {
+      var thisNumber = this;
+      thisPager.get('pages').push(App.GridPage.create({
+        number:thisNumber,
+        pager:thisPager
+      }));
+    })
+  }.observes('grid._pager')
+});
+
+App.Grid = Em.View.extend({
+  _columns:{}, // not used
+  _filters:{}, // prepared filters from data values
+  _pager:{pages:[1, 2, 3, 4, 5]}, // observed by pager to config it
+
+  _collection:{className:false, staticOptions:{}}, // collection config
+  currentPage:1,
+  fieldNames:[],
+  appliedFilters:{},
+  filteredArray:[],
+  columns:[],
+  collection:[],
+  initComleted:false,
+  rows:[],
+  templateName:require('templates/main/admin/audit'),
+
+  init:function () {
+    console.warn("  Grid INIT  ");
+    this._super();
+    this.prepareColumns(); // should be the 1
+    this.prepareCollection();
+    this.preparePager();
+  },
+
+  preparePager:function () {
+//    this.set('pager', App.GridPager.extend({ grid:this })); ask to hide
+  },
+
+  addFilters: function(field, values){
+    var filters = this.get('appliedFilters');
+    filters[field] = values;
+
+    var collection = this.get('_collection.className');
+    collection = collection.find();
+    arrayCollection = collection.filter(function(data) {
+      var oneFilterFail = false;
+      $.each(filters, function(fieldname, values){
+        if(values.length && values.indexOf(data.get(fieldname)) == -1) {
+          return oneFilterFail = true;
+        }
+      });
+      return !oneFilterFail;
+    });
+
+    this.set('filteredArray', arrayCollection);
+  },
+
+  prepareCollection:function () {
+    if (validator.empty(this.get('_collection.className'))) {
+      throw "_collection.className field is not defined";
+    }
+    var collection = this.get('_collection.className');
+    this.set('collection', collection.find(this.get('_collection.staticOptions')));
+  },
+
+  addColumn:function (options) {
+    options.grid = this;
+    if (validator.empty(options.name)) {
+      throw "define column name";
+    }
+
+    if (this.get('_columns.' + options.name)) {
+      throw "column with this '" + options.name + "' already exists";
+    }
+
+    var field = App.GridHeader.extend(options);
+    this.columns.push(field);
+
+    if (field.filterable || 1) { // .filterable - field not working :(
+      this.fieldNames.push(options.name);
+    }
+  },
+
+  clearColumns:function () {
+    this.set('_columns', {});
+    this.set('columns', []);
+    this.set('fieldNames', []);
+  },
+
+  prepareColumns:function () {
+    this.clearColumns();
+  },
+
+  prepareFilters:function () {
+    var thisGrid = this;
+    var collection = this.get('collection');
+    var fieldNames = this.get('fieldNames');
+    var options = {};
+
+    if (collection && collection.content) {
+      collection.forEach(function (object, i) {
+        $.each(fieldNames, function (j, field) {
+          if (!options[field]) {
+            options[field] = [];
+          }
+
+          var filter = object.get(field);
+          if (options[field].indexOf(filter) == -1) {
+            options[field].push(filter);
+          }
+        });
+      })
+
+      thisGrid.set('_filters', options);
+    }
+  }.observes('collection.length'),
+
+
+  clearRows:function () {
+    this.set('rows', [])
+  },
+
+  prepareRows:function () {
+    var collection = this.get('collection');
+    var thisGrid = this;
+    this.clearRows();
+    console.warn("PREPARE ROWS LEN:", collection.get('length'));
+    var i=1;
+
+    if (collection && collection.content) {
+      collection.forEach(function (object, i) {
+        var row = App.GridRow.extend({grid:thisGrid, object:object});
+        thisGrid.rows.push(row);
+      });
+    }
+  }.observes('collection.length'),
+
+  filteredRows:function () {
+    var collection = this.get('filteredArray');
+    var thisGrid = this;
+    this.clearRows();
+
+    collection.forEach(function (object) {
+      var row = App.GridRow.extend({grid:thisGrid, object:object});
+      thisGrid.rows.push(row);
+    });
+  }.observes('filteredArray')
+});

+ 0 - 0
ambari-web/app/views/installer/step1.js


+ 0 - 0
ambari-web/app/views/installer/step2.js


+ 0 - 0
ambari-web/app/views/installer/step3.js


+ 13 - 83
ambari-web/app/views/main/admin/audit.js

@@ -17,99 +17,29 @@
  */
 
 var App = require('app');
-var validator = require('utils/validator');
 
-App.GridHeader = Em.View.extend({
-  templateName:require('templates/common/grid/header'),
-  tagName:'th',
-  filterable:true,
-  getGrid:function () {
-    return this.get('grid');
-  },
-  doFilter:function () {
-    console.log(this.get('grid'));
-  },
-  click:function (event) {
-    // will be called when when an instance's
-    // rendered element is clicked
-  }
-});
+require('views/common/grid');
 
-App.MainAdminAuditView = Em.View.extend({
-  _columns:{},
-  columns:[],
-  initComleted:false,
-  collection:[],
-  templateName:require('templates/main/admin/audit'),
-  fieldNames:[],
-  init:function () {
+App.MainAdminAuditView = App.Grid.extend({
+  _collection: {className: App.ServiceAudit},
+  prepareColumns:function () {
     this._super();
-    if (!this.columns.length) { // init completed on this
-      this.prepareColumns();
-      this.prepareFilters();
-      this.prepareCollection();
-    }
-  },
-  prepareCollection:function () {
-    this.set('collection', App.ServiceAudit.find());
-  },
-
-  addColumn:function (options) {
-    options.grid = this;
-    if (validator.empty(options.name)) {
-      throw "define column name";
-    }
-
-    if (this.get('_columns.' + options.name)) {
-      throw "column with this '" + options.name + "' already exists";
-    }
 
-    var field = App.GridHeader.extend(options);
-    this.columns.push(field);
-
-//    console.log("FI:", field.filterable);
-    if (field.filterable || 1) { // .filterable - field not working :(
-      this.fieldNames.push(options.name);
-    }
-  },
-  prepareColumns:function () {
     this.addColumn({
       name:"date",
       label:Em.I18n.t("admin.audit.grid.date")
     });
+    this.addColumn({
+      name:"service.label",
+      label:Em.I18n.t("admin.audit.grid.service")
+    });
     this.addColumn({
       name:"operationName",
       label:Em.I18n.t("admin.audit.grid.operationName")
     });
-  },
-  prepareFilters:function () {
-    var collection = this.get('collection');
-    var fieldNames = this.get('fieldNames');
-    var options = {};
-
-    if (collection && collection.content && collection.content.length) {
-      collection.forEach(function (i, object) {
-        console.warn("INTO");
-        console.warn(object, object.content);
-        $.each(fieldNames, function (j, field) {
-          if (!options[field]) {
-            options[field] = [];
-          }
-//          var value = object.get(field); repair this
-//          options[field].push({value:value});
-        });
-      })
-
-      console.warn("SORT OPTIONS:", options);
-      this.set('fieldNames', false);
-//      indexOf
-
-//      console.warn("LENGTH:" + collection.content.length);
-//      console.warn("fieldNames:", this.get('fieldNames'));
-    }
-//    var fieldNames = this.get('fieldNames');
-
-//    $.each(this.get('collection'), function(){})
-
-  }.observes('collection')
+    this.addColumn({
+      name:"user.userName",
+      label:Em.I18n.t("admin.audit.grid.performedBy")
+    });
+  }
 });

+ 13 - 5
ambari-web/app/views/main/admin/menu.js

@@ -35,24 +35,32 @@ App.MainAdminMenuView = Em.CollectionView.extend({
     {
       route:'audit',
       label:'Audit'
-    },
+    }
+    /*,
     {
       route:'advanced',
       label:'Advanced'
     }
+    */
   ],
   tagName: "ul",
-  classNames: ["nav", "nav-list", "span2"],
+  classNames: ["nav", "nav-stacked", "nav-pills"],
 
-  activateView:function (route) {
+  init: function(){
+    this._super();
+    this.activateView(); // default selected menu
+  },
+
+  activateView:function () {
+    var route = App.get('router.mainAdminController.category');
     $.each(this._childViews, function () {
       this.set('active', (this.get('content.route') == route ? "active" : ""));
     });
-  },
+  }.observes('App.router.mainAdminController.category'),
 
   itemViewClass:Em.View.extend({
     classNameBindings:["active"],
     active:"",
-    template:Ember.Handlebars.compile('<a {{action adminNavigate view.content }} href="#"> {{unbound view.content.label}}</a>')
+    template:Ember.Handlebars.compile('<a {{action adminNavigate view.content.route }} href="#"> {{unbound view.content.label}}</a>')
   })
 });

+ 68 - 12
ambari-web/app/views/main/host.js

@@ -20,23 +20,79 @@ var App = require('app');
 
 App.MainHostView = Em.View.extend({
   templateName: require('templates/main/host'),
-  content: [],
+  content:function(){
+    return App.router.get('mainHostController.content');
+  }.property('App.router.mainHostController.content'),
+  componentsIds: [],
+  isFilterOpen: false,
+  isApplyDisabled: function(){
+    return !this.get('isFilterOpen')
+  }.property('isFilterOpen'),
+  btnGroupClass: function(){
+    return this.get('isFilterOpen') ? 'btn-group open' : 'btn-group';
+  }.property('isFilterOpen'),
 
-  ComponentCheckboxView: Em.Checkbox.extend({
-    content: null,
-    isChecked: false,
-    change: function(event) {
-      this.set('isChecked', !this.get('isChecked'));
-      App.router.get('mainHostController').setFilters(this.get('isChecked'), this.get('content'));
+  applyFilters: function(){
+    this.set('isFilterOpen', false);
+    App.router.get('mainHostController').filterByComponentsIds(this.get('componentsIds'));
+  },
+
+  clickFilterButton: function(){
+    var self = this;
+    this.set('isFilterOpen', !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 index = filters.indexOf(componentId);
+        $(this).attr('checked', index!=-1);
+      });
+      this.set('componentsIds', filters.toArray());
+
+      var dropDown = $('#filter-dropdown');
+      var firstClick = true;
+      $(document).bind('click', function(e) {
+        if (!firstClick && $(e.target).closest(dropDown).length == 0) {
+          self.set('isFilterOpen', false);
+          $(document).unbind('click');
+        }
+        firstClick = false;
+      });
     }
+  },
+  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) {
+        this.set('isChecked', !this.get('content.isChecked'));
+        App.router.get('mainHostController').onHostChecked(this.get('content'));
+      }
+    })
   }),
-  HostCheckboxView: Em.Checkbox.extend({
+  ComponentCheckboxView: Em.Checkbox.extend({
     content: null,
-    isChecked: false,
+    elementId: function(){
+      return 'component-' + this.get('content.id');
+    }.property('content.id'),
+    classNames: ['filter-component'],
+    parentView: function(){
+      return this._parentView.templateData.view;
+    }.property(),
     change: function(event) {
-      this.set('isChecked', !this.get('content.isChecked'));
-      App.router.get('mainHostController').onHostChecked(this.get('isChecked'), this.get('content.id'));
+      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);
     }
   })
-
 });

+ 7 - 3
ambari-web/app/views/main/host/summary.js

@@ -23,7 +23,11 @@ App.MainHostSummaryView = Em.View.extend({
   content:function(){
     return App.router.get('mainHostDetailsController.content');
   }.property('App.router.mainHostDetailsController.content'),
-  buttonClass: function(){
-    return this.get('startComponents') ? 'success' : 'danger';
-  }.property('startComponents')
+  ComponentButtonView: Em.View.extend({
+    content: null,
+    buttonClass: function(){
+      return this.get('content.workStatus') ? 'btn btn-success dropdown-toggle' : 'btn btn-danger dropdown-toggle';
+    }.property('content.workStatus')
+  })
+
 });

+ 51 - 3
ambari-web/app/views/main/service/info/audit.js

@@ -18,7 +18,55 @@
 
 var App = require('app');
 
-App.MainServiceInfoAuditView = Em.View.extend({
-  templateName: require('templates/main/service/info/audit'),
-  content: App.ServiceAudit.find()
+require('views/common/grid');
+
+App.MainServiceInfoAuditView = App.Grid.extend({
+//  audits: function() {
+//    return App.router.get('mainServiceInfoAuditController.content').get('serviceAudit');
+//  }.property('App.router.mainServiceInfoAuditController.content'),
+
+  prepareCollection:function () {
+    var audits = App.router.get('mainServiceInfoAuditController.content').get('serviceAudit');
+    console.warn(" AUDITS: ", audits);
+    this.set('collection', audits);
+  },
+
+  addFilters: function(field, values){
+    var filters = this.get('appliedFilters');
+    filters[field] = values;
+    var collection = App.router.get('mainServiceInfoAuditController.content').get('serviceAudit');
+    arrayCollection = collection.filter(function(data) {
+      var oneFilterFail = false;
+      $.each(filters, function(fieldname, values){
+        if(values.indexOf(data.get(fieldname)) == -1) {
+          return oneFilterFail = true;
+        }
+      });
+      return !oneFilterFail;
+    });
+
+    this.set('filteredArray', arrayCollection);
+  },
+
+  _collection: {className: App.ServiceAudit},
+  prepareColumns:function () {
+    this._super();
+
+    this.addColumn({
+      name:"date",
+      label:Em.I18n.t("admin.audit.grid.date")
+    });
+    this.addColumn({
+      name:"service.label",
+      label:Em.I18n.t("admin.audit.grid.service")
+    });
+    this.addColumn({
+      name:"operationName",
+      label:Em.I18n.t("admin.audit.grid.operationName")
+    });
+    this.addColumn({
+      name:"user.userName",
+      label:Em.I18n.t("admin.audit.grid.performedBy")
+    });
+  }
 });

+ 13 - 12
ambari-web/test/login_test.js

@@ -21,22 +21,23 @@ require('controllers/login_controller');
 
 describe('App.LoginController', function () {
 
+  var loginController = App.LoginController.create();
+
   describe('#validateCredentials()', function () {
-    it('should return false if no username is present', function () {
-      var loginController = App.LoginController.create();
+    it('should return undefined if no username is present', function () {
       loginController.set('loginName', '');
-      expect(loginController.validateCredentials()).to.equal(false);
+      expect(loginController.validateCredentials()).to.equal(undefined);
     })
-    it('should return false if no password is present', function () {
-      var loginController = App.LoginController.create();
+    it('should return undefined if no password is present', function () {
       loginController.set('password', '');
-      expect(loginController.validateCredentials()).to.equal(false);
+      expect(loginController.validateCredentials()).to.equal(undefined);
     })
-    it('should return true if username and password are the same (dummy until actual integration)', function () {
-      var loginController = App.LoginController.create();
-      loginController.set('loginName', 'abc');
-      loginController.set('password', 'abc');
-      expect(loginController.validateCredentials()).to.equal(true);
+    /*
+    it('should return the user object with the specified username and password (dummy until actual integration)', function () {
+      loginController.set('loginName', 'admin');
+      loginController.set('password', 'admin');
+      expect(loginController.validateCredentials().get('loginName'), 'admin');
     })
+    */
   })
-})
+})

+ 34 - 0
ambari-web/test/main/dashboard_test.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');
+
+require('models/alert'); 
+App.Alert.FIXTURES = [{ status: 'ok' }, { status: 'corrupt' }, { status: 'corrupt',}];
+require('controllers/main/dashboard');
+ 
+describe('MainDashboard', function () {
+ 
+  var controller = App.MainDashboardController.create();
+  
+  describe('#alertsCount', function () {
+    it('should return 2 if 2 alerts has status corrupt', function () {
+        expect(controller.get('alertsCount')).to.equal(2);
+    })
+  })
+})

+ 49 - 0
ambari-web/test/main/host/details_test.js

@@ -0,0 +1,49 @@
+/**
+ * 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 Ember = require('ember');
+ var App = require('app');
+ 
+require('controllers/main/host/details');
+
+describe('MainHostdetails', function () {
+  var controller = App.MainHostDetailsController.create();
+  controller.content = Ember.Object.create({});
+   
+  describe('#setBack(value)', function () {
+    it('should return true if value is true', function () {
+      controller.setBack(true);
+      expect(controller.get('isFromHosts')).to.equal(true);
+    })
+  }),
+  describe('#workStatus positive', function () {
+    it('should return true if workstatus is true', function () {
+      controller.content.set('workStatus',true);   
+      expect(controller.get('isStarting')).to.equal(true);
+      }),
+    it('should return false if workStatus is true', function () {
+      expect(controller.get('isStopping')).to.equal(false);
+    })
+    it('should return false if workstatus is false', function () {
+      controller.content.set('workStatus',false);   
+      expect(controller.get('isStarting')).to.equal(false);
+      }),
+    it('should return true if workStatus is false', function () {
+      expect(controller.get('isStopping')).to.equal(true);
+    })
+  })
+})

+ 84 - 0
ambari-web/test/main/host_test.js

@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+require('models/cluster');
+require('models/service');
+require('models/pagination');
+require('controllers/main/host');
+
+
+describe('MainHostController', function () {
+    describe('#sortByName()', function () {
+        it('should change isSort value to true', function () {
+            var mainHostController = App.MainHostController.create();
+            mainHostController.set('isSort', false);
+            mainHostController.sortByName();
+            expect(mainHostController.get('isSort')).to.equal(true);
+        });
+
+
+        it('should inverse sortingAsc ', function () {
+            var mainHostController = App.MainHostController.create();
+            mainHostController.set('sortingAsc', false);
+            mainHostController.sortByName();
+            expect(mainHostController.get('sortingAsc')).to.equal(true);
+            mainHostController.sortByName();
+            expect(mainHostController.get('sortingAsc')).to.equal(false);
+        })
+    });
+
+
+    describe('#showNextPage, #showPreviousPage()', function () {
+        it('should change rangeStart according to page', function () {
+            var mainHostController = App.MainHostController.create();
+            mainHostController.set('pageSize', 3);
+            mainHostController.showNextPage();
+            expect(mainHostController.get('rangeStart')).to.equal(3);
+            mainHostController.showPreviousPage();
+            expect(mainHostController.get('rangeStart')).to.equal(0);
+        })
+    });
+
+
+    describe('#sortClass()', function () {
+        it('should return \'icon-arrow-down\' if sortingAsc is true', function () {
+            var mainHostController = App.MainHostController.create({});
+            mainHostController.set('sortingAsc', true);
+            expect(mainHostController.get('sortClass')).to.equal('icon-arrow-down');
+        });
+        it('should return \'icon-arrow-up\' if sortingAsc is false', function () {
+            var mainHostController = App.MainHostController.create({});
+            mainHostController.set('sortingAsc', false);
+            expect(mainHostController.get('sortClass')).to.equal('icon-arrow-up');
+        })
+    });
+
+
+    describe('#allChecked', function () {
+        it('should fill selectedhostsids array', function () {
+            var mainHostController = App.MainHostController.create();
+            mainHostController.set('allChecked', false);
+            expect(mainHostController.get('selectedHostsIds').length).to.equal(0);
+            mainHostController.set('allChecked', true);
+            expect(!!(mainHostController.get('selectedHostsIds').length)).to.equal(true);
+        })
+    });
+
+
+});

+ 32 - 0
ambari-web/test/main/item_test.js

@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var App = require('app');
+require('views/common/modal_popup');
+require('controllers/main/service/item');
+
+describe('App.MainServiceItemController', function () {
+
+    describe('#showRebalancer', function () {
+        it('should return true if serviceName is hdfs', function () {
+            var mainServiceItemController = App.MainServiceItemController.create({
+            });
+            mainServiceItemController.content.set('serviceName', 'hdfs');
+            expect(mainServiceItemController.get('showRebalancer')).to.equal(true);
+        })
+    })
+})

+ 164 - 0
ambari-web/test/utils/form_field_test.js

@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+require('models/form');
+
+
+/*
+ * formField.isValid property doesn't update correctly, so I have to work with errorMessage property
+ */
+describe('App.FormField', function () {
+
+  describe('#validate()', function () {
+    /*DIGITS TYPE*/
+    it('123456789 is correct digits', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'digits');
+      formField.set('value', 123456789);
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('"a33bc" is incorrect digits', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'digits');
+      formField.set('value', 'a33bc');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    /*DIGITS TYPE END*/
+    /*NUMBER TYPE*/
+    it('+1234 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '+1234');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('-1234 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-1234');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('-1.23.6 is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-1.23.6');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    it('+1.6 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', +1.6);
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('-1.6 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', -1.6);
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('1.6 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', 1.6);
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('-.356 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-.356');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('+.356 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '+.356');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('-1. is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-1.');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    it('+1. is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '+1.');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    it('1. is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '1.');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    it('-1,23,6 is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-1,23,6');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    it('-1234567890 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '-1234567890');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('+1234567890 is correct number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '+1234567890');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(true);
+    })
+    it('123eed is incorrect number', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'number');
+      formField.set('value', '123eed');
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    /*NUMBER TYPE END*/
+    /*REQUIRE*/
+    it('Required field shouldn\'t be empty', function () {
+      var formField = App.FormField.create();
+      formField.set('displayType', 'string');
+      formField.set('value', '');
+      formField.set('isRequired', true);
+      formField.validate();
+      expect(formField.get('errorMessage') === '').to.equal(false);
+    })
+    /*REQUIRE END*/
+
+  })
+})

+ 61 - 0
ambari-web/test/utils/validator_test.js

@@ -137,5 +137,66 @@ describe('validator', function () {
     })
 
   })
+  /*describe('#isIpAddress(value)', function () {
+    it('"127.0.0.1" - valid IP', function () {
+      expect(validator.isIpAddress('127.0.0.1')).to.equal(true);
+    })
+    it('"227.3.67.196" - valid IP', function () {
+      expect(validator.isIpAddress('227.3.67.196')).to.equal(true);
+    })
+    it('"327.0.0.0" - invalid IP', function () {
+      expect(validator.isIpAddress('327.0.0.0')).to.equal(false);
+    })
+    it('"127.0.0." - invalid IP', function () {
+      expect(validator.isIpAddress('127.0.0.')).to.equal(false);
+    })
+    it('"127.0." - invalid IP', function () {
+      expect(validator.isIpAddress('127.0.')).to.equal(false);
+    })
+    it('"127" - invalid IP', function () {
+      expect(validator.isIpAddress('127')).to.equal(false);
+    })
+    it('"127.333.0.1" - invalid IP', function () {
+      expect(validator.isIpAddress('127.333.0.1')).to.equal(false);
+    })
+    it('"127.0.333.1" - invalid IP', function () {
+      expect(validator.isIpAddress('127.0.333.1')).to.equal(false);
+    })
+    it('"127.0.1.333" - invalid IP', function () {
+      expect(validator.isIpAddress('127.0.1.333')).to.equal(false);
+    })
+    it('"127.0.0.0:45555" - valid IP', function () {
+      expect(validator.isIpAddress('127.0.0.0:45555')).to.equal(true);
+    })
+    it('"327.0.0.0:45555" - invalid IP', function () {
+      expect(validator.isIpAddress('327.0.0.0:45555')).to.equal(false);
+    })
+    it('"0.0.0.0" - invalid IP', function () {
+      expect(validator.isIpAddress('0.0.0.0')).to.equal(false);
+    })
+    it('"0.0.0.0:12" - invalid IP', function () {
+      expect(validator.isIpAddress('0.0.0.0:12')).to.equal(false);
+    })
+    it('"1.0.0.0:0" - invalid IP', function () {
+      expect(validator.isIpAddress('1.0.0.0:0')).to.equal(false);
+    })
+  })*/
+  describe('#isDomainName(value)', function () {
+    it('"google.com" - valid Domain Name', function () {
+      expect(validator.isDomainName('google.com')).to.equal(true);
+    })
+    it('"google" - invalid Domain Name', function () {
+      expect(validator.isDomainName('google')).to.equal(false);
+    })
+    it('"123.123" - invalid Domain Name', function () {
+      expect(validator.isDomainName('123.123')).to.equal(false);
+    })
+    it('"4goog.le" - valid Domain Name', function () {
+      expect(validator.isDomainName('4goog.le')).to.equal(true);
+    })
+    it('"55454" - invalid Domain Name', function () {
+      expect(validator.isDomainName('55454')).to.equal(false);
+    })
+  })
 
 })