Browse Source

AMBARI-2338. Customizable Dashboard for Ambari Web. (Xi Wang via yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/trunk@1491658 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 years ago
parent
commit
cd7aae3d9f
39 changed files with 4292 additions and 271 deletions
  1. 3 3
      ambari-web/app/controllers/global/update_controller.js
  2. 8 2
      ambari-web/app/mappers/service_mapper.js
  3. 27 0
      ambari-web/app/messages.js
  4. 3 1
      ambari-web/app/models/service/hdfs.js
  5. 3 1
      ambari-web/app/models/service/mapreduce.js
  6. 667 207
      ambari-web/app/styles/application.less
  7. 4 2
      ambari-web/app/templates/main/charts/linear_time.hbs
  8. 41 43
      ambari-web/app/templates/main/dashboard.hbs
  9. 58 0
      ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs
  10. 47 0
      ambari-web/app/templates/main/dashboard/plus_button_filter.hbs
  11. 1 1
      ambari-web/app/templates/main/service.hbs
  12. 25 0
      ambari-web/app/views.js
  13. 7 2
      ambari-web/app/views/common/chart/linear_time.js
  14. 32 6
      ambari-web/app/views/common/chart/pie.js
  15. 273 0
      ambari-web/app/views/main/dashboard.js
  16. 311 0
      ambari-web/app/views/main/dashboard/widget.js
  17. 203 0
      ambari-web/app/views/main/dashboard/widgets/datanode_live.js
  18. 200 0
      ambari-web/app/views/main/dashboard/widgets/hbase_average_load.js
  19. 151 0
      ambari-web/app/views/main/dashboard/widgets/hbase_links.js
  20. 134 0
      ambari-web/app/views/main/dashboard/widgets/hbase_master_heap.js
  21. 192 0
      ambari-web/app/views/main/dashboard/widgets/hbase_regions_in_transition.js
  22. 144 0
      ambari-web/app/views/main/dashboard/widgets/hdfs_capacity.js
  23. 101 0
      ambari-web/app/views/main/dashboard/widgets/hdfs_links.js
  24. 105 0
      ambari-web/app/views/main/dashboard/widgets/jobtracker_cpu.js
  25. 127 0
      ambari-web/app/views/main/dashboard/widgets/jobtracker_heap.js
  26. 203 0
      ambari-web/app/views/main/dashboard/widgets/jobtracker_rpc.js
  27. 132 0
      ambari-web/app/views/main/dashboard/widgets/jobtracker_uptime.js
  28. 94 0
      ambari-web/app/views/main/dashboard/widgets/mapreduce_links.js
  29. 77 0
      ambari-web/app/views/main/dashboard/widgets/mapreduce_slots.js
  30. 34 0
      ambari-web/app/views/main/dashboard/widgets/metrics_cpu.js
  31. 34 0
      ambari-web/app/views/main/dashboard/widgets/metrics_load.js
  32. 34 0
      ambari-web/app/views/main/dashboard/widgets/metrics_memory.js
  33. 34 0
      ambari-web/app/views/main/dashboard/widgets/metrics_network.js
  34. 104 0
      ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js
  35. 132 0
      ambari-web/app/views/main/dashboard/widgets/namenode_heap.js
  36. 204 0
      ambari-web/app/views/main/dashboard/widgets/namenode_rpc.js
  37. 134 0
      ambari-web/app/views/main/dashboard/widgets/namenode_uptime.js
  38. 205 0
      ambari-web/app/views/main/dashboard/widgets/tasktracker_live.js
  39. 4 3
      ambari-web/vendor/scripts/jquery.ui.sortable.js

+ 3 - 3
ambari-web/app/controllers/global/update_controller.js

@@ -79,8 +79,9 @@ App.UpdateController = Em.Controller.extend({
     var self = this;
     self.set('isUpdated', false);
     var servicesUrl = isInitialLoad ? 
-        this.getUrl('/data/dashboard/services.json', '/services?fields=components/ServiceComponentInfo,components/host_components,components/host_components/HostRoles') : 
-        this.getUrl('/data/dashboard/services.json', '/services?fields=components/ServiceComponentInfo,components/host_components,components/host_components/HostRoles,components/host_components/metrics/jvm/memHeapUsedM,components/host_components/metrics/jvm/memHeapCommittedM,components/host_components/metrics/mapred/jobtracker/trackers_decommissioned');
+      this.getUrl('/data/dashboard/services.json', '/services?fields=components/ServiceComponentInfo,components/host_components,components/host_components/HostRoles') :
+      // this.getUrl('/data/dashboard/services.json', '/services?fields=components/ServiceComponentInfo,components/host_components,components/host_components/HostRoles,components/host_components/metrics/jvm/memHeapUsedM,components/host_components/metrics/jvm/memHeapCommittedM,components/host_components/metrics/mapred/jobtracker/trackers_decommissioned,components/host_components/metrics/cpu,components/host_components/metrics/rpc/RpcQueueTime_avg_time');
+      this.getUrl('/data/dashboard/services.json', '/services?fields=components/ServiceComponentInfo,components/host_components,components/host_components/HostRoles,components/host_components/metrics');
     var callback = callback || function (jqXHR, textStatus) {
       self.set('isUpdated', true);
     };
@@ -89,5 +90,4 @@ App.UpdateController = Em.Controller.extend({
       });
   }
 
-
 });

+ 8 - 2
ambari-web/app/mappers/service_mapper.js

