You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2013/03/23 05:38:03 UTC

svn commit: r1460092 - in /incubator/ambari/trunk: ./ ambari-web/app/ ambari-web/app/controllers/main/ ambari-web/app/mappers/ ambari-web/app/models/ ambari-web/app/routes/ ambari-web/app/styles/ ambari-web/app/templates/main/ ambari-web/app/views/comm...

Author: yusaku
Date: Sat Mar 23 04:38:02 2013
New Revision: 1460092

URL: http://svn.apache.org/r1460092
Log:
AMBARI-1653. HDFS Mirroring: Display DataSets table. (yusaku)

Added:
    incubator/ambari/trunk/ambari-web/app/controllers/main/mirroring_controller.js
    incubator/ambari/trunk/ambari-web/app/templates/main/mirroring.hbs
    incubator/ambari/trunk/ambari-web/app/views/main/mirroring_view.js
Modified:
    incubator/ambari/trunk/CHANGES.txt
    incubator/ambari/trunk/ambari-web/app/controllers.js
    incubator/ambari/trunk/ambari-web/app/mappers/dataset_mapper.js
    incubator/ambari/trunk/ambari-web/app/messages.js
    incubator/ambari/trunk/ambari-web/app/models/dataset.js
    incubator/ambari/trunk/ambari-web/app/models/host.js
    incubator/ambari/trunk/ambari-web/app/routes/main.js
    incubator/ambari/trunk/ambari-web/app/styles/application.less
    incubator/ambari/trunk/ambari-web/app/views.js
    incubator/ambari/trunk/ambari-web/app/views/common/filter_view.js
    incubator/ambari/trunk/ambari-web/app/views/common/sort_view.js
    incubator/ambari/trunk/ambari-web/app/views/main/host.js
    incubator/ambari/trunk/ambari-web/app/views/main/menu.js

Modified: incubator/ambari/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/CHANGES.txt?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/CHANGES.txt (original)
+++ incubator/ambari/trunk/CHANGES.txt Sat Mar 23 04:38:02 2013
@@ -12,6 +12,8 @@ Trunk (unreleased changes):
 
  NEW FEATURES
 
+ AMBARI-1653. HDFS Mirroring: Display DataSets table. (yusaku)
+
  AMBARI-1658. Implement API/Service Provider for HDFS mirroring. (tbeerbower)
 
  AMBARI-1422. Allow client to specify a "context" value for asynchronous requests (jspeidel)

Modified: incubator/ambari/trunk/ambari-web/app/controllers.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/controllers.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/controllers.js (original)
+++ incubator/ambari/trunk/ambari-web/app/controllers.js Sat Mar 23 04:38:02 2013
@@ -75,6 +75,7 @@ require('controllers/main/charts/heatmap
 require('controllers/main/charts/heatmap');
 require('controllers/main/apps_controller');
 require('controllers/main/apps/item_controller');
+require('controllers/main/mirroring_controller');
 require('controllers/wizard/slave_component_groups_controller');
 require('controllers/wizard/step1_controller');
 require('controllers/wizard/step2_controller');

Added: incubator/ambari/trunk/ambari-web/app/controllers/main/mirroring_controller.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/controllers/main/mirroring_controller.js?rev=1460092&view=auto
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/controllers/main/mirroring_controller.js (added)
+++ incubator/ambari/trunk/ambari-web/app/controllers/main/mirroring_controller.js Sat Mar 23 04:38:02 2013
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.MainMirroringController = Em.ArrayController.extend({
+  name:'mainMirroringController',
+  content: App.DataSet.find()
+});

Modified: incubator/ambari/trunk/ambari-web/app/mappers/dataset_mapper.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/mappers/dataset_mapper.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/mappers/dataset_mapper.js (original)
+++ incubator/ambari/trunk/ambari-web/app/mappers/dataset_mapper.js Sat Mar 23 04:38:02 2013
@@ -46,16 +46,6 @@ App.dataSetMapper = App.QuickDataMapper.
     //data: 'Instances.details'
   },
 