@@ -76,7 +76,10 @@ App.servicesMapper = App.QuickDataMapper.create({
     dfs_under_replicated_blocks: 'nameNodeComponent.ServiceComponentInfo.UnderReplicatedBlocks',
     dfs_total_files: 'nameNodeComponent.ServiceComponentInfo.TotalFiles',
     upgrade_status: 'nameNodeComponent.ServiceComponentInfo.UpgradeFinalized',
-    safe_mode_status: 'nameNodeComponent.ServiceComponentInfo.Safemode'
+    safe_mode_status: 'nameNodeComponent.ServiceComponentInfo.Safemode',
+
+    name_node_cpu: 'nameNodeComponent.host_components[0].metrics.cpu.cpu_wio',
+    name_node_rpc: 'nameNodeComponent.host_components[0].metrics.rpc.RpcQueueTime_avg_time'
   },
   mapReduceConfig: {
     version: 'jobTrackerComponent.ServiceComponentInfo.Version',
@@ -100,7 +103,10 @@ App.servicesMapper = App.QuickDataMapper.create({
     maps_waiting: 'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_maps',
     reduces_running: 'jobTrackerComponent.ServiceComponentInfo.jobtracker.running_reduces',
     reduces_waiting: 'jobTrackerComponent.ServiceComponentInfo.jobtracker.waiting_reduces',
-    trackers_decommissioned: 'jobTrackerComponent.host_components[0].metrics.mapred.jobtracker.trackers_decommissioned'
+    trackers_decommissioned: 'jobTrackerComponent.host_components[0].metrics.mapred.jobtracker.trackers_decommissioned',
+
+    job_tracker_cpu: 'jobTrackerComponent.host_components[0].metrics.cpu.cpu_wio',
+    job_tracker_rpc: 'jobTrackerComponent.host_components[0].metrics.rpc.RpcQueueTime_avg_time'
   },
   hbaseConfig: {
     version: 'masterComponent.ServiceComponentInfo.Version',

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

@@ -70,6 +70,7 @@ Em.I18n.translations = {
   'common.regionServer':'RegionServer',
   'common.taskTracker':'TaskTracker',
   'common.dataNode':'DataNode',
+  'common.more': 'More...',
   'common.print':'Print',
   'common.deploy':'Deploy',
   'common.message':'Message',
@@ -133,6 +134,7 @@ Em.I18n.translations = {
   'common.errorPopup.header': 'An error has been encountered',
   'common.use': 'Use',
   'common.stacks': 'Stacks',
+  'common.reset': 'Reset',
 
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
@@ -970,6 +972,31 @@ Em.I18n.translations = {
   'dashboard.clusterMetrics.memory':'Memory Usage',
   'dashboard.clusterMetrics.network':'Network Usage',
 
+  'dashboard.widgets': 'Cluster Status and Metrics',
+
+  'dashboard.widgets.NameNodeHeap': 'NameNode Heap',
+  'dashboard.widgets.NameNodeCpu': 'NameNode CPU WIO',
+  'dashboard.widgets.NameNodeCapacity': 'HDFS Capacity',
+  'dashboard.widgets.JobTrackerHeap': 'JobTracker Heap',
+  'dashboard.widgets.JobTrackerCpu': 'JobTracker CPU WIO',
+  'dashboard.widgets.JobTrackerCapacity': 'JobTracker Capacity',
+  'dashboard.widgets.DataNodeUp': 'DataNode Live',
+  'dashboard.widgets.TaskTrackerUp': 'TaskTracker Live',
+  'dashboard.widgets.NameNodeRpc': 'NameNode RPC',
+  'dashboard.widgets.JobTrackerRpc': 'JobTracker RPC',
+  'dashboard.widgets.MapReduceSlots': 'MapReduce Slots',
+  'dashboard.widgets.mapSlots': 'Map Slots',
+  'dashboard.widgets.reduceSlots': 'Reduce Slots',
+  'dashboard.widgets.nothing': 'No Widget to Add',
+  'dashboard.widgets.NameNodeUptime': 'NameNode Uptime',
+  'dashboard.widgets.JobTrackerUptime': 'JobTracker Uptime',
+  'dashboard.widgets.HDFSLinks': 'HDFS Links',
+  'dashboard.widgets.MapReduceLinks': 'MapReduce Links',
+  'dashboard.widgets.HBaseLinks': 'HBase Links',
+  'dashboard.widgets.HBaseAverageLoad': 'HBase Ave Load',
+  'dashboard.widgets.HBaseMasterHeap': 'HBase MasterHeap',
+  'dashboard.widgets.HBaseRegionsInTransition': 'Region InTransition',
+
 
   'dashboard.services':'Services',
   'dashboard.services.hosts':'Hosts',

+ 3 - 1
ambari-web/app/models/service/hdfs.js

@@ -37,7 +37,9 @@ App.HDFSService = App.Service.extend({
   dfsUnderReplicatedBlocks: DS.attr('number'),
   dfsTotalFiles: DS.attr('number'),
   upgradeStatus: DS.attr('boolean'),
-  safeModeStatus: DS.attr('string')
+  safeModeStatus: DS.attr('string'),
+  nameNodeCpu: DS.attr('number'),
+  nameNodeRpc: DS.attr('number')
 });
 
 App.HDFSService.FIXTURES = [];

+ 3 - 1
ambari-web/app/models/service/mapreduce.js

@@ -39,7 +39,9 @@ App.MapReduceService = App.Service.extend({
   mapsWaiting: DS.attr('number'),
   reducesRunning: DS.attr('number'),
   reducesWaiting: DS.attr('number'),
-  trackersDecommissioned: DS.attr('number')
+  trackersDecommissioned: DS.attr('number'),
+  jobTrackerCpu: DS.attr('number'),
+  jobTrackerRpc: DS.attr('number')
 });
 
 App.MapReduceService.FIXTURES = [];

+ 667 - 207
ambari-web/app/styles/application.less

@@ -21,6 +21,7 @@
   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%);
+  filter: progid:dximagetransform.microsoft.gradient(startColorstr= @start, endColorstr= @stop, GradientType=0); // for IE9-
 }
 
 html {
@@ -831,6 +832,86 @@ a:focus {
     }
   }
 }
+.sixty-percent-width-modal-edit-widget {
+  .modal {
+    width: 60%;
+    margin: 0 0 0 -30%;
+    max-height: 544px;
+    top: 5%;
+  }
+  .modal-header{
+    min-width: 650px;
+  }
+  .modal-footer{
+    min-width: 650px;
+  }
+
+  .modal-body {
+
+    max-height: 403px;
+    min-width: 650px;
+
+    #slider-range {
+      margin-top: 40px;
+      margin-bottom: 20px;
+      margin-left: 38px;
+      max-width: 630px;
+      max-height: 15px;
+    }
+    #min-height-limit .span9{
+      min-height: 15px;
+    }
+    #min-height-limit .progress{
+      margin-left: 40px;
+      .bar-success {
+        background-image: linear-gradient(to bottom, #95b000, #95a800);
+        background-repeat: repeat-x;
+        filter: progid:dximagetransform.microsoft.gradient(startColorstr='#95b000', endColorstr='#95a800', GradientType=0);
+      }
+      .bar-warning {
+        background-image: linear-gradient(to bottom, #FF9E00, #FF8E00);
+        background-repeat: repeat-x;
+        filter: progid:dximagetransform.microsoft.gradient(startColorstr='#FF9E00', endColorstr='#FF8E00', GradientType=0);
+      }
+      .bar-danger {
+        background-image: linear-gradient(to bottom, #B81000, #B80000);
+        background-repeat: repeat-x;
+        filter: progid:dximagetransform.microsoft.gradient(startColorstr='#B81000', endColorstr='#B80000', GradientType=0);
+      }
+    }
+
+    .value-on-slider{
+      max-height: 100px;
+      margin-bottom: 40px;
+      margin-top: 10px;
+      text-align: center;
+    }
+    #slider-value1{
+      max-width: 65px;
+    }
+    #slider-value2{
+      max-width: 250px;
+    }
+    #slider-value3{
+      max-width: 250px;
+    }
+    #slider-value4{
+      max-width: 65px;
+    }
+    .slider-error{
+      color: #b94a48;
+      .help-inline{
+        color: #b94a48;
+      }
+    }
+    .slider-error input{
+      border-color: #b94a48;
+      -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+      -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+      box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    }
+  }
+}
 .sixty-percent-width-modal {
 
   .modal {
@@ -844,233 +925,235 @@ a:focus {
 
     max-height: 403px;
 
-    .form-horizontal{
+.form-horizontal{
 
-      .add-cluster-1{
-          width: 100%;
-          height : auto;
-          min-height : 350px;
-          margin: 0 auto;
 
-        .each-row{
-          margin-top: 10px;
-        }
+.add-cluster-1{
+width: 100%;
+height : auto;
+min-height : 350px;
+margin: 0 auto;
 
-          .add-cluster-1-1{
-            width: 100%;
-            height : 100%;
-            float: left;
-            div.error{
-              color: #b94a48;
-              .help-inline{
-                color: #b94a48;
-              }
-            }
+.each-row{
+margin-top: 10px;
+}
 
-            div.error input{
-              border-color: #b94a48;
-              -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-              -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-              box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-            }
-          }
-          .add-cluster-1-3{
-            width: 45%;
-            height : 100%;
-            float: right;
-          }
-          .add-cluster-1-2{
-            width: 10%;
-            height : 100%;
-            float: left;
+.add-cluster-1-1{
+  width: 100%;
+  height : 100%;
+  float: left;
+  div.error{
+    color: #b94a48;
+    .help-inline{
+      color: #b94a48;
+    }
+  }
 
-            .add-cluster-1-2-1{
-              width: 100%;
-              height : 40%;
-            }
-            .add-cluster-1-2-2{
-              width: 100%;
-              height : 10%;
-            }
-            .add-cluster-1-2-3{
-              width: 100%;
-              height : 50%;
-            }
-            .middle-line{
-              width : 2%;
-              margin : 0 auto;
-              height : 100%;
-              background-color: #000000;
-            }
-            .add-cluster-center-most-div{
-              height : 30%;
-              text-align: center;
-              position: relative;
-              top: 30%;
-            }
-          }
-        }
+  div.error input{
+    border-color: #b94a48;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  }
+}
+.add-cluster-1-3{
+  width: 45%;
+  height : 100%;
+  float: right;
+}
+.add-cluster-1-2{
+  width: 10%;
+  height : 100%;
+  float: left;
 
-      .add-cluster-2{
-        margin : 0 auto;
-        height : auto;
-        min-height : 350px;
-        table{
-          width : 60%;
-          margin : 0 20%;
+  .add-cluster-1-2-1{
+    width: 100%;
+    height : 40%;
+  }
+  .add-cluster-1-2-2{
+    width: 100%;
+    height : 10%;
+  }
+  .add-cluster-1-2-3{
+    width: 100%;
+    height : 50%;
+  }
+  .middle-line{
+    width : 2%;
+    margin : 0 auto;
+    height : 100%;
+    background-color: #000000;
+  }
+  .add-cluster-center-most-div{
+    height : 30%;
+    text-align: center;
+    position: relative;
+    top: 30%;
+  }
+}
+}
 
-          .spacer{
-            height: 20px;
-          }
+.add-cluster-2{
+margin : 0 auto;
+height : auto;
+min-height : 350px;
+table{
+width : 60%;
+margin : 0 20%;
 
-          tr.error{
-            color: #b94a48;
-            .help-inline{
-              color: #b94a48;
-            }
+.spacer{
+  height: 20px;
+}
 
-            input{
-              border-color: #b94a48;
-              -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-              -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-              box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-            }
-          }
+tr.error{
+  color: #b94a48;
+  .help-inline{
+    color: #b94a48;
+  }
 
-          td {
-            width : 50%;
-            a.btn-success{
-              margin-left: 30%;
-            }
-          }
+  input{
+    border-color: #b94a48;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  }
+}
 
-        }
+td {
+  width : 50%;
+  a.btn-success{
+    margin-left: 30%;
+  }
+}
 
-      }
+}
 
+}
 
-      .add-data-set{
-        width: 80%;
-        margin: 0 auto;
-        table{
-          width: 100%;
-        }
-        tr.error{
-          color: #b94a48;
-          .help-inline{
-            color: #b94a48;
-          }
-        }
 
-        div.error{
-          color: #b94a48;
-          .help-inline{
-            color: #b94a48;
-          }
-        }
+.add-data-set{
+width: 80%;
+margin: 0 auto;
+table{
+width: 100%;
+}
+tr.error{
+color: #b94a48;
+.help-inline{
+  color: #b94a48;
+}
+}
 
-        div.error input{
-          border-color: #b94a48;
-          -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-        }
-        td.percent25 {
-          width: 25%;
-        }
-        td.spacer{
-          height: 10px;
-        }
-        td{
-          .btn-group{
-            display : inline;
-            span.caret{
-              float : right;
-            }
-            ul.dropdown-menu{
-              margin-top:15px;
-            }
-          }
-          .ember-view{
-            display : inline;
-          }
+div.error{
+color: #b94a48;
+.help-inline{
+  color: #b94a48;
+}
+}
 
-        }
+div.error input{
+border-color: #b94a48;
+-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
 
-        input.hyper-mini{
-          width: 20px;
-        }
+td.percent25 {
+width: 25%;
+}
+td.spacer{
+height: 10px;
+}
+td{
+.btn-group{
+  display : inline;
+  span.caret{
+    float : right;
+  }
+  ul.dropdown-menu{
+    margin-top:15px;
+  }
+}
+.ember-view{
+  display : inline;
+}
 
-        .targetClusterDD{
-          min-width : 170px;
-        }
+}
 
-      }
-      .each-row{
-        margin-top: 10px;
-      }
-      .each-row.control-label{
-        float:left;
-        clear:both;
-      }
-      .override-controls{
-        .icon-ok-sign {
-          color: #5AB400;
-        }
-        #filter-dropdown-div {
-          .dropdown-menu {
-            right: 0;
-            left: auto;
-          }
-        }
-        #component-dropdown-div {
-          vertical-align: top;
-          padding-left: 5px;
-          .dropdown-menu {
-            right: 0;
-            left: auto;
-          }
-        }
-        .input-append{
-          input{
-            -webkit-border-radius: 4px 0 0 4px;
-            -moz-border-radius: 4px 0 0 4px;
-            border-radius: 4px 0 0 4px;
-          }
-          .btn-group{
-            display: inline-block;
-            margin-left: -1px;
-            .btn{
-              -webkit-border-radius: 0 4px 4px 0;
-              -moz-border-radius: 0 4px 4px 0;
-              border-radius: 0 4px 4px 0;
-            }
-          }
-        }
-        .hosts-table-container{
-          width:100%;
-          height: 270px;
-          overflow: auto;
-          border: 1px solid #eee;
-        }
-        table{
-          th {
-            background-color: #d9edf7;
-          }
-          margin: 0 auto;
-        }
-        .message{
-          color: #777;
-        }
-      }
-      .control-label{
-         width:auto;
-      }
-      .overrideSelectBox {
-        width:100%;
-      }
-    }
+input.hyper-mini{
+width: 20px;
+}
+
+.targetClusterDD{
+min-width : 170px;
+}
+
+}
+.each-row{
+margin-top: 10px;
+}
+.each-row.control-label{
+float:left;
+clear:both;
+}
+.override-controls{
+.icon-ok-sign {
+color: #5AB400;
+}
+#filter-dropdown-div {
+.dropdown-menu {
+  right: 0;
+  left: auto;
+}
+}
+#component-dropdown-div {
+vertical-align: top;
+padding-left: 5px;
+.dropdown-menu {
+  right: 0;
+  left: auto;
+}
+}
+.input-append{
+input{
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.btn-group{
+  display: inline-block;
+  margin-left: -1px;
+  .btn{
+    -webkit-border-radius: 0 4px 4px 0;
+    -moz-border-radius: 0 4px 4px 0;
+    border-radius: 0 4px 4px 0;
   }
+}
+}
+.hosts-table-container{
+width:100%;
+height: 270px;
+overflow: auto;
+border: 1px solid #eee;
+}
+table{
+th {
+  background-color: #d9edf7;
+}
+margin: 0 auto;
+}
+.message{
+color: #777;
+}
+}
+.control-label{
+width:auto;
+}
+.overrideSelectBox {
+width:100%;
+}
+}
+}
 
   .clear {
     clear: both;
@@ -1673,7 +1756,7 @@ a:focus {
 /*****end styles for dashboard page*****/
 
 /*Services*/
-#services-menu {
+.services-menu {
   .nav-list {
     .tab-marker-position {
       background-position: 6px 5px;
@@ -1815,6 +1898,329 @@ table.graphs {
   width: 100%;
 }
 
+/*Dashboard Widgets Start*/
+
+#dashboard-widgets-container{
+  h4{
+    line-height: 30px;
+  }
+  .filter-components{
+    li {
+      ul{
+        margin-left: 0px;
+      }
+      display: block;
+      padding: 3px 0 3px 5px;
+      line-height: 20px;
+
+      label.checkbox {
+        padding-left: 3px;
+      }
+
+      input[type="checkbox"] {
+        margin: 4px 4px 2px 2px;
+      }
+    }
+    &>li {
+      &>ul {
+        width: 180px;
+        margin-left: 0;
+      }
+    }
+  }
+  .reset-widgets-button{
+    color: #ffffff;
+    margin-top: 5px;
+    margin-left: -15px;
+    width: 68px;
+    padding-left: 0px;
+    padding-right: 0px;
+  }
+  .add-widget-button{
+    color: #000000;
+    margin-top: 5px;
+    margin-left: 12px;
+    width: 60px;
+  }
+
+  #dashboard-widgets{
+    .thumbnails {
+      margin-left: 8px;
+    }
+    .sortable-placeholder {
+    }
+    .row-fluid .span2p4 {
+      width: 19.34%;
+      *width: 19.34%;
+    }
+    .row-fluid .span4p8 {
+      width: 39.1%;
+      *width: 39.1%;
+    }
+
+    .thumbnails  > div {
+      margin-left: 0px;
+      margin-right: 3px;
+      margin-top: 5px;
+      margin-bottom: 7px;
+    }
+    .thumbnails  li {
+      height:150px;
+      margin-left: 0px;
+      margin-right: 3px;
+      margin-top: 3px;
+      margin-bottom: 0px;
+    }
+
+    .thumbnail .corner-icon{
+      color: #ffffff;
+      display: none;
+    }
+    .thumbnail .hidden-info{
+      color: #ffffff;
+      font-size: 12px;
+      font-weight: bold;
+      padding-top: 60px;
+      text-align: center;
+      text-decoration: none;
+      display: none;
+    }
+    .thumbnail .hidden-info-three-line{
+      color: #ffffff;
+      font-size: 12px;
+      font-weight: bold;
+      padding-top: 50px;
+      text-align: center;
+      text-decoration: none;
+      display: none;
+      table td{
+        text-align: center ;
+      }
+    }
+
+    .thumbnail .caption {
+      padding-left: 0px;
+      padding-top: 7px;
+      padding-bottom: 7px;
+      padding-right: 0px;
+      color: #555555;
+      font-weight:bold;
+      font-size: 12px;
+      text-align: left;
+    }
+
+    .thumbnail .widget-content{
+      text-align: center;
+      font-size: 35px;
+      padding-top: 40px; //svg
+      .screensaver{ // graph onload wait
+        width: 90%;
+        height: 105px;
+        border: 1px solid silver;
+        color: #ffffff;
+        background: url(/img/spinner.gif) no-repeat center center;
+      }
+    }
+    .thumbnail .widget-content-isNA{ // for pie chart n\a
+      text-align: center;
+      font-size: 35px;
+      color: #D6DDDF;
+      padding-top: 70px;
+      font-weight: bold;
+
+    }
+    .cluster-metrics .chart-container{
+      margin: 0px 10px 0px 10px;
+      .chart-y-axis{
+        margin-top: 10px;
+      }
+      .chart svg{
+        margin-right: 20px;
+      }
+    }
+    .cluster-metrics .alert {
+      padding: 0px;
+      font-size: 12px;
+    }
+    .cluster-metrics .thumbnail:hover{
+      background-color: #909090;
+      .corner-icon{
+        display:block;
+        text-decoration: none;
+        .icon-remove-sign{
+          color: #000000;
+          text-shadow: #fff 0px 0px 15px;
+          position:absolute;
+          margin-left: -17px;
+          margin-top: -7px;
+        }
+      }
+      .caption{
+        color: #ffffff;
+        text-shadow: #000 0px 0px 15px;
+      }
+      .rickshaw_legend{
+        padding-top: 3px;
+      }
+      .chart-legend {
+        top: 120px;
+        left:15px;
+        text-align: left;
+        z-index: 3;
+        ul >li{
+          max-height: 10px;
+        }
+      }
+
+    }
+    .links .thumbnail{
+      li{
+        height:20px;
+        margin: 3px;
+      }
+      .link-button{
+        padding-top: 7px;
+        padding-left: 20px;
+      }
+      .widget-content{
+        text-align: left;
+        font-weight: none;
+        font-size: 10px;
+        color: #555555;
+        padding-top: 35px;
+        padding-left: 10px;
+        td{
+          padding-top: 2px;
+          padding-left:5px ;
+        }
+      }
+    }
+    .links .thumbnail:hover{
+      background-color: #909090;
+      .corner-icon{
+        display:block;
+        text-decoration: none;
+        .icon-remove-sign{
+          color: #000000;
+          text-shadow: #fff 0px 0px 15px;
+          position:absolute;
+          margin-left: -17px;
+          margin-top: -7px;
+        }
+      }
+      .caption{
+        color: #ffffff;
+        text-shadow: #000 0px 0px 15px;
+      }
+      .widget-content{
+        color: #D6DDDF;
+        text-shadow: #000 0px 0px 5px;
+        a{
+          color: #ffffff;
+        }
+      }
+    }
+    .has-hidden-info .thumbnail:hover {
+      background-color: #909090;
+      text-shadow: #000 0px 0px 15px;
+      .corner-icon{
+        display:block;
+        text-decoration: none;
+        .icon-remove-sign{
+          color: #000000;
+          text-shadow: #fff 0px 0px 15px;
+          position:absolute;
+          margin-left: -17px;
+          margin-top: -7px;
+        }
+      }
+      .hidden-info{
+        display: block;
+      }
+      .hidden-info-three-line{
+        display: block;
+      }
+      .widget-content{
+        display: none;
+      }
+      .widget-content-isNA{
+        display: none;
+      }
+      .caption{
+        color: #ffffff;
+      }
+    }
+
+
+
+    .thumbnail .widget-content .svg {
+      position:relative;
+    }
+
+    #map-reduce-slots-text{
+      padding-left: 5px;
+      padding-top: 40px;
+      color: #555555;
+      font-weight:bold;
+      font-size: 10px;
+      >ul{
+        margin-top:5px;
+        margin-bottom: 0px;
+      }
+      #map-reduce-slots-bar1{
+        margin-left: 10px;
+      }
+      #map-reduce-slots-bar2{
+        margin-left: 3px;
+      }
+      #map-reduce-slots-num1{
+        margin-left: 5px;
+      }
+      #map-reduce-slots-num2{
+        margin-left: 5px;
+      }
+    }
+
+    .is-red{
+      .widget-content {
+        color: #B80000;
+        //text-shadow: 0.1em 0.1em #D6DDDF ;
+        //filter: Shadow(Color=red, Direction=45, Strength=1); /* IE 10-*/
+        padding-top: 70px;
+        font-weight: bold;
+      }
+
+    }
+    .is-orange{
+      .widget-content {
+        color: #FF8E00;
+        //text-shadow: 0.1em 0.1em #D6DDDF ;
+        //filter: Shadow(Color=red, Direction=45, Strength=1); /* IE 10-*/
+        padding-top: 70px;
+        font-weight: bold;
+      }
+    }
+    .is-green {
+      .widget-content {
+        color: #95A800;
+        //text-shadow: 0.1em 0.1em #D6DDDF ;
+        //filter: Shadow(Color=red, Direction=45, Strength=0); /* IE 10-*/
+        padding-top: 70px;
+        font-weight: bold;
+      }
+    }
+    .is-na {
+      .widget-content {
+        color: #D6DDDF;
+        text-shadow: none;
+        padding-top: 70px;
+        font-weight: bold;
+      }
+    }
+  }
+}
+/*Dashboard Widgets END*/
+
 /*Hosts*/
 #hosts {
 