-  getDuration: function (startDate, endDate) {
-
-    var milliseconds = endDate - startDate;
-    var date = new Date(milliseconds);
-    var h = Math.floor(milliseconds / 3600000);
-    var m = Math.floor((milliseconds % 3600000) / 60000);
-    var s = Math.floor(((milliseconds % 360000) % 60000) / 1000);
-    return (h == 0 ? '' : h + 'hr ') + (m == 0 ? '' : m + 'mins ') + (s == 0 ? '' : s + 'secs ');
-
-  },
   map: function (json) {
     if (!this.get('model')) {
       return;
@@ -91,13 +81,13 @@ App.dataSetMapper = App.QuickDataMapper.
             }
             if (job.Instances.status === 'FAILED') {
               if (last_failed_date == null || last_failed_date < end_date) {
-                item.last_failed_date = end_date;
+                item.last_failed_date = end_date.getTime();
                 last_failed_date = end_date;
               }
             }
             else if (job.Instances.status === 'SUCCESSFUL') {
               if (last_succeeded_date == null || last_succeeded_date < end_date) {
-                item.last_succeeded_date = end_date;
+                item.last_succeeded_date = end_date.getTime();
                 last_succeeded_date = end_date;
               }
             }
@@ -109,7 +99,7 @@ App.dataSetMapper = App.QuickDataMapper.
 
           var last_end_date = new Date(item.last_job.Instances.end);
           var last_start_date = new Date(item.last_job.Instances.start);
-          item.last_duration = this.getDuration(last_start_date, last_end_date);
+          item.last_duration = last_end_date - last_start_date;
 
 
           item.avg_data = '';
@@ -135,7 +125,7 @@ App.dataSetMapper = App.QuickDataMapper.
             instance.Instances.end = new Date(instance.Instances.end); // neeed to be calulated end -start
 
 
-            instance.duration = this.getDuration(instance.Instances.start, instance.Instances.end);
+            instance.duration = instance.Instances.end - instance.Instances.start;
 
             instance.start_date_str = instance.Instances.start.toString();
             instance.end_date_str = instance.Instances.end.toString();

Modified: incubator/ambari/trunk/ambari-web/app/messages.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/messages.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/messages.js (original)
+++ incubator/ambari/trunk/ambari-web/app/messages.js Sat Mar 23 04:38:02 2013
@@ -950,11 +950,19 @@ Em.I18n.translations = {
   'apps.isRunning.popup.title':'Is running',
   'apps.isRunning.popup.content':'Job is running now',
 
+  'mirroring.table.noDatasets':'No datasets to display',
+  'mirroring.table.datasetSource':'Dataset Source',
+  'mirroring.table.lastSuccess':'Last Success',
+  'mirroring.table.lastFail':'Last Fail',
+  'mirroring.table.lastDuration':'Last Duration',
+  'mirroring.table.avgData':'Avg Data',
+
   'menu.item.dashboard':'Dashboard',
   'menu.item.heatmaps':'Heatmaps',
   'menu.item.services':'Services',
   'menu.item.hosts':'Hosts',
   'menu.item.jobs':'Jobs',
+  'menu.item.mirroring':'Mirroring',
   'menu.item.admin':'Admin',
 
   'common.combobox.placeholder': 'Filter...',

Modified: incubator/ambari/trunk/ambari-web/app/models/dataset.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/models/dataset.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/models/dataset.js (original)
+++ incubator/ambari/trunk/ambari-web/app/models/dataset.js Sat Mar 23 04:38:02 2013
@@ -27,9 +27,9 @@ App.DataSet = DS.Model.extend({
   sourceDir: DS.attr('string'),
   targetDir: DS.attr('string'),
   schedule: DS.attr('string'),
-  lastSucceededDate: DS.attr('string'),
-  lastFailedDate: DS.attr('date'),
-  lastDuration: DS.attr('string'),
+  lastSucceededDate: DS.attr('number'),
+  lastFailedDate: DS.attr('number'),
+  lastDuration: DS.attr('number'),
   avgData: DS.attr('string'),
   createdDate: DS.attr('string'),
   datasetJobs: DS.hasMany('App.DataSetJob')

Modified: incubator/ambari/trunk/ambari-web/app/models/host.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/models/host.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/models/host.js (original)
+++ incubator/ambari/trunk/ambari-web/app/models/host.js Sat Mar 23 04:38:02 2013
@@ -107,7 +107,7 @@ App.Host = DS.Model.extend({
    * formatted bytes to appropriate value
    */
   memoryFormatted: function () {
-    return misc.formatBandwidth(this.get('memory') * 1000);
+    return misc.formatBandwidth(this.get('memory') * 1024);
   }.property('memory'),
   /**
    * Return true if host not heartbeating last 180 seconds

Modified: incubator/ambari/trunk/ambari-web/app/routes/main.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/routes/main.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/routes/main.js (original)
+++ incubator/ambari/trunk/ambari-web/app/routes/main.js Sat Mar 23 04:38:02 2013
@@ -100,6 +100,13 @@ module.exports = Em.Route.extend({
     }
   }),
 
+  mirroring: Em.Route.extend({
+    route: '/mirroring',
+    connectOutlets: function (router) {
+      router.get('mainController').connectOutlet('mainMirroring');
+    }
+  }),
+
   hosts: Em.Route.extend({
     route: '/hosts',
     index: Ember.Route.extend({

Modified: incubator/ambari/trunk/ambari-web/app/styles/application.less
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/styles/application.less?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/styles/application.less (original)
+++ incubator/ambari/trunk/ambari-web/app/styles/application.less Sat Mar 23 04:38:02 2013
@@ -2279,6 +2279,178 @@ ul.filter {
 }
 
 /*End Heatmap*/
+
+/*Start Mirroring*/
+
+#mirroring {
+
+  .page-bar {
+    border: 1px solid silver;
+    text-align: right;
+    div {
+      display: inline-block;
+      margin: 0 10px;
+    }
+    .items-on-page {
+      label {
+        display: inline;
+      }
+      select {
+        margin-bottom: 4px;
+        margin-top: 4px;
+        width: 70px;
+      }
+    }
+    .paging_two_button {
+      a.paginate_disabled_next, a.paginate_disabled_previous {
+        color: gray;
+        &:hover {
+          color: gray;
+          text-decoration: none;
+          cursor: default;
+        }
+      }
+
+      a.paginate_next, a.paginate_previous {
+        &:hover {
+          text-decoration: none;
+          cursor: pointer;
+        }
+      }
+      a {
+        padding:0 5px;
+      }
+    }
+  }
+
+  .table { //margin-bottom: 0;
+    thead { //background: #EDF5FC;
+    }
+    th {
+      border-top: none;
+    }
+    th, td {
+      width: 82px;
+      border-left-width: 0;
+    }
+    th.first, td.first {
+      width: 10px !important;
+      border-left-width: 1px;
+    }
+    td.first label {
+      padding-top: 3px;
+    }
+    td.first span {
+      display: block;
+      float: right;
+      height: 13px;
+      margin: 5px 0 0 0;
+      width: 13px;
+    }
+    ul.filter-components {
+      padding: 5px 0;
+      li {
+        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 {
+          height: 250px;
+          margin-left: 0;
+          overflow-y: scroll;
+        }
+      }
+    }
+    .sorting_asc {
+      background: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAZAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAgICAgICAgICAwMDAwMDAwMDAwEBAQEBAQECAQECAgIBAgIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD/8AAEQgAEwATAwERAAIRAQMRAf/EAHgAAAMBAQAAAAAAAAAAAAAAAAAFCAYKAQACAQUAAAAAAAAAAAAAAAAABQMCBAYHCBAAAQUAAQMEAwAAAAAAAAAAAwECBAUGABESByExIghBMxQRAAIBAwMDAwUAAAAAAAAAAAECAwAEBRESBiExUUHhB2GBIhMU/9oADAMBAAIRAxEAPwDvA8k+Qc54sxGj32qlNi0ucrjTj/JqGlmROyJXQ2u/bOsZTmBExPd70/HXmQcW41lOX5+145h0L391KEHhR3Z28Ii6sx9AKgubiO1gaeU6Io19h9TUg/S/7eP+wia3NbBIFbuqiyn3VTCjIMArHHTJarEDGGiNU8vOKVsc7/VxBuGR3yV683X86/Cq/GpssrhP2S8emiSKRm1JS5VfyLH0WfQug7KwZR0CilWHy39++ObQTgkgeV9ux+xq9uc6U8pLfZzP6mClZpKWrvq1DilJAt4Mewh/0hRyBOsaUMoVKLvXtVU6t6+nL/HZTJYi4/rxU81tdbSu+N2Rtp7jcpB0OnUa9aoeOOVdsgDL4I1pFS+NPHmcsQ2+fw+UpLWOwwwWNVQ1kCaIcgaiONkmLGEZrDDXtcnXo5PfjC+5VybKWrWWSyF5cWbEEpJNI6kqdQSrMRqD1B9KjS2t423xoqt5AArb8QVPRwoo4UUc
 KK//2Q==) no-repeat right 50%;
+    }
+    .sorting_desc {
+      background: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAZAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAgICAgICAgICAwMDAwMDAwMDAwEBAQEBAQECAQECAgIBAgIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD/8AAEQgAEwATAwERAAIRAQMRAf/EAIEAAAIDAQAAAAAAAAAAAAAAAAAGBwgJCgEBAAIDAQAAAAAAAAAAAAAAAAMFBAYHCBAAAAUDAwMFAAAAAAAAAAAAAQIDBAUABgcSNTYRFQgTZFUWZhEAAAQEAggGAwAAAAAAAAAAAAECAxEhBAYSMjFBYRMzFDQFUZFSYmMHJFRk/9oADAMBAAIRAxEAPwDv4oAKACgCKc1tMmusb3Eph6cSgsgx7fucEZxGRks2llGIGVWgVm8q1dt0+6ogKaapSgdNbQPXTqAdwsN602bopk3vTnUW24rduwccbU2S5E8Sm1JM92czSZwNOKUYDFrCqTp1corDUFMpEcYap+Ipb4P5O8n81y9xXXlG50yY+thR3AEivqFvRDmduvSUrhuLtrFNXqCFvJm1LAQ5RMuchB6gBy13f7+tP6lsOipuz2jSGdy1ZJeNzmXnEtU+pWFTikmbxyTEjgglKKZpMU3ZanudYtTtSr8dMoYSKKvKMte0aUV5YGxgoASbD2iQ4Tyi6uB7Rvz/AHD9R8r7/wBWr64uta6/pKfq+JwUZP5/1/hwCFjIeTMrLo0np93q2xDtVCJh/9k=) no-repeat right 50%;
+    }
+    .sorting {
+      background: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAZAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAgICAgICAgICAwMDAwMDAwMDAwEBAQEBAQECAQECAgIBAgIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD/8AAEQgAEwATAwERAAIRAQMRAf/EAGgAAAIDAQAAAAAAAAAAAAAAAAUHAAYICgEBAQAAAAAAAAAAAAAAAAAAAAEQAAEEAQIFAgcAAAAAAAAAAAECAwQFABEGIRI0NQcTFDFBMmNUZRYRAQEBAQAAAAAAAAAAAAAAAAABEUH/2gAMAwEAAhEDEQA/AO93cd/XbXpLC9tHQ1Dr46nljUBby/gzGZB+p+Q6QhA+ZOApfDnllW/ha1tv6Ee7iyH5kRlvlbTIqHndWkNJ0HO7XFQbWeJUkpUeOpySrZh65UUnyFUW1ztaexRmIbaPyzoLE6vg2UWW9GC1e0XHnsSGEqfQohCwApK9OIGuAjfBP9VuG0m39vGqINVUe4r2xF21TVsuXZOI9N9lMmLBYkttQ21auBKhqtSUngCMkW5xqjKiYASh6SR2Tulr2HpOvf6j9p+V9/mwDeB//9k=) no-repeat right 50%;
+    }
+
+    div.view-wrapper {
+      .btn-group {
+        margin-bottom: 9px;
+      }
+    }
+
+    a.ui-icon-circle-close {
+      float: right;
+      opacity: 0.2;
+      padding: 1px;
+      position: relative;
+      right: -8px;
+      margin-top: 6px;
+      z-index: 10;
+      &:hover {
+        opacity: 0.7;
+      }
+    }
+    .notActive {
+      a.ui-icon-circle-close {
+        visibility: hidden;
+      }
+    }
+  }
+
+  .box-footer .footer-pagination {
+    float: right;
+    .nav {
+      margin-bottom: 0;
+    }
+    .dropdown {
+      margin-top: 3px;
+    }
+    .dropdown {
+      margin-top: 3px;
+    }
+    .dropdown select {
+      width: 60px;
+    }
+    .page-listing a {
+      line-height: 0;
+      border: none;
+      margin: 0;
+      margin-right: 10px;
+      cursor: pointer;
+      color: #0088CC;
+      padding: 8px 0;
+      float: left;
+      text-decoration: underline;
+    }
+    .page-listing a:hover {
+      text-decoration: none;
+    }
+    .page-listing {
+      width: 100px;
+      .table {
+        th.name {
+          width: 300px;
+          a.filter-label {
+            width: 57px;
+            display: block;
+            float: left;
+          }
+        }
+      }
+    }
+  }
+}
+
+/*End Mirroring*/
+
 .noDisplay {
   display: none !important;
 }

Added: incubator/ambari/trunk/ambari-web/app/templates/main/mirroring.hbs
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/templates/main/mirroring.hbs?rev=1460092&view=auto
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/templates/main/mirroring.hbs (added)
+++ incubator/ambari/trunk/ambari-web/app/templates/main/mirroring.hbs Sat Mar 23 04:38:02 2013
@@ -0,0 +1,88 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div id="mirroring">
+
+  <table class="datatable table table-bordered table-striped">
+    <thead>
+    <tr>
+        {{#view view.sortView contentBinding="view.filteredContent"}}
+          <th class="first"> </th>
+          {{view view.parentView.nameSort}}
+          {{view view.parentView.dataSetSourceSort}}
+          {{view view.parentView.lastSuccessSort}}
+          {{view view.parentView.lastFailSort}}
+          {{view view.parentView.lastDurationSort}}
+          {{view view.parentView.avgDataSort}}
+        {{/view}}
+    </tr>
+    <tr>
+      <th class="first"> </th>
+      <th>{{view view.nameFilterView}}</th>
+      <th>{{view view.datasetSourceFilterView}}</th>
+      <th>{{view view.lastSuccessFilterView}}</th>
+      <th>{{view view.lastFailFilterView}}</th>
+      <th>{{view view.lastDurationFilterView}}</th>
+      <th>{{view view.avgDataFilterView}}</th>
+    </tr>
+    </thead>
+    <tbody>
+    {{#if view.pageContent}}
+      {{#each dataset in view.pageContent}}
+        {{#view view.DatasetView contentBinding="dataset"}}
+
+          <td class="first">
+          </td>
+
+          <td class="name">
+            <a title="{{unbound dataset.name}}" href="#">{{unbound dataset.name}}</a>
+          </td>
+          <td>{{dataset.sourceDir}}</td>
+          <td>{{view.lastSucceededDateFormatted}}</td>
+          <td>{{view.lastFailedDateFormatted}}</td>
+
+          <td>
+            {{view.lastDurationFormatted}}
+          </td>
+
+          <td>{{dataset.avgData}}</td>
+        {{/view}}
+      {{/each}}
+    {{else}}
+      <tr>
+          <td class="first"></td>
+          <td colspan="6">
+              {{t mirroring.table.noDatasets}}
+          </td>
+      </tr>
+    {{/if}}
+    </tbody>
+  </table>
+
+  <div class="page-bar">
+    <div class="items-on-page">
+      <label>{{t common.show}}: {{view view.datasetsPerPageSelectView selectionBinding="view.displayLength"}}</label>
+    </div>
+    <div class="info">{{view.paginationInfo}}</div>
+    <div class="paging_two_button">
+      {{view view.paginationLeft}}
+      {{view view.paginationRight}}
+    </div>
+  </div>
+</div>
+

Modified: incubator/ambari/trunk/ambari-web/app/views.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views.js Sat Mar 23 04:38:02 2013
@@ -119,6 +119,7 @@ require('views/main/apps_view');
 require('views/main/apps/item_view');
 require('views/main/apps/item/bar_view');
 require('views/main/apps/item/dag_view');
+require('views/main/mirroring_view');
 require('views/installer');
 require('views/wizard/controls_view');
 require('views/wizard/step1_view');

Modified: incubator/ambari/trunk/ambari-web/app/views/common/filter_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/common/filter_view.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/common/filter_view.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views/common/filter_view.js Sat Mar 23 04:38:02 2013
@@ -281,23 +281,7 @@ module.exports = {
           }
           rowValue = (jQuery(rowValue).text()) ? jQuery(rowValue).text() : rowValue;
 
-          var convertedRowValue;
-          if (rowValue === '<1KB') {
-            convertedRowValue = 1;
-          } else {
-            var rowValueScale = rowValue.substr(rowValue.length - 2, 2);
-            switch (rowValueScale) {
-              case 'KB':
-                convertedRowValue = parseFloat(rowValue)*1024;
-                break;
-              case 'MB':
-                convertedRowValue = parseFloat(rowValue)*1048576;
-                break;
-              case 'GB':
-                convertedRowValue = parseFloat(rowValue)*1073741824;
-                break;
-            }
-          }
+          var convertedRowValue = rowValue*1024;
 
           switch (compareChar) {
             case '<':
@@ -314,6 +298,74 @@ module.exports = {
           return match;
         }
         break;
+      case 'duration':
+        return function (rowValue, rangeExp) {
+          var compareChar = isNaN(rangeExp.charAt(0)) ? rangeExp.charAt(0) : false;
+          var compareScale = rangeExp.charAt(rangeExp.length - 1);
+          var compareValue = compareChar ? parseFloat(rangeExp.substr(1, rangeExp.length)) : parseFloat(rangeExp.substr(0, rangeExp.length));
+          var match = false;
+          if (rangeExp.length == 1 && compareChar !== false) {
+            // User types only '=' or '>' or '<', so don't filter column values
+            match = true;
+            return match;
+          }
+          switch (compareScale) {
+            case 's':
+              compareValue *= 1000;
+              break;
+            case 'm':
+              compareValue *= 60000;
+              break;
+            case 'h':
+              compareValue *= 3600000;
+              break;
+            default:
+              compareValue *= 1000;
+          }
+          rowValue = (jQuery(rowValue).text()) ? jQuery(rowValue).text() : rowValue;
+
+          switch (compareChar) {
+            case '<':
+              if (compareValue > rowValue) match = true;
+              break;
+            case '>':
+              if (compareValue < rowValue) match = true;
+              break;
+            case false:
+            case '=':
+              if (compareValue == rowValue) match = true;
+              break;
+          }
+          return match;
+        }
+        break;
+      case 'date':
+        return function (rowValue, rangeExp) {
+          var match = false;
+          var timePassed = new Date().getTime() - rowValue;
+          switch (rangeExp) {
+            case 'Past 1 Day':
+              match = timePassed <= 86400000;
+              break;
+            case 'Past 2 Days':
+              match = timePassed <= 172800000;
+              break;
+            case 'Past 7 Days':
+              match = timePassed <= 604800000;
+              break;
+            case 'Past 14 Days':
+              match = timePassed <= 1209600000;
+              break;
+            case 'Past 30 Days':
+              match = timePassed <= 2592000000;
+              break;
+            case 'Any':
+              match = true;
+              break;
+          }
+          return match;
+        }
+        break;
       case 'number':
         return function(rowValue, rangeExp){
           var compareChar = rangeExp.charAt(0);

Modified: incubator/ambari/trunk/ambari-web/app/views/common/sort_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/common/sort_view.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/common/sort_view.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views/common/sort_view.js Sat Mar 23 04:38:02 2013
@@ -65,6 +65,17 @@ var wrapperView = Em.View.extend({
           }
         };
         break;
+      case 'number':
+        func = function (a, b) {
+          var a = parseFloat(a.get(property.get('name')));
+          var b = parseFloat(b.get(property.get('name')));
+          if(order){
+            return b - a;
+          } else {
+            return a - b;
+          }
+        }
+        break;
       default:
         func = function(a,b){
           if(order){

Modified: incubator/ambari/trunk/ambari-web/app/views/main/host.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/main/host.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/main/host.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views/main/host.js Sat Mar 23 04:38:02 2013
@@ -130,11 +130,13 @@ App.MainHostView = Em.View.extend({
   }),
   cpuSort: sort.fieldView.extend({
     name:'cpu',
-    displayName: Em.I18n.t('common.cpu')
+    displayName: Em.I18n.t('common.cpu'),
+    type: 'number'
   }),
   memorySort: sort.fieldView.extend({
     name:'memory',
-    displayName: Em.I18n.t('common.ram')
+    displayName: Em.I18n.t('common.ram'),
+    type: 'number'
   }),
   diskUsageSort: sort.fieldView.extend({
     name:'diskUsage',
@@ -142,7 +144,8 @@ App.MainHostView = Em.View.extend({
   }),
   loadAvgSort: sort.fieldView.extend({
     name:'loadAvg',
-    displayName: Em.I18n.t('common.loadAvg')
+    displayName: Em.I18n.t('common.loadAvg'),
+    type: 'number'
   }),
   HostView:Em.View.extend({
     content:null,
@@ -426,7 +429,7 @@ App.MainHostView = Em.View.extend({
     associations[1] = 'publicHostName';
     associations[2] = 'ip';
     associations[3] = 'cpu';
-    associations[4] = 'memoryFormatted';
+    associations[4] = 'memory';
     associations[5] = 'loadAvg';
     associations[6] = 'hostComponents';
     associations[7] = 'criticalAlertsCount';

Modified: incubator/ambari/trunk/ambari-web/app/views/main/menu.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/main/menu.js?rev=1460092&r1=1460091&r2=1460092&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/main/menu.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views/main/menu.js Sat Mar 23 04:38:02 2013
@@ -31,7 +31,8 @@ App.MainMenuView = Em.CollectionView.ext
       { label:Em.I18n.t('menu.item.heatmaps'), routing:'charts'},
       { label:Em.I18n.t('menu.item.services'), routing:'services'},
       { label:Em.I18n.t('menu.item.hosts'), routing:'hosts'},
-      { label:Em.I18n.t('menu.item.jobs'), routing:'apps'}
+      { label:Em.I18n.t('menu.item.jobs'), routing:'apps'},
+      { label:Em.I18n.t('menu.item.mirroring'), routing:'mirroring'}
 
     ];
       if(App.db.getUser().admin) result.push({ label:Em.I18n.t('menu.item.admin'), routing:'admin'});

Added: incubator/ambari/trunk/ambari-web/app/views/main/mirroring_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/main/mirroring_view.js?rev=1460092&view=auto
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/main/mirroring_view.js (added)
+++ incubator/ambari/trunk/ambari-web/app/views/main/mirroring_view.js Sat Mar 23 04:38:02 2013
@@ -0,0 +1,288 @@
+/**
+ * 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 filters = require('views/common/filter_view');
+var sort = require('views/common/sort_view');
+
+App.MainMirroringView = Em.View.extend({
+  templateName: require('templates/main/mirroring'),
+  content: function () {
+    return this.get('controller.content');
+  }.property('controller.content'),
+
+  didInsertElement: function () {
+    this.filter();
+  },
+
+  /**
+   * return pagination information displayed on the mirroring page
+   */
+  paginationInfo: function () {
+    return this.t('apps.filters.paginationInfo').format(this.get('startIndex'), this.get('endIndex'), this.get('filteredContent.length'));
+  }.property('displayLength', 'filteredContent.length', 'startIndex', 'endIndex'),
+
+  paginationLeft: Ember.View.extend({
+    tagName: 'a',
+    template: Ember.Handlebars.compile('<i class="icon-arrow-left"></i>'),
+    classNameBindings: ['class'],
+    class: function () {
+      if (this.get("parentView.startIndex") > 1) {
+        return "paginate_previous";
+      }
+      return "paginate_disabled_previous";
+    }.property("parentView.startIndex", 'filteredContent.length'),
+
+    click: function () {
+      this.get('parentView').previousPage();
+    }
+  }),
+
+  paginationRight: Ember.View.extend({
+    tagName: 'a',
+    template: Ember.Handlebars.compile('<i class="icon-arrow-right"></i>'),
+    classNameBindings: ['class'],
+    class: function () {
+      if ((this.get("parentView.endIndex")) < this.get("parentView.filteredContent.length")) {
+        return "paginate_next";
+      }
+      return "paginate_disabled_next";
+    }.property("parentView.endIndex", 'filteredContent.length'),
+
+    click: function () {
+      this.get('parentView').nextPage();
+    }
+  }),
+
+  datasetsPerPageSelectView: Em.Select.extend({
+    content: ['10', '25', '50']
+  }),
+
+  // start index for displayed content on the mirroring page
+  startIndex: 1,
+
+  // calculate end index for displayed content on the mirroring page
+  endIndex: function () {
+    return Math.min(this.get('filteredContent.length'), this.get('startIndex') + parseInt(this.get('displayLength')) - 1);
+  }.property('startIndex', 'displayLength', 'filteredContent.length'),
+
+  /**
+   * onclick handler for previous page button on the mirroring page
+   */
+  previousPage: function () {
+    var result = this.get('startIndex') - parseInt(this.get('displayLength'));
+    if (result < 2) {
+      result = 1;
+    }
+    this.set('startIndex', result);
+  },
+
+  /**
+   * onclick handler for next page button on the mirroring page
+   */
+  nextPage: function () {
+    var result = this.get('startIndex') + parseInt(this.get('displayLength'));
+    if (result - 1 < this.get('filteredContent.length')) {
+      this.set('startIndex', result);
+    }
+  },
+
+  // the number of mirroring to show on every page of the mirroring page view
+  displayLength: null,
+
+  // calculates default value for startIndex property after applying filter or changing displayLength
+  updatePaging: function () {
+    this.set('startIndex', Math.min(1, this.get('filteredContent.length')));
+  }.observes('displayLength', 'filteredContent.length'),
+
+  sortView: sort.wrapperView,
+  nameSort: sort.fieldView.extend({
+    name: 'name',
+    displayName: Em.I18n.t('common.name')
+  }),
+  dataSetSourceSort: sort.fieldView.extend({
+    name: 'sourceDir',
+    displayName: Em.I18n.t('mirroring.table.datasetSource')
+  }),
+  lastSuccessSort: sort.fieldView.extend({
+    name: 'lastSucceededDate',
+    displayName: Em.I18n.t('mirroring.table.lastSuccess'),
+    type: 'number'
+  }),
+  lastFailSort: sort.fieldView.extend({
+    name: 'lastFailedDate',
+    displayName: Em.I18n.t('mirroring.table.lastFail'),
+    type: 'number'
+  }),
+  lastDurationSort: sort.fieldView.extend({
+    name: 'lastDuration',
+    displayName: Em.I18n.t('mirroring.table.lastDuration'),
+    type: 'number'
+  }),
+  avgDataSort: sort.fieldView.extend({
+    name: 'avgData',
+    displayName: Em.I18n.t('mirroring.table.avgData'),
+    type: 'number'
+  }),
+
+  /**
+   * Filter view for name column
+   * Based on <code>filters</code> library
+   */
+  nameFilterView: filters.createTextView({
+    fieldType: 'input-small',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(1, this.get('value'), 'string');
+    }
+  }),
+
+  datasetSourceFilterView: filters.createTextView({
+    fieldType: 'input-small',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(2, this.get('value'), 'string');
+    }
+  }),
+
+  lastSuccessFilterView: filters.createSelectView({
+    fieldType: 'input-medium',
+    content: ['Any', 'Past 1 Day', 'Past 2 Days', 'Past 7 Days', 'Past 14 Days', 'Past 30 Days'],
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(3, this.get('value'), 'date');
+    }
+  }),
+
+  lastFailFilterView: filters.createSelectView({
+    fieldType: 'input-medium',
+    content: ['Any', 'Past 1 Day', 'Past 2 Days', 'Past 7 Days', 'Past 14 Days', 'Past 30 Days'],
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(4, this.get('value'), 'date');
+    }
+  }),
+
+  lastDurationFilterView: filters.createTextView({
+    fieldType: 'input-small',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(5, this.get('value'), 'duration');
+    }
+  }),
+
+  avgDataFilterView: filters.createTextView({
+    fieldType: 'input-small',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(6, this.get('value'), 'ambari-bandwidth');
+    }
+  }),
+
+  DatasetView: Em.View.extend({
+    content: null,
+    tagName: 'tr',
+
+    lastDurationFormatted: function () {
+      var milliseconds = this.get('content.lastDuration');
+      var h = Math.floor(milliseconds / 3600000);
+      var m = Math.floor((milliseconds % 3600000) / 60000);
+      var s = Math.floor(((milliseconds % 360000) % 60000) / 1000);
+      return (h == 0 ? '' : h + 'hr ') + (m == 0 ? '' : m + 'mins ') + (s == 0 ? '' : s + 'secs ');
+    }.property('content.lastDuration'),
+
+    lastSucceededDateFormatted: function () {
+      if (this.get('content.lastSucceededDate')) {
+        return $.timeago(this.get('content.lastSucceededDate'));
+      }
+    }.property('content.lastSucceededDate'),
+
+    lastFailedDateFormatted: function () {
+      if (this.get('content.lastFailedDate')) {
+        return $.timeago(this.get('content.lastFailedDate'));
+      }
+    }.property('content.lastFailedDate')
+  }),
+
+  /**
+   * Apply each filter to dataset
+   *
+   * @param iColumn number of column by which filter
+   * @param value
+   */
+  updateFilter: function (iColumn, value, type) {
+    var filterCondition = this.get('filterConditions').findProperty('iColumn', iColumn);
+    if (filterCondition) {
+      filterCondition.value = value;
+    } else {
+      filterCondition = {
+        iColumn: iColumn,
+        value: value,
+        type: type
+      }
+      this.get('filterConditions').push(filterCondition);
+    }
+    this.filter();
+  },
+
+  /**
+   * associations between dataset property and column index
+   */
+  colPropAssoc: function () {
+    var associations = [];
+    associations[1] = 'name';
+    associations[2] = 'sourceDir';
+    associations[3] = 'lastSucceededDate';
+    associations[4] = 'lastFailedDate';
+    associations[5] = 'lastDuration';
+    associations[6] = 'avgData';
+    return associations;
+  }.property(),
+
+  /**
+   * contain filter conditions for each column
+   */
+  filterConditions: [],
+
+  filteredContent: [],
+
+  // contain content to show on the current page of mirroring page view
+  pageContent: function () {
+    return this.get('filteredContent').slice(this.get('startIndex') - 1, this.get('endIndex'));
+  }.property('filteredContent.length', 'startIndex', 'endIndex'),
+
+  /**
+   * filter table by filterConditions
+   */
+  filter: function () {
+    var content = this.get('content');
+    var filterConditions = this.get('filterConditions').filterProperty('value');
+    var result;
+    var assoc = this.get('colPropAssoc');
+    if (filterConditions.length) {
+      result = content.filter(function (dataset) {
+        var match = true;
+        filterConditions.forEach(function (condition) {
+          var filterFunc = filters.getFilterByType(condition.type, false);
+          if (match) {
+            match = filterFunc(dataset.get(assoc[condition.iColumn]), condition.value);
+          }
+        });
+        return match;
+      });
+      this.set('filteredContent', result);
+    } else {
+      this.set('filteredContent', content.toArray());
+    }
+  }.observes('content')
+
+});