@@ -3804,6 +4210,60 @@ i.icon-asterisks {
     margin-left: 0;
   }
 
+
+#dashboard-widgets-container{
+  .reset-widgets-button{
+    margin-top: 5px;
+    margin-left: 0px;
+    width: 70px;
+    padding-left: 1px;
+    padding-right: 1px;
+  }
+  .add-widget-button{
+    margin-top: 5px;
+    margin-left: 5px;
+    width: 70px;
+  }
+
+  #dashboard-widgets{
+    .row-fluid .span2p4 {
+      width: 19.08%;
+      *width: 19.08%;
+    }
+    .row-fluid .span4p8 {
+      width: 39%;
+      *width: 39%;
+    }
+    .thumbnails  > div {
+      margin-right: 8px;
+    }
+    .thumbnail .caption {
+      font-size: 14px;
+    }
+    .thumbnail .hidden-info{
+      font-size: 14px;
+    }
+    .thumbnail .hidden-info-three-line{
+      font-size: 14px;
+    }
+    .links .thumbnail .widget-content{
+        font-weight: bold;
+        font-size: 11px;
+        padding-left: 18px;
+    }
+    #map-reduce-slots-text{
+      font-size: 12px;
+      #map-reduce-slots-bar1{
+        margin-left: 11px;
+      }
+    }
+  }
+}
+
+
+
+
+
   .summary-metric-graphs {
     [class*="span"] {
       float: left;

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

@@ -19,12 +19,14 @@
 <div class="screensaver chart-container" {{bindAttr class="view.isReady:hide"}} >
   <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
 </div>
-<div  id="{{unbound view.id}}-container" class="chart-container hide" {{bindAttr class="view.isReady:show"}}  {{action showGraphInPopup target="view"}} title="Click to zoom">
+<div  id="{{unbound view.id}}-container" class="chart-container hide" {{bindAttr class="view.isReady:show"}}  {{action showGraphInPopup target="view"}} rel="ZoomInTooltip" title="Click to zoom">
   <div id="{{unbound view.id}}-yaxis" class="chart-y-axis"></div>
   <div id="{{unbound view.id}}-xaxis" class="chart-x-axis"></div>
   <div id="{{unbound view.id}}-legend" class="chart-legend"></div>
   <div id="{{unbound view.id}}-chart" class="chart"></div>
   <div id="{{unbound view.id}}-timeline" class="timeline"></div>
+  {{#unless view.noTitleUnderGraph}}
+    <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
+  {{/unless}}
 
-  <div id="{{unbound view.id}}-title" class="chart-title">{{view.title}}</div>
 </div>

+ 41 - 43
ambari-web/app/templates/main/dashboard.hbs

@@ -15,54 +15,52 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 }}
-
-<div class="row">
-  <div class="span6">
-    <div class="row">
-      <div class="span6">
-        <div class="box">
-          <div class="box-header">
-            <h4>{{t dashboard.services}}</h4>
-          </div>
-          <dl class="dl-horizontal services">
-            {{#each item in view.content}}
-                {{view item.viewName serviceBinding="item.model"}}
-            {{/each}}
-          </dl>
-        </div>
-      </div>
-    </div>
+{{#if view.isDataLoaded}}
+<div class="row-fluid">
+  <div class="services-menu well span2" style="padding: 8px 0">
+    {{view App.MainServiceMenuView}}
   </div>
-  <div class="span6">
+
+  <div class="span10" id="dashboard-widgets-container">
 		<div class="box">
 			<div class="box-header">
-				<h4>{{t dashboard.clusterMetrics}}</h4>
-				<div class="btn-group">
-        </div>
-				<div class="btn-group">
-          <a class="btn" target="_blank" rel="tooltip" title="Go to Ganglia" {{bindAttr href="view.gangliaUrl"}}><i class="icon-link"></i></a>
+        <div class="row-fluid">
+          <h4 class="span10">{{t dashboard.widgets}}</h4>
+          <a href="javascript:void(null)" class="btn btn-warning span1 reset-widgets-button" data-toggle="modal" {{action "resetAllWidgets" target="view"}}>
+             <i class="icon-refresh"></i>
+            {{t common.reset}}
+          </a>
+          <a class="add-widget-button span1">{{view view.plusButtonFilterView}}</a>
         </div>
 			</div>
-			<div class="graphs-container">
-        <table class="graphs">
-          <tr>
-            <td>
-              {{view App.ChartClusterMetricsNetwork}}
-            </td>
-            <td>
-              {{view App.ChartClusterMetricsLoad}}
-            </td>
-          </tr>
-          <tr>
-            <td>
-              {{view App.ChartClusterMetricsMemory}}
-            </td>
-            <td>
-              {{view App.ChartClusterMetricsCPU}}
-            </td>
-          </tr>
-        </table>
-			</div>
+
+      <div id="dashboard-widgets"  class="widgets-container">
+        <div class="thumbnails row-fluid" id="sortable">
+          {{#if view.visibleWidgets.length}}
+            {{#each widgetClass in view.visibleWidgets}}
+              {{#if widgetClass.isProgressBar}}
+                <div class="span4p8">
+                  {{view widgetClass }}
+                </div>
+              {{else}}
+                {{#if widgetClass.isLinks}}
+                  <div class="span4p8">
+                    {{view widgetClass }}
+                  </div>
+                {{else}}
+                  <div class="span2p4">
+                    {{view widgetClass }}
+                  </div>
+                {{/if}}
+              {{/if}}
+            {{/each}}
+          {{/if}}
+        </div>
+      </div>
+
 		</div>
 	</div>
 </div>
+
+{{/if}}
+

+ 58 - 0
ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs

@@ -0,0 +1,58 @@
+{{!
+* 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.
+}}
+<form class="form-horizontal">
+    <div class="each-row">
+        <div class="alert alert-info">
+          {{view.configPropertyObj.hintInfo}}
+        </div>
+    </div>
+
+    <div class="row-fluid" id= "min-height-limit">
+       {{#if view.configPropertyObj.isIE9}}
+         {{#if view.configPropertyObj.isGreenOrangeRed}}
+             <div class="progress span9">
+                 <div class="bar bar-success" style="width: 33%;"></div>
+                 <div class="bar bar-warning" style="width: 33%;"></div>
+                 <div class="bar bar-danger" style="width: 34%;"></div>
+             </div>
+         {{else}}
+             <div class="progress span9">
+                 <div class="bar bar-danger" style="width: 33%;"></div>
+                 <div class="bar bar-warning" style="width: 33%;"></div>
+                 <div class="bar bar-success" style="width: 34%;"></div>
+             </div>
+         {{/if}}
+       {{else}}
+         <div class="span9" id="slider-range"></div>
+       {{/if}}
+    </div>
+
+    <div class="row-fluid">
+        <div id="slider-value1" class="value-on-slider span2">0</div>
+        <div id="slider-value2" {{bindAttr class="view.configPropertyObj.isThresh1Error:slider-error :value-on-slider :span4"}}>
+          {{view Ember.TextField valueBinding="view.configPropertyObj.thresh1"}}
+            <span class="help-inline">{{view.configPropertyObj.errorMessage1}}</span>
+        </div>
+        <div id="slider-value3" {{bindAttr class="view.configPropertyObj.isThresh2Error:slider-error :value-on-slider :span4"}}>
+          {{view Ember.TextField valueBinding="view.configPropertyObj.thresh2" }}
+            <span class="help-inline">{{view.configPropertyObj.errorMessage2}}</span>
+        </div>
+        <div id="slider-value4" class="value-on-slider span2">{{view.configPropertyObj.maxValue}}</div>
+    </div>
+
+</form>

+ 47 - 0
ambari-web/app/templates/main/dashboard/plus_button_filter.hbs

@@ -0,0 +1,47 @@
+{{!
+* 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.
+}}
+
+<button class="btn btn-info single-btn-group" {{action "clickFilterButton" target="view"}} >
+    <i class="icon-plus"></i> {{t common.add}}
+</button>
+<ul class="dropdown-menu filter-components" >
+  {{#if view.hiddenWidgets.length}}
+    <li>
+      <ul>
+        {{#each widget in view.hiddenWidgets}}
+          <li>
+            <label class="checkbox">
+              {{view Ember.Checkbox checkedBinding="widget.checked"}} {{unbound widget.displayName}}
+            </label>
+          </li>
+        {{/each}}
+      </ul>
+    </li>
+    <li>
+      <button class="btn" {{action "closeFilter" target="view"}}>{{t common.cancel}}</button>
+      <button class="btn btn-primary" {{action "applyFilter" target="view"}}>{{t common.apply}}</button>
+    </li>
+  {{else}}
+    <li>
+      <ul>
+      <li>{{t dashboard.widgets.nothing}}</li>
+      </ul>
+    </li>
+    <button class="btn" {{action "closeFilter" target="view"}}>{{t common.cancel}}</button>
+  {{/if}}
+</ul>

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

@@ -17,7 +17,7 @@
 }}
 
 <div class="row-fluid">
-  <div id="services-menu" class="well span2" style="padding: 8px 0">
+  <div class="services-menu well span2" style="padding: 8px 0">
     {{view App.MainServiceMenuView}}
     {{#if App.isAdmin}}
     {{#if App.supports.addServices}}

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

@@ -84,6 +84,31 @@ require('views/main/dashboard/cluster_metrics/cpu');
 require('views/main/dashboard/cluster_metrics/load');
 require('views/main/dashboard/cluster_metrics/memory');
 require('views/main/dashboard/cluster_metrics/network');
+
+require('views/main/dashboard/widget');
+require('views/main/dashboard/widgets/namenode_heap');
+require('views/main/dashboard/widgets/namenode_cpu');
+require('views/main/dashboard/widgets/hdfs_capacity');
+require('views/main/dashboard/widgets/jobtracker_heap');
+require('views/main/dashboard/widgets/jobtracker_cpu');
+require('views/main/dashboard/widgets/datanode_live');
+require('views/main/dashboard/widgets/tasktracker_live');
+require('views/main/dashboard/widgets/namenode_rpc');
+require('views/main/dashboard/widgets/jobtracker_rpc');
+require('views/main/dashboard/widgets/mapreduce_slots');
+require('views/main/dashboard/widgets/metrics_memory');
+require('views/main/dashboard/widgets/metrics_network');
+require('views/main/dashboard/widgets/metrics_cpu');
+require('views/main/dashboard/widgets/metrics_load');
+require('views/main/dashboard/widgets/namenode_uptime');
+require('views/main/dashboard/widgets/jobtracker_uptime');
+require('views/main/dashboard/widgets/hdfs_links');
+require('views/main/dashboard/widgets/mapreduce_links');
+require('views/main/dashboard/widgets/hbase_links');
+require('views/main/dashboard/widgets/hbase_master_heap');
+require('views/main/dashboard/widgets/hbase_average_load');
+require('views/main/dashboard/widgets/hbase_regions_in_transition');
+
 require('views/main/service');
 require('views/main/service/menu');
 require('views/main/service/item');

+ 7 - 2
ambari-web/app/views/common/chart/linear_time.js

@@ -508,6 +508,11 @@ App.ChartLinearTimeView = Ember.View.extend({
     }
 
     var height = 150;
+    var diff = 32;
+    if(this.get('id').indexOf('cluster-metrics') != -1){
+      height = 105; // for widgets view
+      diff = 22;
+    }
     var width = 400;
     if (isPopup) {
       height = 180;
@@ -518,8 +523,8 @@ App.ChartLinearTimeView = Ember.View.extend({
       var thisElement = this.get('element');
       if (thisElement!=null) {
         var calculatedWidth = $(thisElement).width();
-        if (calculatedWidth > 32) {
-          width = calculatedWidth-32;
+        if (calculatedWidth > diff) {
+          width = calculatedWidth - diff;
         }
       }
     }

+ 32 - 6
ambari-web/app/views/common/chart/pie.js

@@ -22,10 +22,13 @@ App.ChartPieView = Em.View.extend({
   w:90,
   h:90,
   data:[300, 500],
+  id:null,
   palette: new Rickshaw.Color.Palette({ scheme: 'munin'}),
   stroke: 'black',
   strokeWidth: 2,
   donut:d3.layout.pie().sort(null),
+  existCenterText: false,
+  centerTextColor: 'black',
 
   r:function () {
     return Math.min(this.get('w'), this.get('h')) / 2 - this.get('strokeWidth');
@@ -53,22 +56,45 @@ App.ChartPieView = Em.View.extend({
   }.property('elementId'),
 
   appendSvg:function () {
-    var thisChart = this;
 
-    this.set('svg', d3.select(this.get('selector')).append("svg:svg")
+    var thisChart = this;
+    var svg = d3.select(thisChart.get('selector')).append("svg:svg")
+      .attr("id", thisChart.get('id'))
       .attr("width", thisChart.get('w'))
       .attr("height", thisChart.get('h'))
-      .attr("stroke", this.get('stroke'))
-      .attr("stroke-width", this.get('strokeWidth'))
+      .attr("stroke", thisChart.get('stroke'))
+      .attr("stroke-width", thisChart.get('strokeWidth'));
+
+    // set percentage data in center if there exist a center text
+    if(thisChart.get('existCenterText')){
+      this.set('svg', svg
+        .append("svg:g")
+        .attr("render-order", 1)
+        .append("svg:text")
+        .attr("stroke", thisChart.get('centerTextColor'))
+        .attr("font-size", 17)
+        .attr("transform", "translate(" + thisChart.get('w') / 2 + "," + ((thisChart.get('h') / 2) + 3) + ")")
+        .attr("text-anchor", "middle")
+        .text(function(d) {
+                 return thisChart.get('data')[0] + '%';
+              })
+         );
+    }
+
+    this.set('svg', svg
       .append("svg:g")
       .attr("transform", "translate(" + thisChart.get('w') / 2 + "," + thisChart.get('h') / 2 + ")"));
 
-    this.set('arcs', this.get('svg').selectAll("path")
+    this.set('arcs', thisChart.get('svg').selectAll("path")
       .data(thisChart.donut(thisChart.get('data')))
       .enter().append("svg:path")
       .attr("fill", function (d, i) {
         return thisChart.palette.color(i);
       })
-      .attr("d", this.get('arc')));
+      .attr("d", thisChart.get('arc'))
+
+    );
+
   }
+
 });

+ 273 - 0
ambari-web/app/views/main/dashboard.js

@@ -17,13 +17,286 @@
  */
 
 var App = require('app');
+var filters = require('views/common/filter_view');
 
 App.MainDashboardView = Em.View.extend({
   templateName:require('templates/main/dashboard'),
   didInsertElement:function () {
     this.services();
+    this.set('isDataLoaded',true);
+    this.setWidgetsDataModel();
+    this.setOnLoadVisibleWidgets();
+    Ember.run.next(this, 'makeSortable');
   },
   content:[],
+  isDataLoaded: false,
+
+  makeSortable: function() {
+    var self = this;
+    $( "#sortable" ).sortable({
+      items: "> div",
+      //placeholder: "sortable-placeholder",
+      cursor: "move",
+
+      update: function (event, ui) {
+        if (!App.testMode) {
+          // update persist then translate to real
+          var widgetsArray = $('div[viewid]'); // get all in DOM
+          self.getUserPref(self.get('persistKey'));
+          var oldValue = self.get('currentPrefObject');
+          var newValue = Em.Object.create({
+            visible: [],
+            hidden: oldValue.hidden,
+            threshold: oldValue.threshold
+          });
+          var size = oldValue.visible.length;
+          for(var j = 0; j <= size -1; j++){
+            var viewID = widgetsArray.get(j).getAttribute('viewid');
+            var id = viewID.split("-").get(1);
+            newValue.visible.push(id);
+          }
+          self.postUserPref(self.get('persistKey'), newValue);
+          // self.translateToReal(newValue);
+        }
+
+      }
+    });
+    $( "#sortable" ).disableSelection();
+  },
+
+  setWidgetsDataModel: function () {
+    var services = App.Service.find();
+    var self = this;
+    services.forEach(function (item) {
+      switch (item.get('serviceName')) {
+        case "HDFS":
+          self.set('hdfs_model',  App.HDFSService.find(item.get('id')) || item);
+          break;
+        case "MAPREDUCE":
+          self.set('mapreduce_model', App.MapReduceService.find(item.get('id')) || item);
+          break;
+        case "HBASE":
+          self.set('hbase_model', App.HBaseService.find(item.get('id')) || item);
+          break;
+      }
+    }, this);
+  },
+  hdfs_model: null,
+  mapreduce_model:null,
+  hbase_model: null,
+  visibleWidgets: [],
+  hiddenWidgets: [], // widget child view will push object in this array if deleted
+
+  plusButtonFilterView: filters.createComponentView({
+    /**
+     * Base methods was implemented in <code>filters.componentFieldView</code>
+     */
+    hiddenWidgetsBinding: 'parentView.hiddenWidgets',
+    visibleWidgetsBinding: 'parentView.visibleWidgets',
+    layout: null,
+
+    filterView: filters.componentFieldView.extend({
+      templateName:require('templates/main/dashboard/plus_button_filter'),
+      hiddenWidgetsBinding: 'parentView.hiddenWidgets',
+      visibleWidgetsBinding: 'parentView.visibleWidgets',
+      valueBinding: '',
+      applyFilter:function() {
+        this._super();
+        var parent = this.get('parentView').get('parentView');
+        var hiddenWidgets = this.get('hiddenWidgets');
+        var checkedWidgets = hiddenWidgets.filterProperty('checked', true);
+
+        if (App.testMode) {
+          var visibleWidgets = this.get('visibleWidgets');
+          checkedWidgets.forEach(function(item){
+            var new_obj = parent.widgetsMapper(item.id);
+            visibleWidgets.pushObject(new_obj);
+            hiddenWidgets.removeObject(item);
+          }, this);
+
+        } else {
+          //save in persist
+          parent.getUserPref(parent.get('persistKey'));
+          var oldValue = parent.get('currentPrefObject');
+          var newValue = Em.Object.create({
+            visible: oldValue.visible,
+            hidden: [],
+            threshold: oldValue.threshold
+          });
+          checkedWidgets.forEach(function(item){
+            newValue.visible.push(item.id);
+            hiddenWidgets.removeObject(item);
+          }, this);
+          hiddenWidgets.forEach(function(item){
+            newValue.hidden.push([item.id, item.displayName]);
+          },this);
+          parent.postUserPref(parent.get('persistKey'), newValue);
+          parent.translateToReal(newValue);
+        }
+
+      }
+    })
+  }),
+
+  /**
+   * translate from Json value got from persist to real widgets view
+   */
+  translateToReal: function (value) {
+    var visible = value.visible;
+    var hidden = value.hidden;
+    var threshold = value.threshold;
+
+    // clear current visible and hiddenWidgets
+    var visibleWidgets = this.get('visibleWidgets');
+    var size = visibleWidgets.length;
+    for (var i = 1; i <= size; i++) {
+      visibleWidgets.removeAt(0);
+    }
+    var hiddenWidgets = this.get('hiddenWidgets');
+    var size = hiddenWidgets.length;
+    for (var i = 1; i <= size; i++) {
+      hiddenWidgets.removeAt(0);
+    }
+    // re-construct visibleWidgets and hiddenWidgets
+    for (var j = 0; j <= visible.length -1; j++){
+      var cur_id = visible[j];
+      var widgetClass = this.widgetsMapper(cur_id);
+      //override with new threshold
+      if (threshold[cur_id].length > 0) {
+        widgetClass.reopen({
+          thresh1: threshold[cur_id][0],
+          thresh2: threshold[cur_id][1]
+        });
+      }
+      visibleWidgets.pushObject(widgetClass);
+    }
+    for (var j = 0; j <= hidden.length -1; j++){
+      var cur_id = hidden[j][0];
+      var cur_title = hidden[j][1];
+      hiddenWidgets.pushObject(Em.Object.create({displayName:cur_title , id: cur_id, checked: false}));
+    }
+  },
+
+  setOnLoadVisibleWidgets: function () {
+    if (App.testMode) {
+      this.translateToReal(this.get('initPrefObject'));
+    } else {
+      // called when first load/refresh/jump back page
+      this.getUserPref(this.get('persistKey'));
+      var currentPrefObject = this.get('currentPrefObject');
+      if (currentPrefObject) {
+        this.translateToReal(currentPrefObject);
+      } else {
+        // post persist then translate init object
+        this.postUserPref(this.get('persistKey'), this.get('initPrefObject'));
+        this.translateToReal(this.get('initPrefObject'));
+      }
+    }
+  },
+
+  widgetsMapper: function(id){
+    switch(id){
+      case '1': return App.NameNodeHeapPieChartView;
+      case '2': return App.NameNodeCapacityPieChartView;
+      case '3': return App.NameNodeCpuPieChartView;
+      case '4': return App.DataNodeUpView;
+      case '5': return App.NameNodeRpcView;
+      case '6': return App.JobTrackerHeapPieChartView;
+      case '7': return App.JobTrackerCpuPieChartView;
+      case '8': return App.TaskTrackerUpView;
+      case '9': return App.JobTrackerRpcView;
+      case '10': return App.MapReduceSlotsView;
+      case '11': return App.ChartClusterMetricsMemoryWidgetView;
+      case '12': return App.ChartClusterMetricsNetworkWidgetView;
+      case '13': return App.ChartClusterMetricsCPUWidgetView;
+      case '14': return App.ChartClusterMetricsLoadWidgetView;
+      case '15': return App.NameNodeUptimeView;
+      case '16': return App.JobTrackerUptimeView;
+      case '17': return App.HDFSLinksView;
+      case '18': return App.MapReduceLinksView;
+      case '19': return App.HBaseLinksView;
+      case '20': return App.HBaseMasterHeapPieChartView;
+      case '21': return App.HBaseAverageLoadView;
+      case '22': return App.HBaseRegionsInTransitionView;
+    }
+
+  },
+
+  currentPrefObject: null,
+  initPrefObject: Em.Object.create({
+    visible: [
+      '11', '12', '13', '14', //cluster-metrics
+      '1', '2', '3', '4', '5', '15', '17', //hdfs
+      '6', '7', '8', '9', '10', '18', '16',//map reduce
+      '20', '21', '19', '22' //hbase
+    ], // all in order
+    hidden:[],
+    threshold:{1: [40,70], 2: [40,70], 3: [40,70], 4: [40,70], 5: [0.5, 2], 6: [40,70], 7: [40,70], 8: [40,70], 9: [0.5, 2],
+      10:[], 11:[], 12:[], 13:[], 14:[], 15:[], 16:[], 17:[], 18:[], 19:[], 20:[40,70], 21:[10,19.2], 22: [3, 10]} // id:[thresh1, thresh2]
+  }),
+  persistKey: function(){
+    var loginName = App.router.get('loginName');
+    return 'user-pref-' + loginName + '-dashboard';
+  }.property(''),
+
+  /**
+   * get persist value from server with persistKey
+   */
+  getUserPref: function(key){
+    var self = this;
+    var url = App.apiPrefix + '/persist/' + key;
+    jQuery.ajax(
+      {
+        url: url,
+        context: this,
+        async: false,
+        success: function (response) {
+          if (response) {
+            var value = jQuery.parseJSON(response);
+            console.log('Persist value with key ' + key + '. JSON Value is: ' + response);
+            self.set('currentPrefObject', value);
+            return value;
+           }
+        },
+        error: function (xhr) {
+          // this user is first time login
+          if (xhr.status == 404) {
+            console.log('Persist did NOT find the key '+ key);
+            return null;
+          }
+        },
+        statusCode: require('data/statusCodes')
+      }
+    );
+  },
+
+  /**
+   * post persist key/value to server, value is object
+   */
+  postUserPref: function(key, value){
+    var url = App.apiPrefix + '/persist/';
+    var keyValuePair = {};
+    keyValuePair[key] = JSON.stringify(value);
+
+    jQuery.ajax({
+      async: false,
+      context: this,
+      type: "POST",
+      url: url,
+      data: JSON.stringify(keyValuePair),
+      beforeSend: function () {
+        console.log('BeforeSend: persistKeyValues', keyValuePair);
+      }
+    });
+  },
+
+  resetAllWidgets: function(){
+    if(!App.testMode){
+      this.postUserPref(this.get('persistKey'), this.get('initPrefObject'));
+    }
+    this.translateToReal(this.get('initPrefObject'));
+  },
+
   updateServices: function(){
     var services = App.Service.find();
     services.forEach(function (item) {

+ 311 - 0
ambari-web/app/views/main/dashboard/widget.js

@@ -0,0 +1,311 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.DashboardWidgetView = Em.View.extend({
+
+  title: null,
+
+  model : function () {
+    if (this.get('model_type') == 'hdfs') {
+      return this.get('parentView').get('hdfs_model');
+    } else if (this.get('model_type') == 'mapreduce') {
+      return this.get('parentView').get('mapreduce_model');
+    } else if (this.get('model_type') == 'hbase') {
+      return this.get('parentView').get('hbase_model');
+    }
+  }.property(''), //data bind from parent view
+
+  id: null, // id 1-10 used to identify
+  viewID: function(){ // used by re-sort
+    return 'widget-' + this.id;
+  }.property('id'),  //html id bind to view-class: widget-(1-10)
+  attributeBindings: ['viewID'],
+
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+  isLinks: false,
+  content: null, // widget content pieChart/ text/ progress bar. etc
+  hiddenInfo: null, // more info details
+
+  thresh1: null, //num not string
+  thresh2: null,
+
+  didInsertElement: function () {
+    this.$("[rel='ZoomInTooltip']").tooltip({
+      placement : 'left'
+    });
+  },
+
+  template: Ember.Handlebars.compile([
+
+    '{{#if view.isPieChart}}',
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row">',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '<div class="widget-content" >','{{view view.content modelBinding="view.model" thresh1Binding="view.thresh1" thresh2Binding="view.thresh2"}}','</div>',
+    '</li>',
+    '</div>',
+    '{{/if}}',
+
+    '{{#if view.isText }}',
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row" >',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '<div class="widget-content">{{view.content}}</div>',
+    '</li>',
+    '</div>',
+    '{{/if}}',
+
+    '{{#if view.isProgressBar}}',
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row" >',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="span1">', '</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '<div class="widget-content row-fluid" id="map-reduce-slots-text" >',
+
+      '<ul class="span12">',
+        '<div class="span3"> {{t dashboard.widgets.mapSlots}}</div>',
+        '<div class="progress span5" id="map-reduce-slots-bar1">',
+          '<div class="bar bar-success" {{bindAttr style="view.map_occupied"}}></div>',
+        '<div class="bar bar-warning" {{bindAttr style="view.map_reserved"}}></div>',
+        ' </div>',
+        '<div class="span3" id="map-reduce-slots-num1"> {{view.map_display_text}}</div>',
+      '</ul>',
+      '<ul class="span12">',
+        '<div class="span3"> {{t dashboard.widgets.reduceSlots}}</div>',
+        '<div class="progress span5" id="map-reduce-slots-bar2">',
+          '<div class="bar bar-success" {{bindAttr style="view.reduce_occupied"}}></div>',
+          '<div class="bar bar-warning" {{bindAttr style="view.reduce_reserved"}}></div>',
+        '</div>',
+        '<div class="span3" id="map-reduce-slots-num2"> {{view.reduce_display_text}}</div>',
+      '</ul>',
+
+    '</div>',
+    '</li>',
+    '</div>',
+    '{{/if}}',
+
+    '{{#if view.isClusterMetrics}}',
+    '<div class="cluster-metrics">',
+    '<li class="thumbnail row">',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<div class="widget-content" >','{{view view.content}}','</div>',
+    //'<p class="hidden-info">', '{{view.hiddenInfo}}', '</p>',
+    '</li>',
+    '</div>',
+    '{{/if}}'
+
+  ].join('\n')),
+
+  deleteWidget: function (event) {
+    var parent = this.get('parentView');
+
+    if (!App.testMode) {
+      //reconstruct new persist value then post in persist
+      parent.getUserPref(parent.get('persistKey'));
+      var oldValue = parent.get('currentPrefObject');
+      var deletedId = this.id;
+      var newValue = Em.Object.create({
+        visible: [],
+        hidden: oldValue.hidden,
+        threshold: oldValue.threshold
+      });
+      for(var i = 0; i <= oldValue.visible.length -1; i++){
+        if(oldValue.visible[i] != deletedId){
+          newValue.visible.push(oldValue.visible[i]);
+        }
+      }
+      newValue.hidden.push([deletedId, this.title]);
+      parent.postUserPref(parent.get('persistKey'), newValue);
+    }
+    //update view on dashboard
+    var obj_class = parent.widgetsMapper(this.id);
+    parent.get('visibleWidgets').removeObject(obj_class);
+    parent.get('hiddenWidgets').pushObject(Em.Object.create({displayName: this.title, id: this.id, checked: false}));
+  },
+
+  editWidget: function (event) {
+    var self = this;
+    var max_tmp =  parseFloat(self.get('maxValue'));
+    var configObj = Ember.Object.create({
+      thresh1: self.get('thresh1') + '',
+      thresh2: self.get('thresh2') + '',
+      hintInfo: 'Edit the percentage thresholds to change the color of current pie chart. ' + ' '+
+        ' Enter two numbers between 0 to ' + max_tmp,
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: max_tmp,
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if(thresh1.trim() != ""){
+          if (isNaN(thresh1) || thresh1 > max_tmp || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else if (this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 > max_tmp || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+
+        // update the slider handles and color
+        if (this.get('isThresh1Error') === false && this.get('isThresh2Error') === false) {
+          $("#slider-range").slider('values', 0 , parseFloat(thresh1));
+          $("#slider-range").slider('values', 1 , parseFloat(thresh2));
+        }
+      }.observes('thresh1', 'thresh2')
+
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget' ],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function() {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          self.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          self.set('thresh2', parseFloat(configObj.get('thresh2')) );
+
+          if (!App.testMode) {
+            // save to persist
+            var parent = self.get('parentView');
+            parent.getUserPref(parent.get('persistKey'));
+            var oldValue = parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(self.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            parent.postUserPref(parent.get('persistKey'), oldValue);
+          }
+
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var handlers = [configObj.get('thresh1'), configObj.get('thresh2')];
+        var colors = ['#95A800', '#FF8E00', '#B80000']; //color green, orange ,red
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', true);
+          $("#slider-range").slider({
+            range: true,
+            min: 0,
+            max: max_tmp,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            },
+            slide: function (event, ui) {
+              updateColors(ui.values);
+              configObj.set('thresh1', ui.values[0] + '');
+              configObj.set('thresh2', ui.values[1] + '');
+            },
+            change: function (event, ui) {
+              updateColors(ui.values);
+            }
+          });
+
+          function updateColors (handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i]*100/max_tmp + "%,";
+              colorstops += colors[i+1] + " " + handlers[i]*100/max_tmp + "%,";
+            }
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', true);
+        }
+      }
+    });
+  },
+
+  getInternetExplorerVersion: function(){
+    var rv = -1; //return -1 for other browsers
+    if (navigator.appName == 'Microsoft Internet Explorer') {
+      var ua = navigator.userAgent;
+      var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
+      if (re.exec(ua) != null)
+        rv = parseFloat( RegExp.$1 ); // IE version 1-10
+    }
+    var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
+    if(isFirefox) {
+      return -2;
+    }else{
+      return rv;
+    }
+  }
+
+});
+
+

+ 203 - 0
ambari-web/app/views/main/dashboard/widgets/datanode_live.js

@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.DataNodeUpView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.DataNodeUp'),
+  id: '4',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'hdfs',
+
+  hiddenInfo: function () {
+    var result = [];
+    result.pushObject(this.get('model.liveDataNodes.length') + ' ' + this.t('dashboard.services.hdfs.nodes.live') + '/ ' +
+      this.get('model.deadDataNodes.length') + ' ' + this.t('dashboard.services.hdfs.nodes.dead') + '/ ' +
+      this.get('model.decommissionDataNodes.length')+ ' ' + this.t('dashboard.services.hdfs.nodes.decom'));
+    return result;
+  }.property('model.liveDataNodes.length','model.deadDataNodes.length','model.decommissionDataNodes.length'),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen'],
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+
+  thresh1: 40,
+  thresh2: 70,
+  maxValue: 100,
+
+  data: function () {
+    return ((this.get('model.liveDataNodes.length')/ this.get('model.dataNodes.length')).toFixed(2)) * 100;
+  }.property('model.dataNodes.length', 'model.liveDataNodes.length'),
+
+  content: function () {
+    return this.get('model.liveDataNodes.length') + "/" + this.get('model.dataNodes.length');
+  }.property('model.dataNodes.length', 'model.liveDataNodes.length'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var max_tmp =  parseFloat(parent.get('maxValue'));
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the percentage of thresholds to change the color of current widget. ' +
+        ' Assume all data nodes UP is 100, and all DOWN is 0. '+
+        ' So enter two numbers between 0 to ' + max_tmp,
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: max_tmp,
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 > max_tmp || thresh1 < 0){
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else if ( this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 > max_tmp || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+
+        // update the slider handles and color
+        if (this.get('isThresh1Error') === false && this.get('isThresh2Error') === false) {
+          $("#slider-range").slider('values', 0 , parseFloat(thresh1));
+          $("#slider-range").slider('values', 1 , parseFloat(thresh2));
+        }
+      }.observes('thresh1', 'thresh2')
+
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function () {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var handlers = [configObj.get('thresh1'), configObj.get('thresh2')];
+        var colors = ['#B80000', '#FF8E00', '#95A800']; //color red, orange, green
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', false);
+          $("#slider-range").slider({
+            range: true,
+            min: 0,
+            max: max_tmp,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            },
+            slide: function (event, ui) {
+              updateColors(ui.values);
+              configObj.set('thresh1', ui.values[0] + '');
+              configObj.set('thresh2', ui.values[1] + '');
+            },
+            change: function (event, ui) {
+              updateColors(ui.values);
+            }
+          });
+
+          function updateColors(handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', false);
+        }
+      }
+    });
+  }
+
+})

+ 200 - 0
ambari-web/app/views/main/dashboard/widgets/hbase_average_load.js

@@ -0,0 +1,200 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.HBaseAverageLoadView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.HBaseAverageLoad'),
+  id: '21',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'hbase',
+  hiddenInfo: function () {
+    var avgLoad = this.get('model.averageLoad');
+    if (avgLoad == null) {
+      avgLoad = this.t('services.service.summary.notAvailable');
+    }
+    var result = [];
+    result.pushObject(this.t('dashboard.services.hbase.averageLoadPerServer').format(avgLoad));
+    return result;
+  }.property("model.averageLoad"),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+  isNA: function (){
+    return this.get('data') === null;
+  }.property('data'),
+
+  thresh1: 0.5,
+  thresh2: 2,
+  maxValue: 'infinity',
+
+  data: function () {
+    return this.get('model.averageLoad');
+  }.property("model.averageLoad"),
+
+  content: function (){
+    if(this.get('data') || this.get('data') == 0){
+      return this.get('data') + "";
+    }else{
+      return this.t('services.service.summary.notAvailable');
+    }
+  }.property('model.averageLoad'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the thresholds to change the color of current widget. ' +
+
+        ' So enter two numbers larger than 0. ',
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: 'infinity',
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number larger than 0');
+          } else if ( this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)){
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number larger than 0');
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+
+      }.observes('thresh1', 'thresh2')
+
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show( {
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function () {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            //save to persist
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function() {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var colors = ['#95A800', '#FF8E00', '#B80000']; //color green, orange ,red
+        var handlers = [33, 66]; //fixed value
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', true);
+          $("#slider-range").slider({
+            range:true,
+            disabled:true, //handlers cannot move
+            min: 0,
+            max: 100,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            }
+          });
+
+          function updateColors(handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', true);
+        }
+      }
+    });
+  }
+
+})

+ 151 - 0
ambari-web/app/views/main/dashboard/widgets/hbase_links.js

@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.HBaseLinksView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.HBaseLinks'),
+  id: '19',
+
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+  isLinks: true,
+  model_type: 'hbase',
+
+  template: Ember.Handlebars.compile([
+    '<div class="links">',
+    '<li class="thumbnail row">',
+      '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span8">', '{{view.title}}','</div>',
+    '<div class="span3 link-button">',
+    '{{#if view.model.quickLinks.length}}',
+      '{{#view App.QuickViewLinks contentBinding="view.model"}}',
+        '<div class="btn-group">',
+          '<a class="btn btn-mini dropdown-toggle" data-toggle="dropdown" href="#">',
+            '{{t common.more}}',
+            '<span class="caret"></span>',
+          '</a>',
+          '<ul class="dropdown-menu">',
+            '{{#each view.quickLinks}}',
+              '<li><a {{bindAttr href="url"}} target="_blank">{{label}}</a></li>',
+            '{{/each}}',
+          '</ul>',
+        '</div>',
+      '{{/view}}',
+    '{{/if}}',
+
+    '</div>',
+    '<div class="widget-content" >',
+    '<table>',
+    //hbase master server
+    '<tr>',
+    '<td>{{t dashboard.services.hbase.masterServer}}</td>',
+    '<td>',
+    '{{#if view.activeMaster}}',
+      '<a href="#" {{action showDetails view.activeMaster.host}}>{{view.activeMasterTitle}}</a>',
+      '{{#if view.passiveMasters.length}}',
+        '{{view.passiveMasterOutput}}',
+      '{{/if}}',
+    '{{else}}',
+      '{{t dashboard.services.hbase.noMasterServer}}',
+    '{{/if}}',
+    '</td>',
+    '</tr>',
+    //region servers
+    '<tr>',
+    '<td>{{t dashboard.services.hbase.regionServers}}</td>',
+    '<td><a href="#" {{action filterHosts view.regionServerComponent}}>{{view.model.regionServers.length}} {{t dashboard.services.hbase.regionServers}}</a></td>',
+    '</tr>',
+
+    // hbase master Web UI
+    '<tr>',
+    '<td>{{t dashboard.services.hbase.masterWebUI}}</td>',
+    '<td>' +
+    '{{#if view.activeMaster}}',
+      '<a {{bindAttr href="view.hbaseMasterWebUrl"}} target="_blank">{{view.activeMaster.host.publicHostName}}:60010</a>',
+    '{{else}}',
+      '{{t services.service.summary.notAvailable}}',
+    '{{/if}}',
+    '</td>',
+    '</tr>',
+    '</table>',
+
+    '</div>',
+    '</li>',
+    '</div>'
+
+
+  ].join('\n')),
+
+  /**
+   * All master components
+   */
+  masters: function () {
+    return this.get('model.hostComponents').filterProperty('isMaster', true);
+  }.property('model.hostComponents.@each'),
+  /**
+   * Passive master components
+   */
+  passiveMasters: function () {
+    if (App.supports.multipleHBaseMasters) {
+      return this.get('masters').filterProperty('haStatus', 'passive');
+    }
+    return [];
+  }.property('masters'),
+  /**
+   * Formatted output for passive master components
+   */
+  passiveMasterOutput: function () {
+    return Em.I18n.t('service.hbase.passiveMasters').format(this.get('passiveMasters').length);
+  }.property('passiveMasters'),
+  /**
+   * One(!) active master component
+   */
+  activeMaster: function () {
+    if(App.supports.multipleHBaseMasters) {
+      return this.get('masters').findProperty('haStatus', 'active');
+    } else {
+      return this.get('masters')[0];
+    }
+  }.property('masters'),
+
+  activeMasterTitle: function(){
+    if (App.supports.multipleHBaseMasters) {
+      return this.t('service.hbase.activeMaster');
+    } else {
+      return this.get('activeMaster.host.publicHostName');
+    }
+  }.property('activeMaster'),
+
+  regionServerComponent: function () {
+    return App.HostComponent.find().findProperty('componentName', 'HBASE_REGIONSERVER');
+  }.property(),
+
+  hbaseMasterWebUrl: function () {
+    if (this.get('activeMaster.host') && this.get('activeMaster.host').get('publicHostName')) {
+      return "http://" + this.get('activeMaster.host').get('publicHostName') + ":60010";
+    }
+  }.property('activeMaster')
+
+})
+
+App.HBaseLinksView.reopenClass({
+  isLinks: true
+})

+ 134 - 0
ambari-web/app/views/main/dashboard/widgets/hbase_master_heap.js

@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.HBaseMasterHeapPieChartView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.HBaseMasterHeap'),
+
+  id: '20',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'hbase',
+
+  hiddenInfo: function () {
+    var heapUsed = this.get('model').get('heapMemoryUsed') || 0;
+    var heapMax = this.get('model').get('heapMemoryMax') || 0;
+    var percent = heapMax > 0 ? 100 * heapUsed / heapMax : 0;
+    var result = [];
+    result.pushObject(heapUsed.bytesToSize(1, "parseFloat") + ' of ' + heapMax.bytesToSize(1, "parseFloat"));
+    result.pushObject(percent.toFixed(1) + '% used');
+    return result;
+  }.property('model.heapMemoryUsed', 'model.heapMemoryMax'),
+
+  thresh1: null,
+  thresh2: null,
+  maxValue: 100,
+
+  isPieExist: function () {
+    var total = this.get('model.heapMemoryMax') * 1000000;
+    return total > 0 ;
+  }.property('model.heapMemoryMax'),
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row">',
+      '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '{{#if view.isPieExist}}' +
+      '<div class="widget-content" >','{{view view.content modelBinding="view.model" thresh1Binding="view.thresh1" thresh2Binding="view.thresh2"}}','</div>',
+    '{{else}}',
+    '<div class="widget-content-isNA" >','{{t services.service.summary.notAvailable}}','</div>',
+    '{{/if}}',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-hbase-heap', // html id
+    stroke: '#D6DDDF', //light grey
+    thresh1: null, //bind from parent
+    thresh2: null,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var heapUsed = this.get('model').get('heapMemoryUsed');
+      var heapMax = this.get('model').get('heapMemoryMax');
+      var percent = heapMax > 0 ? (100 * heapUsed / heapMax).toFixed() : 0;
+      return [percent, 100-percent];
+    }.property('model.heapMemoryUsed', 'model.heapMemoryMax'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'this.thresh1', 'this.thresh2'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      if(old_svg){
+        old_svg.remove();
+      }
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.heapMemoryUsed', 'model.heapMemoryMax', 'this.thresh1', 'this.thresh2')
+
+  })
+
+})
+
+

+ 192 - 0
ambari-web/app/views/main/dashboard/widgets/hbase_regions_in_transition.js

@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.HBaseRegionsInTransitionView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.HBaseRegionsInTransition'),
+  id: '22',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'hbase',
+  hiddenInfo: function () {
+    var result = [];
+    result.pushObject(this.get("model.regionsInTransition") + " regions");
+    result.pushObject("in transition");
+    return result;
+  }.property("model.regionsInTransition"),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+  isNA: function () {
+    return this.get('data') === null;
+  }.property('data'),
+
+  thresh1: 0.5,
+  thresh2: 2,
+  maxValue: 'infinity',
+
+  data: function () {
+    return this.get('model.regionsInTransition');
+  }.property("model.regionsInTransition"),
+
+  content: function (){
+    return this.get('data') + "";
+  }.property('model.regionsInTransition'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the thresholds to change the color of current widget. ' +
+
+        ' So enter two numbers larger than 0. ',
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: 'infinity',
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number larger than 0');
+          } else if ( this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)){
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number larger than 0');
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+      }.observes('thresh1', 'thresh2')
+
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function () {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            //save to persist
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var colors = ['#95A800', '#FF8E00', '#B80000']; //color green, orange ,red
+        var handlers = [33, 66]; //fixed value
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', true);
+          $("#slider-range").slider({
+            range:true,
+            disabled:true, //handlers cannot move
+            min: 0,
+            max: 100,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            }
+          });
+
+          function updateColors (handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', true);
+        }
+      }
+    });
+  }
+
+})

+ 144 - 0
ambari-web/app/views/main/dashboard/widgets/hdfs_capacity.js

@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.NameNodeCapacityPieChartView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.NameNodeCapacity'),
+  id: '2',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'hdfs',
+
+  hiddenInfo: function () {
+    var text = this.t("dashboard.services.hdfs.capacityUsed");
+    var total = this.get('model.capacityTotal') + 0;
+    var remaining = this.get('model.capacityRemaining') + 0;
+    var used = total - remaining;
+    var percent = total > 0 ? ((used * 100) / total).toFixed(1) : 0;
+    if (percent == "NaN" || percent < 0) {
+      percent = Em.I18n.t('services.service.summary.notAvailable') + " ";
+    }
+    if (used < 0) {
+      used = 0;
+    }
+    if (total < 0) {
+      total = 0;
+    }
+    var result = [];
+    result.pushObject(used.bytesToSize(1, 'parseFloat') + ' of ' + total.bytesToSize(1, 'parseFloat'));
+    result.pushObject(percent + '% used');
+    return result;
+  }.property('model.capacityUsed', 'model.capacityTotal'),
+
+  thresh1: 40,// can be customized
+  thresh2: 70,
+  maxValue: 100,
+
+  isPieExist: function () {
+    var total = this.get('model.capacityTotal') + 0;
+    return total > 0 ;
+  }.property('model.capacityTotal'),
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row">',
+      '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '{{#if view.isPieExist}}',
+      '<div class="widget-content" >','{{view view.content modelBinding="view.model" thresh1Binding="view.thresh1" thresh2Binding="view.thresh2"}}','</div>',
+    '{{else}}',
+      '<div class="widget-content-isNA" >','{{t services.service.summary.notAvailable}}','</div>',
+    '{{/if}}',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-nn-capacity', // html id
+    stroke: '#D6DDDF', //light grey
+    thresh1: null,  // can be customized later
+    thresh2: null,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var total = this.get('model.capacityTotal') + 0;
+      var remaining = this.get('model.capacityRemaining') + 0;
+      var used = total - remaining;
+      var percent = total > 0 ? ((used * 100) / total).toFixed() : 0;
+      if (percent == "NaN" || percent < 0) {
+        percent = Em.I18n.t('services.service.summary.notAvailable') + " ";
+      }
+      return [ percent, 100 - percent];
+    }.property('model.capacityUsed', 'model.capacityTotal'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'this.thresh1', 'this.thresh2'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      old_svg.remove();
+
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.capacityUsed', 'model.capacityTotal', 'this.thresh1', 'this.thresh2')
+  })
+
+})

+ 101 - 0
ambari-web/app/views/main/dashboard/widgets/hdfs_links.js

@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.HDFSLinksView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.HDFSLinks'),
+  id: '17',
+
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+  isLinks: true,
+  model_type: 'hdfs',
+
+  template: Ember.Handlebars.compile([
+    '<div class="links">',
+    '<li class="thumbnail row">',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span8">', '{{view.title}}','</div>',
+    '<div class="span3 link-button">',
+     '{{#if view.model.quickLinks.length}}',
+        '{{#view App.QuickViewLinks contentBinding="view.model"}}',
+        '<div class="btn-group">',
+          '<a class="btn btn-mini dropdown-toggle" data-toggle="dropdown" href="#">',
+            '{{t common.more}}',
+            '<span class="caret"></span>',
+          '</a>',
+          '<ul class="dropdown-menu">',
+            '{{#each view.quickLinks}}',
+            '<li><a {{bindAttr href="url"}} target="_blank">{{label}}</a></li>',
+            '{{/each}}',
+          '</ul>',
+        '</div>',
+        '{{/view}}',
+     '{{/if}}',
+
+    '</div>',
+    '<div class="widget-content" >',
+    '<table>',
+    //NameNode
+    '<tr>',
+    '<td>{{t dashboard.services.hdfs.nanmenode}}</td>',
+    '<td><a href="#" {{action showDetails view.model.nameNode}}>{{view.model.nameNode.publicHostName}}</a></td>',
+    '</tr>',
+    //SecondaryNameNode
+    '<tr>',
+    '<td>{{t dashboard.services.hdfs.snanmenode}}</td>',
+    '<td><a href="#" {{action showDetails view.model.snameNode}}>{{view.model.snameNode.publicHostName}}</a></td>',
+    '</tr>',
+    //Data Nodes
+    '<tr>',
+    '<td>{{t dashboard.services.hdfs.datanodes}}</td>',
+    '<td>',
+    '<a href="#" {{action filterHosts view.dataNodeComponent}}>{{view.model.dataNodes.length}} {{t dashboard.services.hdfs.datanodes}}</a>',
+    '</td>',
+    '</tr>',
+    // NameNode Web UI
+    //    '<tr>',
+    //    '<td>{{t dashboard.services.hdfs.nameNodeWebUI}}</td>',
+    //    '<td><a {{bindAttr href="view.nodeWebUrl"}} target="_blank">{{view.model.nameNode.publicHostName}}:50070</a>',
+    //    '</td>',
+    //    '</tr>',
+    '</table>',
+
+    '</div>',
+    '</li>',
+    '</div>'
+
+
+  ].join('\n')),
+
+  dataNodeComponent: function () {
+    return App.HostComponent.find().findProperty('componentName', 'DATANODE');
+  }.property(),
+
+  nodeWebUrl: function () {
+    return "http://" + this.get('model').get('nameNode').get('publicHostName') + ":50070";
+  }.property('model.nameNode')
+
+})
+
+App.HDFSLinksView. reopenClass({
+  isLinks: true
+})

+ 105 - 0
ambari-web/app/views/main/dashboard/widgets/jobtracker_cpu.js

@@ -0,0 +1,105 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.JobTrackerCpuPieChartView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.JobTrackerCpu'),
+  id: '7',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'mapreduce',
+  hiddenInfo: function () {
+    var value = this.get('model.jobTrackerCpu');
+    value = value >= 100 ? 100: value;
+    var result = [];
+    result.pushObject((value + 0).toFixed(2) + '%');
+    result.pushObject(' CPU wait I/O');
+    return result;
+  }.property('model.jobTrackerCpu'),
+
+  thresh1: 40,// can be customized
+  thresh2: 70,
+  maxValue: 100,
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-jt-cpu', // html id
+    stroke: '#D6DDDF', //light grey
+    thresh1: 50,  // can be customized later
+    thresh2: 80,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var value = this.get('model.jobTrackerCpu');
+      //console.log('JT Cpu ' + value);
+      value = value >= 100 ? 100: value;
+      var percent = (value + 0).toFixed();
+      return [ percent, 100 - percent];
+    }.property('model.jobTrackerCpu'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'thresh1', 'thresh2'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      old_svg.remove();
+
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.jobTrackerCpu', 'thresh1', 'thresh2')
+  })
+
+})

+ 127 - 0
ambari-web/app/views/main/dashboard/widgets/jobtracker_heap.js

@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.JobTrackerHeapPieChartView = App.DashboardWidgetView.extend({
+  title: Em.I18n.t('dashboard.widgets.JobTrackerHeap'),
+  id: '6',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'mapreduce',
+
+  hiddenInfo: function () {
+    var heapUsed = this.get('model').get('jobTrackerHeapUsed') || 0;
+    var heapMax = this.get('model').get('jobTrackerHeapMax') || 0;
+    var percent = heapMax > 0 ? 100 * heapUsed / heapMax : 0;
+    var result = [];
+    result.pushObject(heapUsed.bytesToSize(1, "parseFloat") + ' of ' + heapMax.bytesToSize(1, "parseFloat"));
+    result.pushObject(percent.toFixed(1) + '% used');
+    return result;
+  }.property('model.jobTrackerHeapUsed', 'model.jobTrackerHeapMax'),
+
+  thresh1: 40,// can be customized
+  thresh2: 70,
+  maxValue: 100,
+
+  isPieExist: function () {
+    var total = this.get('model.jobTrackerHeapMax') * 1000000;
+    return total > 0 ;
+  }.property('model.jobTrackerHeapMax'),
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row">',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '{{#if view.isPieExist}}',
+      '<div class="widget-content" >','{{view view.content modelBinding="view.model" thresh1Binding="view.thresh1" thresh2Binding="view.thresh2"}}','</div>',
+    '{{else}}',
+      '<div class="widget-content-isNA" >','{{t services.service.summary.notAvailable}}','</div>',
+    '{{/if}}',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-jt-heap', // id in html
+    stroke: '#D6DDDF', //light grey
+    thresh1: null,
+    thresh2: null,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function (){
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var used = this.get('model.jobTrackerHeapUsed') * 1000000;
+      var total = this.get('model.jobTrackerHeapMax') * 1000000;
+      var percent = total > 0 ? ((used)*100 / total).toFixed() : 0;
+      return [ percent, 100 - percent];
+    }.property('model.jobTrackerHeapUsed', 'model.jobTrackerHeapMax'),
+
+    contentColor: function (){
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'this.thresh1', 'this.thresh2'),
+
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      old_svg.remove();
+
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.jobTrackerHeapUsed', 'model.jobTrackerHeapMax', 'this.thresh1', 'this.thresh2')
+  })
+
+})

+ 203 - 0
ambari-web/app/views/main/dashboard/widgets/jobtracker_rpc.js

@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.JobTrackerRpcView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.JobTrackerRpc'),
+  id:'9',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'mapreduce',
+  hiddenInfo: function (){
+    var result = [];
+    result.pushObject(this.get('content') + ' average RPC');
+    result.pushObject('queue wait time');
+    return result;
+  }.property('model.jobTrackerRpc'),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+  isNA: function () {
+     return this.get('data') === null;
+  }.property('data'),
+
+  thresh1: 0.5,
+  thresh2: 2,
+  maxValue: 'infinity',
+
+  data: function (){
+    if (this.get('model.jobTrackerRpc')) {
+      return (this.get('model.jobTrackerRpc')).toFixed(2);
+    } else {
+      if (this.get('model.jobTrackerRpc') == 0) {
+        return 0;
+      } else {
+        return null;
+      }
+    }
+  }.property('model.jobTrackerRpc'),
+
+  content: function () {
+    if (this.get('data') || this.get('data') == 0) {
+      return this.get('data') + " ms";
+    } else {
+      return this.t('services.service.summary.notAvailable');
+    }
+  }.property('model.jobTrackerRpc'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the thresholds to change the color of current widget. ' +
+        ' The unit is milli-second. '+
+        ' So enter two numbers larger than 0. ',
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: 'infinity',
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number larger than 0');
+          } else if (this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number larger than 0');
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else{
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+      }.observes('thresh1', 'thresh2')
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function () {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            // save to persist
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var colors = ['#95A800', '#FF8E00', '#B80000']; //color green, orange ,red
+        var handlers = [33, 66]; //fixed value
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', true);
+          $("#slider-range").slider({
+            range:true,
+            disabled:true,
+            min: 0,
+            max: 100,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            }
+          });
+
+          function updateColors (handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', true);
+        }
+      }
+    });
+  }
+
+})

+ 132 - 0
ambari-web/app/views/main/dashboard/widgets/jobtracker_uptime.js

@@ -0,0 +1,132 @@
+/**
+ * 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 date = require('utils/date');
+
+App.JobTrackerUptimeView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.JobTrackerUptime'),
+  id: '16',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'mapreduce',
+  hiddenInfo: [],
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    return this.get('data') != null;
+  }.property('data'),
+  isOrange: function () {
+   return false;
+  }.property('data'),
+  isRed: function () {
+    return false;
+  }.property('data'),
+  isNA: function () {
+    return this.get('data') == null;
+  }.property('data'),
+
+  thresh1: 5,
+  thresh2: 10,
+  maxValue: 'infinity',
+
+  data: function(){
+    var uptime = this.get('model.jobTrackerStartTime');
+    if (uptime && uptime > 0) {
+      var uptimeString = this.timeConverter(uptime);
+      var diff = (new Date()).getTime() - uptime;
+      if (diff < 0) {
+        diff = 0;
+      }
+      var formatted = date.timingFormat(diff); //17.67 days
+      var timeUnit = null;
+      switch (formatted.split(" ")[1]){
+        case 'secs':
+          timeUnit = 's';
+          break;
+        case 'hours':
+          timeUnit = 'hr';
+          break;
+        case 'days':
+          timeUnit = 'd';
+          break;
+        case 'mins':
+          timeUnit = 'min';
+          break;
+        default:
+          timeUnit = formatted.split(" ")[1];
+      }
+      this.set('timeUnit', timeUnit);
+      this.set('hiddenInfo', []);
+      this.get('hiddenInfo').pushObject(formatted);
+      this.get('hiddenInfo').pushObject(uptimeString[0]);
+      this.get('hiddenInfo').pushObject(uptimeString[1]);
+      return parseFloat(formatted.split(" ")[0]);
+    }
+    this.set('hiddenInfo', ['JobTracker','Not running']);
+    return null;
+  }.property('model.jobTrackerStartTime'),
+
+  timeUnit: null,
+
+  content: function () {
+    var data = this.get('data');
+    if (data) {
+      return data.toFixed(1) + ' '+ this.get('timeUnit');
+    } else {
+      return this.t('services.service.summary.notAvailable');
+    }
+  }.property('model.jobTrackerStartTime'),
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row" >',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<div class="hidden-info-three-line">', '<table  align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+    '<div class="widget-content">{{view.content}}</div>',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  timeConverter: function (timestamp) {
+    var origin = new Date(timestamp);
+    origin = origin.toString();
+    var result = [];
+    var start = origin.indexOf('GMT');
+    if(start == -1){ // ie
+      var arr = origin.split(" ");
+      result.pushObject(arr[0] + " " + arr[1] + " " + arr[2] + " " + arr[3]);
+      var second = '';
+      for(var i = 4; i < arr.length; i++){
+        second = second + " " + arr[i];
+      }
+      result.pushObject(second);
+    }else{ // other browsers
+      var end = origin.indexOf(" ", start);
+      result.pushObject(origin.slice(0, start-10));
+      result.pushObject(origin.slice(start-9));
+    }
+    return result;
+  }
+
+})

+ 94 - 0
ambari-web/app/views/main/dashboard/widgets/mapreduce_links.js

@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.MapReduceLinksView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.MapReduceLinks'),
+  id: '18',
+
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+  isLinks: true,
+  model_type: 'mapreduce',
+
+  template: Ember.Handlebars.compile([
+    '<div class="links">',
+    '<li class="thumbnail row">',
+      '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span8">', '{{view.title}}','</div>',
+    '<div class="span3 link-button">',
+    '{{#if view.model.quickLinks.length}}',
+      '{{#view App.QuickViewLinks contentBinding="view.model"}}',
+        '<div class="btn-group">',
+        '<a class="btn btn-mini dropdown-toggle" data-toggle="dropdown" href="#">',
+          '{{t common.more}}',
+          '<span class="caret"></span>',
+        '</a>',
+        '<ul class="dropdown-menu">',
+          '{{#each view.quickLinks}}',
+          '<li><a {{bindAttr href="url"}} target="_blank">{{label}}</a></li>',
+          '{{/each}}',
+        '</ul>',
+        '</div>',
+      '{{/view}}',
+    '{{/if}}',
+
+    '</div>',
+    '<div class="widget-content" >',
+    '<table>',
+    //jobTracker
+    '<tr>',
+    '<td>{{t services.service.summary.jobTracker}}</td>',
+    '<td><a href="#" {{action showDetails view.model.jobTracker}}>{{view.model.jobTracker.publicHostName}}</a></td>',
+    '</tr>',
+    //taskTrackers
+    '<tr>',
+    '<td>{{t dashboard.services.mapreduce.taskTrackers}}</td>',
+    '<td><a href="#" {{action filterHosts view.taskTrackerComponent}}>{{view.model.taskTrackers.length}} {{t dashboard.services.mapreduce.taskTrackers}}</a></td>',
+    '</tr>',
+
+    // jobTracker Web UI
+    '<tr>',
+    '<td>{{t services.service.summary.jobTrackerWebUI}}</td>',
+    '<td><a {{bindAttr href="view.jobTrackerWebUrl"}} target="_blank">{{view.model.jobTracker.publicHostName}}:50030</a>','</td>',
+    '</tr>',
+    '</table>',
+
+    '</div>',
+    '</li>',
+    '</div>'
+
+
+  ].join('\n')),
+
+  taskTrackerComponent: function () {
+    return App.HostComponent.find().findProperty('componentName', 'TASKTRACKER');
+  }.property(),
+
+  jobTrackerWebUrl: function () {
+    return "http://" + this.get('model').get('jobTracker').get('publicHostName') + ":50030";
+  }.property('model.nameNode')
+
+})
+
+App.MapReduceLinksView. reopenClass({
+  isLinks: true
+})

+ 77 - 0
ambari-web/app/views/main/dashboard/widgets/mapreduce_slots.js

@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.MapReduceSlotsView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.MapReduceSlots'),
+  id:'10',
+
+  isPieChart: false,
+  isText: false,
+  isProgressBar: true,
+  model_type: 'mapreduce',
+  hiddenInfo: function (){
+    var result = [];
+    result.pushObject('Occupied Slots/ Reserved Slots/ Total Slots');
+    return result;
+  }.property(),
+
+  map_occupied: function () {
+    if (this.get('model.mapSlotsOccupied')) {
+      return "width: " + ((this.get('model.mapSlotsOccupied'))*100/(this.get('model.mapSlots'))).toString() + "%";
+    } else {
+      return "width: 0%";
+    }
+  }.property('model.mapSlotsOccupied','model.mapSlots'),
+  map_reserved: function () {
+    if (this.get('model.mapSlotsReserved')) {
+      return "width: " + ((this.get('model.mapSlotsReserved'))*100/(this.get('model.mapSlots'))).toString() + "%";
+    } else {
+      return "width: 0%";
+    }
+  }.property('model.mapSlotsReserved','model.mapSlots'),
+  map_display_text: function () {
+    return this.get('model.mapSlotsOccupied') + "/" + this.get('model.mapSlotsReserved') + "/" + this.get('model.mapSlots');
+  }.property('model.mapSlotsReserved','model.mapSlotsOccupied','model.mapSlots'),
+
+
+  reduce_occupied: function () {
+    if (this.get('model.reduceSlotsOccupied')) {
+      return "width: " + ((this.get('model.reduceSlotsOccupied'))*100/(this.get('model.reduceSlots'))).toString() + "%";
+    } else {
+      return "width: 0%";
+    }
+  }.property('model.reduceSlotsOccupied','model.reduceSlots'),
+  reduce_reserved: function () {
+    if (this.get('model.reduceSlotsReserved')) {
+      return "width: " + ((this.get('model.reduceSlotsReserved'))*100/(this.get('model.reduceSlots'))).toString() + "%";
+    } else {
+      return "width: 0%";
+    }
+  }.property('model.reduceSlotsReserved','model.reduceSlots'),
+  reduce_display_text: function () {
+    return this.get('model.reduceSlotsOccupied') + "/" + this.get('model.reduceSlotsReserved') + "/" + this.get('model.reduceSlots');
+  }.property('model.reduceSlotsReserved','model.reduceSlotsOccupied','model.reduceSlots')
+
+})
+
+App.MapReduceSlotsView.reopenClass({
+  isProgressBar: true
+})

+ 34 - 0
ambari-web/app/views/main/dashboard/widgets/metrics_cpu.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ChartClusterMetricsCPUWidgetView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.clusterMetrics.cpu'),
+  id: '13',
+
+  isClusterMetrics: true,
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+
+  content: App.ChartClusterMetricsCPU.extend({
+    noTitleUnderGraph: true
+  })
+})

+ 34 - 0
ambari-web/app/views/main/dashboard/widgets/metrics_load.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ChartClusterMetricsLoadWidgetView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.clusterMetrics.load'),
+  id: '14',
+
+  isClusterMetrics: true,
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+
+  content: App.ChartClusterMetricsLoad.extend({
+    noTitleUnderGraph: true
+  })
+})

+ 34 - 0
ambari-web/app/views/main/dashboard/widgets/metrics_memory.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ChartClusterMetricsMemoryWidgetView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.clusterMetrics.memory'),
+  id: '11',
+
+  isClusterMetrics: true,
+  isPieChart: false,
+  isText: false,
+  isProgressBar: false,
+
+  content: App.ChartClusterMetricsMemory.extend({
+    noTitleUnderGraph: true
+  })
+})

+ 34 - 0
ambari-web/app/views/main/dashboard/widgets/metrics_network.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ChartClusterMetricsNetworkWidgetView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.clusterMetrics.network'),
+  id: '12',
+
+  isClusterMetrics: true,
+  isPieChart: false,
+  isText:false,
+  isProgressBar:false,
+
+  content: App.ChartClusterMetricsNetwork.extend({
+    noTitleUnderGraph: true
+  })
+})

+ 104 - 0
ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js

@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.NameNodeCpuPieChartView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.NameNodeCpu'),
+  id: '3',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'hdfs',
+  hiddenInfo: function () {
+    var value = this.get('model.nameNodeCpu');
+    value = value >= 100 ? 100: value;
+    var result = [];
+    result.pushObject((value + 0).toFixed(2) + '%');
+    result.pushObject(' CPU wait I/O');
+    return result;
+  }.property('model.nameNodeCpu'),
+
+  thresh1: 40,// can be customized
+  thresh2: 70,
+  maxValue: 100,
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-nn-cpu', // html id
+    stroke: '#D6DDDF', //light grey
+    thresh1: null,  // can be customized later
+    thresh2: null,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette ({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var value = this.get('model.nameNodeCpu');
+      value = value >= 100 ? 100: value;
+      var percent = (value + 0).toFixed();
+      return [ percent, 100 - percent];
+    }.property('model.nameNodeCpu'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'thresh1', 'thresh2'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      old_svg.remove();
+
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.nameNodeCpu', 'thresh1', 'thresh2')
+  })
+
+})

+ 132 - 0
ambari-web/app/views/main/dashboard/widgets/namenode_heap.js

@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.NameNodeHeapPieChartView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.NameNodeHeap'),
+  id: '1',
+
+  isPieChart: true,
+  isText: false,
+  isProgressBar: false,
+  model_type: 'hdfs',
+
+  hiddenInfo: function () {
+  var memUsed = this.get('model').get('jvmMemoryHeapUsed') * 1000000;
+  var memCommitted = this.get('model').get('jvmMemoryHeapCommitted') * 1000000;
+  var percent = memCommitted > 0 ? ((100 * memUsed) / memCommitted) : 0;
+  var result = [];
+  result.pushObject(memUsed.bytesToSize(1, 'parseFloat') + ' of ' + memCommitted.bytesToSize(1, 'parseFloat'));
+  result.pushObject(percent.toFixed(1) + '% used');
+  return result;
+  }.property('model.jvmMemoryHeapUsed', 'model.jvmMemoryHeapCommitted'),
+
+  thresh1: null,
+  thresh2: null,
+  maxValue: 100,
+
+  isPieExist: function () {
+    var total = this.get('model.jvmMemoryHeapCommitted') * 1000000;
+    return total > 0 ;
+  }.property('model.jvmMemoryHeapCommitted'),
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row">',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<a class="corner-icon span1" href="#" {{action editWidget target="view"}}>','<i class="icon-edit"></i>','</a>',
+    '<div class="hidden-info">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+
+    '{{#if view.isPieExist}}',
+      '<div class="widget-content" >','{{view view.content modelBinding="view.model" thresh1Binding="view.thresh1" thresh2Binding="view.thresh2"}}','</div>',
+    '{{else}}',
+      '<div class="widget-content-isNA" >','{{t services.service.summary.notAvailable}}','</div>',
+    '{{/if}}',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  content: App.ChartPieView.extend({
+
+    model: null,  //data bind here
+    id: 'widget-nn-heap', // html id
+    stroke: '#D6DDDF', //light grey
+    thresh1: null, //bind from parent
+    thresh2: null,
+    innerR: 25,
+
+    existCenterText: true,
+    centerTextColor: function () {
+      return this.get('contentColor');
+    }.property('contentColor'),
+
+    palette: new Rickshaw.Color.Palette({
+      scheme: [ '#FFFFFF', '#D6DDDF'].reverse()
+    }),
+
+    data: function () {
+      var used = this.get('model.jvmMemoryHeapUsed') * 1000000;
+      var total = this.get('model.jvmMemoryHeapCommitted') * 1000000;
+      var percent = total > 0 ? ((used)*100 / total).toFixed() : 0;
+      return [ percent, 100 - percent];
+    }.property('model.jvmMemoryHeapUsed', 'model.jvmMemoryHeapCommitted'),
+
+    contentColor: function () {
+      var used = parseFloat(this.get('data')[0]);
+      var thresh1 = parseFloat(this.get('thresh1'));
+      var thresh2 = parseFloat(this.get('thresh2'));
+      var color_green = '#95A800';
+      var color_red = '#B80000';
+      var color_orange = '#FF8E00';
+      if (used <= thresh1) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_green  ].reverse()
+        }))
+        return color_green;
+      } else if (used <= thresh2) {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_orange  ].reverse()
+        }))
+        return color_orange;
+      } else {
+        this.set('palette', new Rickshaw.Color.Palette({
+          scheme: [ '#FFFFFF', color_red  ].reverse()
+        }))
+        return color_red;
+      }
+    }.property('data', 'this.thresh1', 'this.thresh2'),
+
+    // refresh text and color when data in model changed
+    refreshSvg: function () {
+      // remove old svg
+      var old_svg =  $("#" + this.id);
+      if(old_svg){
+        old_svg.remove();
+      }
+      // draw new svg
+      this.appendSvg();
+    }.observes('model.jvmMemoryHeapUsed', 'model.jvmMemoryHeapCommitted', 'this.thresh1', 'this.thresh2')
+  })
+
+})
+
+

+ 204 - 0
ambari-web/app/views/main/dashboard/widgets/namenode_rpc.js

@@ -0,0 +1,204 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.NameNodeRpcView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.NameNodeRpc'),
+  id: '5',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'hdfs',
+  hiddenInfo: function () {
+    var result = [];
+    result.pushObject(this.get('content') + ' average RPC');
+    result.pushObject('queue wait time');
+    return result;
+  }.property('model.nameNodeRpc'),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+  isNA: function () {
+    return this.get('data') === null;
+  }.property('data'),
+
+  thresh1: 0.5,
+  thresh2: 2,
+  maxValue: 'infinity',
+
+  data: function () {
+    if (this.get('model.nameNodeRpc')) {
+      return (this.get('model.nameNodeRpc')).toFixed(2);
+    } else {
+      if (this.get('model.nameNodeRpc') == 0) {
+        return 0;
+      } else {
+        return null;
+      }
+    }
+  }.property('model.nameNodeRpc'),
+
+  content: function () {
+    if (this.get('data') || this.get('data') == 0) {
+      return this.get('data') + " ms";
+    } else {
+      return this.t('services.service.summary.notAvailable');
+    }
+  }.property('model.nameNodeRpc'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the thresholds to change the color of current widget. ' +
+        ' The unit is milli-second. '+
+        ' So enter two numbers larger than 0. ',
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: 'infinity',
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number larger than 0');
+          } else if ( this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)){
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number larger than 0');
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+      }.observes('thresh1', 'thresh2')
+
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: ['sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function () {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            //save to persist
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var colors = ['#95A800', '#FF8E00', '#B80000']; //color green, orange ,red
+        var handlers = [33, 66]; //fixed value
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', true);
+          $("#slider-range").slider({
+            range:true,
+            disabled:true, //handlers cannot move
+            min: 0,
+            max: 100,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            }
+          });
+
+          function updateColors (handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', true);
+        }
+      }
+    });
+  }
+
+})

+ 134 - 0
ambari-web/app/views/main/dashboard/widgets/namenode_uptime.js

@@ -0,0 +1,134 @@
+/**
+ * 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 date = require('utils/date');
+
+App.NameNodeUptimeView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.NameNodeUptime'),
+  id: '15',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'hdfs',
+  hiddenInfo: [],
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen', 'isNA'],
+  isGreen: function () {
+    return this.get('data') != null;
+  }.property('data'),
+  isOrange: function () {
+    return false;
+  }.property('data'),
+  isRed: function () {
+    return false;
+  }.property('data'),
+  isNA: function () {
+    return this.get('data') == null;
+  }.property('data'),
+
+  thresh1: 5,
+  thresh2: 10,
+  maxValue: 'infinity',
+
+  data: function () {
+    var uptime = this.get('model.nameNodeStartTime');
+    if (uptime && uptime > 0) {
+      var uptimeString = this.timeConverter(uptime);
+      var diff = (new Date()).getTime() - uptime;
+      if (diff < 0) {
+        diff = 0;
+      }
+      var formatted = date.timingFormat(diff); //17.67 days
+      var timeUnit = null;
+      switch (formatted.split(" ")[1]) {
+        case 'secs':
+          timeUnit = 's';
+          break;
+        case 'hours':
+          timeUnit = 'hr';
+          break;
+        case 'days':
+          timeUnit = 'd';
+          break;
+        case 'mins':
+          timeUnit = 'min';
+          break;
+        default:
+          timeUnit = formatted.split(" ")[1];
+      }
+      this.set('timeUnit', timeUnit);
+      this.set('hiddenInfo', []);
+      this.get('hiddenInfo').pushObject(formatted);
+      this.get('hiddenInfo').pushObject(uptimeString[0]);
+      this.get('hiddenInfo').pushObject(uptimeString[1]);
+      return parseFloat(formatted.split(" ")[0]);
+    }
+    this.set('hiddenInfo', []);
+    this.set('hiddenInfo', ['NameNode','Not running']);
+    return null;
+  }.property('model.nameNodeStartTime'),
+
+  timeUnit: null,
+
+  content: function () {
+    var data = this.get('data');
+    if (data) {
+      return data.toFixed(1) + ' '+ this.get('timeUnit');
+    } else {
+      return this.t('services.service.summary.notAvailable');
+    }
+  }.property('model.nameNodeStartTime'),
+
+
+  template: Ember.Handlebars.compile([
+
+    '<div class="has-hidden-info">',
+    '<li class="thumbnail row" >',
+    '<a class="corner-icon" href="#" {{action deleteWidget target="view"}}>','<i class="icon-remove-sign icon-large"></i>','</a>',
+    '<div class="caption span10">', '{{view.title}}','</div>',
+    '<div class="hidden-info-three-line">', '<table align="center">{{#each line in view.hiddenInfo}}', '<tr><td>{{line}}</td></tr>','{{/each}}</table>','</div>',
+    '<div class="widget-content">{{view.content}}</div>',
+    '</li>',
+    '</div>'
+  ].join('\n')),
+
+  timeConverter: function (timestamp){
+    var origin = new Date(timestamp);
+    origin = origin.toString();
+    var result = [];
+    var start = origin.indexOf('GMT');
+    if (start == -1) { // ie
+      var arr = origin.split(" ");
+      result.pushObject(arr[0] + " " + arr[1] + " " + arr[2] + " " + arr[3]);
+      var second = '';
+      for (var i = 4; i < arr.length; i++) {
+        second = second + " " + arr[i];
+      }
+      result.pushObject(second);
+    } else { // other browsers
+      var end = origin.indexOf(" ", start);
+      result.pushObject(origin.slice(0, start-10));
+      result.pushObject(origin.slice(start-9));
+    }
+    return result;
+  }
+
+})

+ 205 - 0
ambari-web/app/views/main/dashboard/widgets/tasktracker_live.js

@@ -0,0 +1,205 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.TaskTrackerUpView = App.DashboardWidgetView.extend({
+
+  title: Em.I18n.t('dashboard.widgets.TaskTrackerUp'),
+  id: '8',
+
+  isPieChart: false,
+  isText: true,
+  isProgressBar: false,
+  model_type: 'mapreduce',
+
+  hiddenInfo: function () {
+    var svc = this.get('model');
+    var liveCount = svc.get('aliveTrackers').get('length');
+    var totalCount = svc.get('taskTrackers').get('length');
+    var template = this.t('dashboard.services.mapreduce.trackersSummary');
+    var result = [];
+    result.pushObject(template.format(liveCount, totalCount));
+    return result;
+  }.property('model.aliveTrackers.length', 'model.taskTrackers.length'),
+
+  classNameBindings: ['isRed', 'isOrange', 'isGreen'],
+  isRed: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') <= thresh1? true: false;
+  }.property('data','thresh1','thresh2'),
+  isOrange: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return (this.get('data') <= thresh2 && this.get('data') > thresh1 )? true: false;
+  }.property('data','thresh1','thresh2'),
+  isGreen: function () {
+    var thresh1 = this.get('thresh1');
+    var thresh2 = this.get('thresh2');
+    return this.get('data') > thresh2? true: false;
+  }.property('data','thresh1','thresh2'),
+
+  thresh1: 40,
+  thresh2: 70,
+  maxValue: 100,
+
+  data: function () {
+    return ((this.get('model.aliveTrackers.length')/ this.get('model.taskTrackers.length')).toFixed(2)) * 100;
+  }.property('model.taskTrackers.length', 'model.aliveTrackers.length'),
+
+  content: function () {
+    return this.get('model.aliveTrackers.length') + "/" + this.get('model.taskTrackers.length');
+  }.property('model.taskTrackers.length', 'model.aliveTrackers.length'),
+
+  editWidget: function (event) {
+    var parent = this;
+    var max_tmp =  parseFloat(parent.get('maxValue'));
+    var configObj = Ember.Object.create({
+      thresh1: parent.get('thresh1') + '',
+      thresh2: parent.get('thresh2') + '',
+      hintInfo: 'Edit the percentage of thresholds to change the color of current widget. ' +
+        ' Assume all task trackers UP is 100, and all DOWN is 0. '+
+        ' So enter two numbers between 0 to ' + max_tmp,
+      isThresh1Error: false,
+      isThresh2Error: false,
+      errorMessage1: "",
+      errorMessage2: "",
+      maxValue: max_tmp,
+      observeNewThresholdValue: function () {
+        var thresh1 = this.get('thresh1');
+        var thresh2 = this.get('thresh2');
+        if (thresh1.trim() != "") {
+          if (isNaN(thresh1) || thresh1 > max_tmp || thresh1 < 0) {
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else if (this.get('isThresh2Error') === false && parseFloat(thresh2)<= parseFloat(thresh1)){
+            this.set('isThresh1Error', true);
+            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
+          } else {
+            this.set('isThresh1Error', false);
+            this.set('errorMessage1', '');
+          }
+        } else {
+          this.set('isThresh1Error', true);
+          this.set('errorMessage1', 'This is required');
+        }
+
+        if (thresh2.trim() != "") {
+          if (isNaN(thresh2) || thresh2 > max_tmp || thresh2 < 0) {
+            this.set('isThresh2Error', true);
+            this.set('errorMessage2', 'Invalid! Enter a number between 0 - ' + max_tmp);
+          } else {
+            this.set('isThresh2Error', false);
+            this.set('errorMessage2', '');
+          }
+        } else {
+          this.set('isThresh2Error', true);
+          this.set('errorMessage2', 'This is required');
+        }
+
+        // update the slider handles and color
+        if (this.get('isThresh1Error') === false && this.get('isThresh2Error') === false) {
+          $("#slider-range").slider('values', 0 , parseFloat(thresh1));
+          $("#slider-range").slider('values', 1 , parseFloat(thresh2));
+        }
+      }.observes('thresh1', 'thresh2')
+    });
+
+    var browserVerion = this.getInternetExplorerVersion();
+    App.ModalPopup.show({
+      header: 'Customize Widget',
+      classNames: [ 'sixty-percent-width-modal-edit-widget'],
+      bodyClass: Ember.View.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup'),
+        configPropertyObj: configObj
+      }),
+      primary: Em.I18n.t('common.apply'),
+      onPrimary: function() {
+        configObj.observeNewThresholdValue();
+        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
+          parent.set('thresh1', parseFloat(configObj.get('thresh1')) );
+          parent.set('thresh2', parseFloat(configObj.get('thresh2')) );
+          if (!App.testMode) {
+            //save to persit
+            var big_parent = parent.get('parentView');
+            big_parent.getUserPref(big_parent.get('persistKey'));
+            var oldValue = big_parent.get('currentPrefObject');
+            oldValue.threshold[parseInt(parent.id)] = [configObj.get('thresh1'), configObj.get('thresh2')];
+            big_parent.postUserPref(big_parent.get('persistKey'),oldValue);
+          }
+          this.hide();
+        }
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      onSecondary: function () {
+        this.hide();
+      },
+
+      didInsertElement: function () {
+        var handlers = [configObj.get('thresh1'), configObj.get('thresh2')];
+        var colors = ['#B80000', '#FF8E00', '#95A800']; //color red, orange, green
+
+        if (browserVerion == -1 || browserVerion > 9) {
+          configObj.set('isIE9', false);
+          configObj.set('isGreenOrangeRed', false);
+          $("#slider-range").slider({
+            range: true,
+            min: 0,
+            max: max_tmp,
+            values: handlers,
+            create: function (event, ui) {
+              updateColors(handlers);
+            },
+            slide: function (event, ui) {
+              updateColors(ui.values);
+              configObj.set('thresh1', ui.values[0] + '');
+              configObj.set('thresh2', ui.values[1] + '');
+            },
+            change: function (event, ui) {
+              updateColors(ui.values);
+            }
+          });
+
+          function updateColors(handlers) {
+            var colorstops = colors[0] + ", "; // start with the first color
+            for (var i = 0; i < handlers.length; i++) {
+              colorstops += colors[i] + " " + handlers[i] + "%,";
+              colorstops += colors[i+1] + " " + handlers[i] + "%,";
+            }
+            // end with the last color
+            colorstops += colors[colors.length - 1];
+            var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
+            $('#slider-range').css('background-image', css1);
+            var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
+            $('#slider-range').css('background-image', css2);
+            //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
+            var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
+            $('#slider-range').css('background-image', css3);
+
+            $('#slider-range .ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
+          }
+        } else {
+          configObj.set('isIE9', true);
+          configObj.set('isGreenOrangeRed', false);
+        }
+      }
+    });
+  }
+
+})

+ 4 - 3
ambari-web/vendor/scripts/jquery.ui.sortable.js

@@ -979,9 +979,10 @@ $.widget("ui.sortable", $.ui.mouse, {
 		this.counter = this.counter ? ++this.counter : 1;
 		var counter = this.counter;
 
-		this._delay(function() {
-			if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
-		});
+    // Cause error when sort widgets on dashboard. Error: Object has no _delay method
+    //this._delay(function() {
+    //if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+    //});
 
 	},