You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by on...@apache.org on 2014/07/18 16:21:30 UTC

[1/3] AMBARI-6539. Create main page with table of jobs. (onechiporenko)

Repository: ambari
Updated Branches:
  refs/heads/trunk 39a92eb49 -> 366bcbb69


http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/views/table_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/table_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/table_view.js
new file mode 100644
index 0000000..a404a49
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/table_view.js
@@ -0,0 +1,362 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.TableView = Em.View.extend({
+
+  /**
+   * Defines to show pagination or show all records
+   * @type {Boolean}
+   */
+  pagination: true,
+
+  /**
+   * Shows if all data is loaded and filtered
+   * @type {Boolean}
+   */
+  filteringComplete: false,
+
+  /**
+   * intermediary for filteringComplete
+   * @type {Boolean}
+   */
+  tableFilteringComplete: false,
+
+  /**
+   * The number of rows to show on every page
+   * The value should be a number converted into string type in order to support select element API
+   * Example: "10", "25"
+   * @type {String}
+   */
+  displayLength: '10',
+
+  /**
+   * default value of display length
+   * The value should be a number converted into string type in order to support select element API
+   * Example: "10", "25"
+   */
+  defaultDisplayLength: "10",
+
+  /**
+   * number of hosts in table after applying filters
+   */
+  filteredCount: function () {
+    return this.get('filteredContent.length');
+  }.property('filteredContent.length'),
+
+  /**
+   * Do filtering, using saved in the local storage filter conditions
+   */
+  willInsertElement:function () {
+    this.initFilters();
+  },
+
+  /**
+   * initialize filters
+   * restore values from local DB
+   * or clear filters in case there is no filters to restore
+   */
+  initFilters: function () {
+    this.clearFilters();
+    this.set('tableFilteringComplete', true);
+  },
+
+  /**
+   * Return pagination information displayed on the page
+   * @type {String}
+   */
+  paginationInfo: function () {
+    return this.t('tableView.filters.paginationInfo').format(this.get('startIndex'), this.get('endIndex'), this.get('filteredCount'));
+  }.property('filteredCount', 'endIndex'),
+
+  paginationLeft: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_left',
+    classNameBindings: ['class'],
+    class: function () {
+      if (this.get("parentView.startIndex") > 1) {
+        return "paginate_previous";
+      }
+      return "paginate_disabled_previous";
+    }.property("parentView.startIndex", 'parentView.filteredCount'),
+
+    click: function () {
+      if (this.get('class') === "paginate_previous") {
+        this.get('parentView').previousPage();
+      }
+    }
+  }),
+
+  paginationRight: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_right',
+    classNameBindings: ['class'],
+    class: function () {
+      if ((this.get("parentView.endIndex")) < this.get("parentView.filteredCount")) {
+        return "paginate_next";
+      }
+      return "paginate_disabled_next";
+    }.property("parentView.endIndex", 'parentView.filteredCount'),
+
+    click: function () {
+      if (this.get('class') === "paginate_next") {
+        this.get('parentView').nextPage();
+      }
+    }
+  }),
+
+  paginationFirst: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_first',
+    classNameBindings: ['class'],
+    class: function () {
+      if ((this.get("parentView.endIndex")) > parseInt(this.get("parentView.displayLength"))) {
+        return "paginate_previous";
+      }
+      return "paginate_disabled_previous";
+    }.property("parentView.endIndex", 'parentView.filteredCount'),
+
+    click: function () {
+      if (this.get('class') === "paginate_previous") {
+        this.get('parentView').firstPage();
+      }
+    }
+  }),
+
+  paginationLast: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_last',
+    classNameBindings: ['class'],
+    class: function () {
+      if (this.get("parentView.endIndex") !== this.get("parentView.filteredCount")) {
+        return "paginate_next";
+      }
+      return "paginate_disabled_next";
+    }.property("parentView.endIndex", 'parentView.filteredCount'),
+
+    click: function () {
+      if (this.get('class') === "paginate_next") {
+        this.get('parentView').lastPage();
+      }
+    }
+  }),
+
+  /**
+   * Select View with list of "rows-per-page" options
+   * @type {Ember.View}
+   */
+  rowsPerPageSelectView: Em.Select.extend({
+    content: ['10', '25', '50', '100'],
+    change: function () {
+      this.get('parentView').saveDisplayLength();
+    }
+  }),
+
+  /**
+   * Start index for displayed content on the page
+   */
+  startIndex: 1,
+
+  /**
+   * Calculate end index for displayed content on the page
+   */
+  endIndex: function () {
+    if (this.get('pagination') && this.get('displayLength')) {
+      return Math.min(this.get('filteredCount'), this.get('startIndex') + parseInt(this.get('displayLength')) - 1);
+    } else {
+      return this.get('filteredCount') || 0;
+    }
+  }.property('startIndex', 'displayLength', 'filteredCount'),
+
+  /**
+   * Onclick handler for previous page button on the page
+   */
+  previousPage: function () {
+    var result = this.get('startIndex') - parseInt(this.get('displayLength'));
+    this.set('startIndex', (result < 2) ? 1 : result);
+  },
+
+  /**
+   * Onclick handler for next page button on the page
+   */
+  nextPage: function () {
+    var result = this.get('startIndex') + parseInt(this.get('displayLength'));
+    if (result - 1 < this.get('filteredCount')) {
+      this.set('startIndex', result);
+    }
+  },
+  /**
+   * Onclick handler for first page button on the page
+   */
+  firstPage: function () {
+    this.set('startIndex', 1);
+  },
+  /**
+   * Onclick handler for last page button on the page
+   */
+  lastPage: function () {
+    var pagesCount = this.get('filteredCount') / parseInt(this.get('displayLength'));
+    var startIndex = (this.get('filteredCount') % parseInt(this.get('displayLength')) === 0) ?
+      (pagesCount - 1) * parseInt(this.get('displayLength')) :
+      Math.floor(pagesCount) * parseInt(this.get('displayLength'));
+    this.set('startIndex', ++startIndex);
+  },
+
+  /**
+   * Calculates default value for startIndex property after applying filter or changing displayLength
+   */
+  updatePaging: function (controller, property) {
+    var displayLength = this.get('displayLength');
+    var filteredContentLength = this.get('filteredCount');
+    if (property == 'displayLength') {
+      this.set('startIndex', Math.min(1, filteredContentLength));
+    }
+    else
+      if (!filteredContentLength) {
+        this.set('startIndex', 0);
+      }
+      else
+        if (this.get('startIndex') > filteredContentLength) {
+          this.set('startIndex', Math.floor((filteredContentLength - 1) / displayLength) * displayLength + 1);
+        }
+        else
+          if (!this.get('startIndex')) {
+            this.set('startIndex', 1);
+          }
+  }.observes('displayLength', 'filteredCount'),
+
+  /**
+   * Apply each filter to each row
+   *
+   * @param {Number} iColumn number of column by which filter
+   * @param {Object} value
+   * @param {String} type
+   */
+  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.filtersUsedCalc();
+    this.filter();
+  },
+
+  /**
+   * Contain filter conditions for each column
+   * @type {Array}
+   */
+  filterConditions: [],
+
+  /**
+   * Contains content after implementing filters
+   * @type {Array}
+   */
+  filteredContent: [],
+
+  /**
+   * Determine if <code>filteredContent</code> is empty or not
+   * @type {Boolean}
+   */
+  hasFilteredItems: function() {
+    return !!this.get('filteredCount');
+  }.property('filteredCount'),
+
+  /**
+   * Contains content to show on the current page of data page view
+   * @type {Array}
+   */
+  pageContent: function () {
+    return this.get('filteredContent').slice(this.get('startIndex') - 1, this.get('endIndex'));
+  }.property('filteredCount', '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 (item) {
+        var match = true;
+        filterConditions.forEach(function (condition) {
+          var filterFunc = App.Filters.getFilterByType(condition.type, false);
+          if (match) {
+            match = filterFunc(item.get(assoc[condition.iColumn]), condition.value);
+          }
+        });
+        return match;
+      });
+      this.set('filteredContent', result);
+    } else {
+      this.set('filteredContent', content.toArray());
+    }
+  }.observes('content.length'),
+
+  /**
+   * Does any filter is used on the page
+   * @type {Boolean}
+   */
+  filtersUsed: false,
+
+  /**
+   * Determine if some filters are used on the page
+   * Set <code>filtersUsed</code> value
+   */
+  filtersUsedCalc: function() {
+    var filterConditions = this.get('filterConditions');
+    if (!filterConditions.length) {
+      this.set('filtersUsed', false);
+      return;
+    }
+    var filtersUsed = false;
+    filterConditions.forEach(function(filterCondition) {
+      if (filterCondition.value.toString() !== '') {
+        filtersUsed = true;
+      }
+    });
+    this.set('filtersUsed', filtersUsed);
+  },
+
+  /**
+   * Run <code>clearFilter</code> in the each child filterView
+   */
+  clearFilters: function() {
+    this.set('filterConditions', []);
+    this.get('_childViews').forEach(function(childView) {
+      if (childView['clearFilter']) {
+        childView.clearFilter();
+      }
+    });
+  },
+
+  actions: {
+    actionClearFilters: function() {
+      this.clearFilters();
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/styles/main.less b/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
index c1997c2..8cd95dc 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
+++ b/contrib/views/jobs/src/main/resources/ui/app/styles/main.less
@@ -16,4 +16,303 @@
  * limitations under the License.
  */
 
-@import '../../app/bower_components/bootstrap/less/bootstrap';
\ No newline at end of file
+@import '../../app/bower_components/bootstrap/less/bootstrap';
+
+#jobs {
+
+  .jobs-type {
+    float: right;
+    margin-top: -24px;
+  }
+
+  .new-jobs-link {
+    float: left;
+    margin-left: 496px;
+    margin-top: -20px;
+  }
+
+  #filtered-jobs{
+    float: left;
+    margin-top: 8px;
+  }
+
+  .jobs_head{
+    height: 30px;
+  }
+
+  .page-bar {
+    border: 1px solid #E4E4E4;
+    color: #7B7B7B;
+    text-align: right;
+    font-size: 12px;
+    label {
+      font-size: 12px;
+    }
+    div {
+      display: inline-block;
+      margin:0 10px;
+    }
+    .items-on-page {
+      label {
+        display:inline;
+      }
+      select {
+        margin-bottom: 4px;
+        margin-top: 4px;
+        width:70px;
+        font-size: 12px;
+        height: 27px;
+      }
+    }
+
+    .paging_two_button {
+      a {
+        padding:0 5px;
+      }
+      a.paginate_disabled_next, a.paginate_disabled_previous {
+        color: gray;
+        &:hover i{
+          color: gray;
+          text-decoration: none;
+          cursor: default;
+        }
+      }
+
+      a.paginate_next, a.paginate_previous {
+        &:hover {
+          text-decoration: none;
+          cursor: pointer;
+        }
+      }
+    }
+  }
+
+  #jobs-table {
+
+    .is-not-link{
+      cursor: default;
+      color: #000000;
+      text-decoration: none;
+    }
+
+    .apply-btn {
+      font-size: 12px;
+      padding: 0px 8px;
+      margin-left: 6px;
+      margin-top: -8px;
+      line-height: 22px;
+    }
+
+    .input-120{
+      width: 120px;
+    }
+
+    .label-row {
+      font-size: 0.9em;
+      th {
+        padding: 4px 4px 4px 8px;
+      }
+      .active-sort {
+        color: #555555;
+        text-decoration: none;
+        background-color: #e5e5e5;
+        -webkit-box-shadow: inset 0 5px 8px rgba(0, 0, 0, 0.100);
+        -moz-box-shadow: inset 0 5px 8px rgba(0, 0, 0, 0.100);
+        box-shadow: inset 0 5px 8px rgba(0, 0, 0, 0.100);
+      }
+    }
+    thead {
+      background: none repeat scroll 0 0 #F8F8F8;
+    }
+    #filter-row {
+      th {
+        padding: 0px;
+        padding-left: 8px;
+      }
+      .active-filter {
+        color: #555555;
+        text-decoration: none;
+        background-color: #e5e5e5;
+        -webkit-box-shadow: inset 0 -5px 8px rgba(0, 0, 0, 0.05);
+        -moz-box-shadow: inset 0 -5px 8px rgba(0, 0, 0, 0.05);
+        box-shadow: inset 0 -5px 8px rgba(0, 0, 0, 0.05);
+      }
+      input {
+        font-size: 12px;
+        height: 14px;
+      }
+      select {
+        height: 27px;
+        font-size: 12px;
+      }
+      .start-time a.ui-icon-circle-close {
+        margin-top: 7px;
+      }
+      .filter-btn {
+        color: #999999;
+        font-size: 12px;
+        line-height: 14px;
+        padding-left: 6px;
+        text-align: left;
+        width: 100px;
+        .icon-filter {
+          color: #999999;
+        }
+      }
+    }
+    th {
+      border-top: none;
+    }
+    th, td {
+      border-left-width: 0;
+    }
+    .no-data{
+      text-align: center;
+    }
+    a.job-link {
+      width: 100%;
+      overflow: auto;
+      word-wrap: break-word;
+      display: inline-block;
+    }
+    .tooltip-inner {
+      text-align: left;
+      max-width: 400px !important;
+    }
+    td:first-child,
+    th:first-child {
+      border-left-width: 1px;
+      width: 14px;
+    }
+    td:first-child + td,
+    th:first-child + th {
+      width: 36%;
+    }
+    td:first-child + td + td,
+    th:first-child + th + th{
+      width: 20%;
+    }
+    td:first-child + td + td + td,
+    th:first-child + th + th + th,
+    td:first-child + td + td + td + td,
+    th:first-child + th + th + th + th{
+      width: 16%;
+    }
+    td:first-child + td + td + td + td + td,
+    th:first-child + th + th + th + th + th{
+      width: 12%;
+    }
+  }
+  .table {
+    table-layout: fixed;
+    th {
+      border-top: none;
+    }
+    ul.filter-components {
+      padding: 5px 0;
+      background: #777777;
+      color: #ffffff;
+      font-weight: normal;
+      font-size: 12px;
+      label {
+        font-size: 12px;
+      }
+      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#title-bar {
+        text-align: left;
+        border-bottom: 1px solid #e4e4e4;
+        a.close {
+          background: #777777;
+          display: inline;
+          color: #ffffff;
+          padding-left: 35px;
+          padding-right: 12px;
+          text-shadow: 0 1px 0 #ffffff;
+          float: none;
+          font-size: 10px;
+          opacity: 0.6;
+        }
+        a.close:hover {
+          background: #777777;
+          opacity: 1.0;
+        }
+      }
+      li#selector-bar {
+        text-align: left;
+        border-bottom: 1px solid #e4e4e4;
+        font-size: 6px;
+      }
+      li#list-area {
+        font-weight: normal;
+        text-align: left;
+      }
+      li#button-bar {
+        text-align: center;
+        button {
+          font-size: 12px;
+        }
+      }
+      ul {
+        margin-left: 10px;
+      }
+      &>li {
+        &>ul {
+          height: 150px;
+          margin-left: 0;
+          overflow-y: scroll;
+        }
+      }
+    }
+
+    .sorting_asc {
+      background:
+        url(data:image/png;base64,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) no-repeat right 50%;
+    }
+    .sorting_desc {
+      background: url(data:image/png;base64,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) no-repeat right 50%;
+    }
+    .sorting {
+      background: url(	data:image/png;base64,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) no-repeat right 50%;
+    }
+    div.view-wrapper {
+      input[type="checkbox"], .btn-group {
+        margin-bottom: 9px;
+      }
+    }
+
+    a.ui-icon-circle-close {
+      float: right;
+      opacity: 0.2;
+      padding: 1px 0;
+      position: relative;
+      right: 0px;
+      margin-top: 3px;
+      z-index: 10;
+      &:hover {
+        opacity: 0.7;
+      }
+    }
+    .notActive {
+      a.ui-icon-circle-close {
+        visibility: hidden;
+      }
+    }
+  }
+}
+
+.sort-wrapper {
+  .column-name {
+    cursor: pointer;
+    padding-right: 18px;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/jobs.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/jobs.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/jobs.hbs
new file mode 100644
index 0000000..d43f5f0
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/jobs.hbs
@@ -0,0 +1,90 @@
+{{!
+* 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="jobs">
+  <div class="jobs_head">
+    {{#if controller.hasNewJobs}}
+      <div class="new-jobs-link">
+        <a href="javascript:void(null);" {{action updateJobsByClick target="controller"}}>{{t jobs.new_jobs.info}}</a>
+      </div>
+    {{/if}}
+    <div class="jobs-type">
+      {{t jobs.type}} : <span class="label label-info">{{t jobs.type.hive}}</span>
+    </div>
+  </div>
+  <table id="jobs-table" class="table table-bordered table-striped">
+    <thead>
+      {{#view view.sortView classNames="label-row" contentBinding="view.content"}}
+        <th></th>
+        {{view view.parentView.idSort}}
+        {{view view.parentView.userSort}}
+        {{view view.parentView.startTimeSort}}
+        {{view view.parentView.endTimeSort}}
+        {{view view.parentView.durationSort}}
+      {{/view}}
+
+    <tr id="filter-row" class="first">
+      <th></th>
+      <th>{{view view.jobsIdFilterView}}</th>
+      <th>{{view view.userFilterView}}</th>
+      <th class="start-time">{{view view.startTimeFilterView}}</th>
+      <th></th>
+      <th></th>
+    </tr>
+    </thead>
+    <tbody>
+      {{#if view.noDataToShow}}
+      <tr>
+        <td class="no-data" colspan="6">
+          {{controller.jobsMessage}}
+        </td>
+      </tr>
+      {{else}}
+        {{#each job in controller.sortedContent}}
+          <tr>
+            <td>
+              {{#if job.failed}}
+                <i class="icon-remove-sign job-link" {{bind-attr title="view.jobFailMessage"}}></i>
+              {{/if}}
+            </td>
+            <td>{{view view.jobNameView jobBinding="job"}}</td>
+            <td>{{job.user}}</td>
+            <td>{{job.startTimeDisplay}}</td>
+            <td>{{job.endTimeDisplay}}</td>
+            <td>{{job.durationDisplay}}</td>
+          </tr>
+        {{/each}}
+      {{/if}}
+    </tbody>
+  </table>
+
+  <div class="page-bar">
+    <div id="filtered-jobs">
+      {{view.filteredJobs}} - <a href="javascript:void(null);" {{action actionClearFilters target="view"}}>{{t jobs.filtered.clear}}</a>
+    </div>
+    <div class="items-on-page">
+      <label>{{t jobs.show.up.to}}: {{view view.rowsPerPageSelectView selectionBinding="view.displayLength"}}</label>
+    </div>
+    <div class="paging_two_button">
+      {{view view.jobsPaginationLeft}}
+      {{view view.jobsPaginationRight}}
+    </div>
+  </div>
+</div>
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/jobs/jobs_name.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/jobs/jobs_name.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/jobs/jobs_name.hbs
new file mode 100644
index 0000000..37690ef
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/jobs/jobs_name.hbs
@@ -0,0 +1,19 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<a href="#" rel="tooltip" {{bind-attr class="view.isLink :job-link" data-original-title="job.queryText"}}>{{job.name}}</a>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/sort_field_template.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/sort_field_template.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/sort_field_template.hbs
new file mode 100644
index 0000000..6fc49fe
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/sort_field_template.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+<span {{bind-attr class="view.status :column-name"}}>{{view.displayName}}</span>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_first.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_first.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_first.hbs
new file mode 100644
index 0000000..d538654
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_first.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+<i class="icon-step-backward"></i>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_last.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_last.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_last.hbs
new file mode 100644
index 0000000..99dbd68
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_last.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+<i class="icon-step-forward"></i>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_left.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_left.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_left.hbs
new file mode 100644
index 0000000..683a180
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_left.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+<i class="icon-arrow-left"></i>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_right.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_right.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_right.hbs
new file mode 100644
index 0000000..a6b67cd
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/table/navigation/pagination_right.hbs
@@ -0,0 +1,19 @@
+{{!
+* 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.
+}}
+
+<i class="icon-arrow-right"></i>

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_layout.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_layout.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_layout.hbs
new file mode 100644
index 0000000..c2904de
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_layout.hbs
@@ -0,0 +1,19 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<a href="#" {{action "clearFilter" target="view"}} class="ui-icon ui-icon-circle-close"></a> {{yield}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_template.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_template.hbs b/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_template.hbs
new file mode 100644
index 0000000..0529100
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/templates/wrapper_template.hbs
@@ -0,0 +1,25 @@
+{{!
+* 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.
+}}
+
+{{#if view.fieldId}}
+  <input type="hidden" id="{{unbound view.fieldId}}" value="" />
+{{/if}}
+{{view view.filterView}}
+{{#if view.showApply}}
+  <button {{action "actionSetValueOnApply" target="view"}} class="apply-btn btn"><span>{{t apply}}</span></button>
+{{/if}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/bower.json
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/bower.json b/contrib/views/jobs/src/main/resources/ui/bower.json
index 0619e51..9b123b2 100644
--- a/contrib/views/jobs/src/main/resources/ui/bower.json
+++ b/contrib/views/jobs/src/main/resources/ui/bower.json
@@ -3,9 +3,11 @@
   "version": "0.0.1",
   "dependencies": {
     "ember": "1.5.0",
+    "moment": ">=2.7.0",
     "handlebars": "1.2.1",
     "ember-data": "1.0.0-beta.7",
-    "bootstrap": ">3.0",
+    "ember-i18n": "1.6.*",
+    "bootstrap": "2.3.*",
     "ember-addons.bs_for_ember": ">=0.7",
     "ember-json-mapper": "master"
   },


[2/3] git commit: AMBARI-6539. Create main page with table of jobs. (onechiporenko)

Posted by on...@apache.org.
AMBARI-6539. Create main page with table of jobs. (onechiporenko)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/5659f0a2
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/5659f0a2
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/5659f0a2

Branch: refs/heads/trunk
Commit: 5659f0a23b2cb8346d496246d0cce635aeefa87d
Parents: 7bdb1e4
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Fri Jul 18 17:17:03 2014 +0300
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Fri Jul 18 17:17:03 2014 +0300

----------------------------------------------------------------------
 .../jobs/src/main/resources/ui/Gruntfile.js     |   2 +-
 .../ui/app/img/glyphicons-halflings.png         | Bin 0 -> 13826 bytes
 .../jobs/src/main/resources/ui/app/index.html   |   7 +-
 .../src/main/resources/ui/app/scripts/app.js    |  15 +-
 .../ui/app/scripts/assets/hive-queries.json     | 156 ++++++
 .../app/scripts/controllers/job_controller.js   |  19 +
 .../app/scripts/controllers/jobs_controller.js  | 489 +++++++++++++++++++
 .../resources/ui/app/scripts/helpers/ajax.js    |  16 +-
 .../resources/ui/app/scripts/helpers/misc.js    |  28 +-
 .../scripts/mappers/jobs/hive_jobs_mapper.js    |  92 ++--
 .../ui/app/scripts/mixins/run_periodically.js   |  78 +++
 .../ui/app/scripts/models/jobs/hive_job.js      |   2 -
 .../resources/ui/app/scripts/translations.js    |  81 +++
 .../ui/app/scripts/views/filter_view.js         | 488 ++++++++++++++++++
 .../resources/ui/app/scripts/views/job_view.js  |  19 +
 .../resources/ui/app/scripts/views/jobs_view.js | 305 ++++++++++++
 .../resources/ui/app/scripts/views/sort_view.js | 253 ++++++++++
 .../ui/app/scripts/views/table_view.js          | 362 ++++++++++++++
 .../src/main/resources/ui/app/styles/main.less  | 301 +++++++++++-
 .../main/resources/ui/app/templates/jobs.hbs    |  90 ++++
 .../ui/app/templates/jobs/jobs_name.hbs         |  19 +
 .../ui/app/templates/sort_field_template.hbs    |  19 +
 .../table/navigation/pagination_first.hbs       |  19 +
 .../table/navigation/pagination_last.hbs        |  19 +
 .../table/navigation/pagination_left.hbs        |  19 +
 .../table/navigation/pagination_right.hbs       |  19 +
 .../ui/app/templates/wrapper_layout.hbs         |  19 +
 .../ui/app/templates/wrapper_template.hbs       |  25 +
 .../views/jobs/src/main/resources/ui/bower.json |   4 +-
 29 files changed, 2910 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/Gruntfile.js b/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
index b5ce07b..7dc777d 100644
--- a/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
+++ b/contrib/views/jobs/src/main/resources/ui/Gruntfile.js
@@ -257,7 +257,7 @@ module.exports = function (grunt) {
             src: [
               '*.{ico,txt}',
               '.htaccess',
-              'images/{,*/}*.{webp,gif}',
+              'img/*',
               'styles/fonts/*',
               'scripts/assets/**/*'
             ]

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/img/glyphicons-halflings.png
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/img/glyphicons-halflings.png b/contrib/views/jobs/src/main/resources/ui/app/img/glyphicons-halflings.png
new file mode 100644
index 0000000..79bc568
Binary files /dev/null and b/contrib/views/jobs/src/main/resources/ui/app/img/glyphicons-halflings.png differ

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/index.html
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/index.html b/contrib/views/jobs/src/main/resources/ui/app/index.html
index 850126e..ea74dcf 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/index.html
+++ b/contrib/views/jobs/src/main/resources/ui/app/index.html
@@ -27,11 +27,14 @@
   </head>
   <body>
     <!-- build:js(app) scripts/components.js -->
-    <script src="bower_components/jquery/dist/jquery.js"></script>
-    <script src="bower_components/handlebars/handlebars.runtime.js"></script>
+    <script src="bower_components/jquery/jquery.js"></script>
+    <script src="bower_components/bootstrap/js/bootstrap-tooltip.js"></script>
+    <script src="bower_components/moment/moment.js"></script>
+    <script src="bower_components/handlebars/handlebars.js"></script>
     <script src="@@ember"></script>
     <script src="@@ember_data"></script>
     <script src="bower_components/ember-json-mapper/ember-json-mapper.js"></script>
+    <script src="bower_components/ember-i18n/lib/i18n.js"></script>
     <!-- endbuild -->
 
     <!-- build:js(.tmp) scripts/templates.js -->

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
index 5622f4a..c5d68f2 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/app.js
@@ -27,11 +27,19 @@ App.initializer({
   initialize: function(container, application) {
 
     application.reopen({
+
       /**
        * Test mode is automatically enabled if running on localhost
        * @type {bool}
        */
-      testMode: (location.hostname == 'localhost')
+      testMode: (location.hostname == 'localhost'),
+
+      /**
+       * Prefix for API-requests
+       * @type {string}
+       */
+      urlPrefix: '/api/v1'
+
     });
 
   }
@@ -39,8 +47,10 @@ App.initializer({
 
 
 /* Order and include as you please. */
+require('scripts/translations');
 require('scripts/router');
 require('scripts/store');
+require('scripts/mixins/*');
 require('scripts/helpers/*');
 require('scripts/models/**/*');
 require('scripts/mappers/server_data_mapper.js');
@@ -48,4 +58,7 @@ require('scripts/mappers/**/*');
 require('scripts/controllers/*');
 require('scripts/routes/*');
 require('scripts/components/*');
+require('scripts/views/sort_view');
+require('scripts/views/filter_view');
+require('scripts/views/table_view');
 require('scripts/views/*');

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/assets/hive-queries.json
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/assets/hive-queries.json b/contrib/views/jobs/src/main/resources/ui/app/scripts/assets/hive-queries.json
index b601670..8bd8f58 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/assets/hive-queries.json
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/assets/hive-queries.json
@@ -227,6 +227,162 @@
       },
       "entity": "root_20140221171313_c9710dd6-0d1c-4d9c-9dff-031edbd20b66",
       "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20188952544444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20196139444444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20127273144444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20113100844444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20167400444444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
+    },
+    {
+      "starttime": 1393443850756,
+      "events": [
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_COMPLETED",
+          "eventinfo": {}
+        },
+        {
+          "timestamp": 1393443850756,
+          "eventtype": "QUERY_SUBMITTED",
+          "eventinfo": {}
+        }
+      ],
+      "otherinfo": {
+        "status": false,
+        "query": "{}"
+      },
+      "primaryfilters": {
+        "user": [
+          "hive"
+        ]
+      },
+      "entity": "hive_20110915544444_6301b51e-d52c-4618-995f-573e3f59006c",
+      "entitytype": "HIVE_QUERY_ID"
     }
   ]
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
new file mode 100644
index 0000000..e300323
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/job_controller.js
@@ -0,0 +1,19 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.JobController = Ember.Controller.extend({});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
new file mode 100644
index 0000000..c2b6560
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
@@ -0,0 +1,489 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.JobsController = Ember.ArrayController.extend(App.RunPeriodically, {
+
+  name:'mainJobsController',
+
+  /**
+   * Sorted ArrayProxy
+   */
+  sortedContent: [],
+
+  contentAndSortObserver : function() {
+    Ember.run.once(this, 'contentAndSortUpdater');
+  }.observes('content.length', 'content.@each.id', 'content.@each.startTime', 'content.@each.endTime', 'sortProperties', 'sortAscending'),
+
+  contentAndSortUpdater: function() {
+    this.set('sortingDone', false);
+    var content = this.get('content');
+    var sortedContent = content.toArray();
+    var sortProperty = this.get('sortProperty');
+    var sortAscending = this.get('sortAscending');
+    sortedContent.sort(function(r1, r2) {
+      var r1id = r1.get(sortProperty);
+      var r2id = r2.get(sortProperty);
+      if (r1id < r2id)
+        return sortAscending ? -1 : 1;
+      if (r1id > r2id)
+        return sortAscending ? 1 : -1;
+      return 0;
+    });
+    var sortedArray = this.get('sortedContent');
+    var count = 0;
+    sortedContent.forEach(function(sortedJob){
+      if(sortedArray.length <= count) {
+        sortedArray.pushObject(Ember.Object.create());
+      }
+      sortedArray[count].set('failed', sortedJob.get('failed'));
+      sortedArray[count].set('hasTezDag', sortedJob.get('hasTezDag'));
+      sortedArray[count].set('queryText', sortedJob.get('queryText'));
+      sortedArray[count].set('name', sortedJob.get('name'));
+      sortedArray[count].set('user', sortedJob.get('user'));
+      sortedArray[count].set('id', sortedJob.get('id'));
+      sortedArray[count].set('startTimeDisplay', sortedJob.get('startTimeDisplay'));
+      sortedArray[count].set('endTimeDisplay', sortedJob.get('endTimeDisplay'));
+      sortedArray[count].set('durationDisplay', sortedJob.get('durationDisplay'));
+      count ++;
+    });
+    if(sortedArray.length > count) {
+      for(var c = sortedArray.length-1; c >= count; c--){
+        sortedArray.removeObject(sortedArray[c]);
+      }
+    }
+    sortedContent.length = 0;
+    this.set('sortingDone', true);
+  },
+
+  navIDs: {
+    backIDs: [],
+    nextID: ''
+  },
+
+  lastJobID: '',
+
+  hasNewJobs: false,
+
+  loaded : false,
+
+  loading : false,
+
+  resetPagination: false,
+
+  loadJobsTimeout: null,
+
+  loadTimeout: null,
+
+  jobsUpdateInterval: 6000,
+
+  jobsUpdate: null,
+
+  sortingColumn: null,
+
+  sortProperty: 'id',
+
+  sortAscending: true,
+
+  sortingDone: true,
+
+  jobsMessage: Em.I18n.t('jobs.loadingTasks'),
+
+  sortingColumnObserver: function () {
+    if(this.get('sortingColumn')){
+      this.set('sortProperty', this.get('sortingColumn').get('name'));
+      this.set('sortAscending', this.get('sortingColumn').get('status') !== "sorting_desc");
+    }
+  }.observes('sortingColumn.name','sortingColumn.status'),
+
+  updateJobsByClick: function () {
+    this.set('navIDs.backIDs', []);
+    this.set('navIDs.nextID', '');
+    this.get('filterObject').set('nextFromId', '');
+    this.get('filterObject').set('backFromId', '');
+    this.get('filterObject').set('fromTs', '');
+    this.set('hasNewJobs', false);
+    this.set('resetPagination', true);
+    this.loadJobs();
+  },
+
+  updateJobs: function (controllerName, funcName) {
+    clearInterval(this.get('jobsUpdate'));
+    var self = this;
+    var interval = setInterval(function () {
+      App.router.get(controllerName)[funcName]();
+    }, this.jobsUpdateInterval);
+    this.set('jobsUpdate', interval);
+  },
+
+  totalOfJobs: 0,
+
+  setTotalOfJobs: function () {
+    if(this.get('totalOfJobs') < this.get('content.length')){
+      this.set('totalOfJobs', this.get('content.length'));
+    }
+  }.observes('content.length'),
+
+  filterObject: Ember.Object.create({
+    id: "",
+    isIdFilterApplied: false,
+    jobsLimit: '10',
+    user: "",
+    windowStart: "",
+    windowEnd: "",
+    nextFromId: "",
+    backFromId: "",
+    fromTs: "",
+    isAnyFilterApplied: false,
+
+    onApplyIdFilter: function () {
+      this.set('isIdFilterApplied', this.get('id') != "");
+    }.observes('id'),
+
+    /**
+     * Direct binding to startTime filter field
+     */
+    startTime: "",
+
+    onStartTimeChange:function(){
+      var time = "";
+      var curTime = new Date().getTime();
+      switch (this.get('startTime')) {
+        case 'Past 1 hour':
+          time = curTime - 3600000;
+          break;
+        case 'Past 1 Day':
+          time = curTime - 86400000;
+          break;
+        case 'Past 2 Days':
+          time = curTime - 172800000;
+          break;
+        case 'Past 7 Days':
+          time = curTime - 604800000;
+          break;
+        case 'Past 14 Days':
+          time = curTime - 1209600000;
+          break;
+        case 'Past 30 Days':
+          time = curTime - 2592000000;
+          break;
+        case 'Custom':
+          this.showCustomDatePopup();
+          break;
+        case 'Any':
+          time = "";
+          break;
+      }
+      if(this.get('startTime') != "Custom"){
+        this.set("windowStart", time);
+        this.set("windowEnd", "");
+      }
+    }.observes("startTime"),
+
+    // Fields values from Select Custom Dates form
+    customDateFormFields: Ember.Object.create({
+      startDate: null,
+      hoursForStart: null,
+      minutesForStart: null,
+      middayPeriodForStart: null,
+      endDate: null,
+      hoursForEnd: null,
+      minutesForEnd: null,
+      middayPeriodForEnd: null
+    }),
+
+    errors: Ember.Object.create({
+      isStartDateError: false,
+      isEndDateError: false
+    }),
+
+    errorMessages: Ember.Object.create({
+      startDate: '',
+      endDate: ''
+    }),
+
+    showCustomDatePopup: function () {
+      var self = this,
+        windowEnd = "",
+        windowStart = "";
+      /*App.ModalPopup.show({
+        header: Em.I18n.t('jobs.table.custom.date.header'),
+        onPrimary: function () {
+          self.validate();
+          if(self.get('errors.isStartDateError') || self.get('errors.isEndDateError')){
+            return;
+          }
+
+          var windowStart = self.createCustomStartDate();
+          var windowEnd = self.createCustomEndDate();
+
+          self.set("windowStart", windowStart.getTime());
+          self.set("windowEnd", windowEnd.getTime());
+          this.hide();
+        },
+        onSecondary: function () {
+          self.set('startTime','Any');
+          this.hide();
+        },
+        bodyClass: App.JobsCustomDatesSelectView.extend({
+          controller: self
+        })
+      });*/
+    },
+
+    createCustomStartDate : function () {
+      var startDate = this.get('customDateFormFields.startDate'),
+        hoursForStart = this.get('customDateFormFields.hoursForStart'),
+        minutesForStart = this.get('customDateFormFields.minutesForStart'),
+        middayPeriodForStart = this.get('customDateFormFields.middayPeriodForStart');
+      if (startDate && hoursForStart && minutesForStart && middayPeriodForStart) {
+        return new Date(startDate + ' ' + hoursForStart + ':' + minutesForStart + ' ' + middayPeriodForStart);
+      }
+      return null;
+    },
+
+    createCustomEndDate : function () {
+      var endDate = this.get('customDateFormFields.endDate'),
+        hoursForEnd = this.get('customDateFormFields.hoursForEnd'),
+        minutesForEnd = this.get('customDateFormFields.minutesForEnd'),
+        middayPeriodForEnd = this.get('customDateFormFields.middayPeriodForEnd');
+      if (endDate && hoursForEnd && minutesForEnd && middayPeriodForEnd) {
+        return new Date(endDate + ' ' + hoursForEnd + ':' + minutesForEnd + ' ' + middayPeriodForEnd);
+      }
+      return null;
+    },
+
+    clearErrors: function () {
+      var errorMessages = this.get('errorMessages');
+      Em.keys(errorMessages).forEach(function (key) {
+        errorMessages.set(key, '');
+      }, this);
+      var errors = this.get('errors');
+      Em.keys(errors).forEach(function (key) {
+        errors.set(key, false);
+      }, this);
+    },
+
+    // Validation for every field in customDateFormFields
+    validate: function () {
+      var formFields = this.get('customDateFormFields'),
+        errors = this.get('errors'),
+        errorMessages = this.get('errorMessages');
+      this.clearErrors();
+      // Check if feild is empty
+      Em.keys(errorMessages).forEach(function (key) {
+        if (!formFields.get(key)) {
+          errors.set('is' + key.capitalize() + 'Error', true);
+          errorMessages.set(key, Em.I18n.t('jobs.customDateFilter.error.required'));
+        }
+      }, this);
+      // Check that endDate is after startDate
+      var startDate = this.createCustomStartDate(),
+        endDate = this.createCustomEndDate();
+      if (startDate && endDate && (startDate > endDate)) {
+        errors.set('isEndDateError', true);
+        errorMessages.set('endDate', Em.I18n.t('jobs.customDateFilter.error.date.order'));
+      }
+    },
+
+    /**
+     * Create link for server request
+     * @return {String}
+     */
+    createJobsFiltersLink: function() {
+      var link = "?fields=events,primaryfilters,otherinfo&secondaryFilter=tez:true",
+        numberOfAppliedFilters = 0;
+
+      if(this.get("id") !== "") {
+        link = "/" + this.get("id") + link;
+        numberOfAppliedFilters++;
+      }
+
+      link += "&limit=" + (parseInt(this.get("jobsLimit")) + 1);
+
+      if(this.get("user") !== ""){
+        link += "&primaryFilter=user:" + this.get("user");
+        numberOfAppliedFilters++;
+      }
+      if(this.get("backFromId") != ""){
+        link += "&fromId=" + this.get("backFromId");
+      }
+      if(this.get("nextFromId") != ""){
+        link += "&fromId=" + this.get("nextFromId");
+      }
+      if(this.get("fromTs") != ""){
+        link += "&fromTs=" + this.get("fromTs");
+      }
+      if(this.get("startTime") !== "" && this.get("startTime") !== "Any"){
+        link += this.get("windowStart") !== "" ? ("&windowStart=" + this.get("windowStart")) : "";
+        link += this.get("windowEnd") !== "" ? ("&windowEnd=" + this.get("windowEnd")) : "";
+        numberOfAppliedFilters++;
+      }
+
+      this.set('isAnyFilterApplied', numberOfAppliedFilters > 0);
+
+      return link;
+    }
+  }),
+
+  /*columnsName: Ember.ArrayController.create({
+    content: [
+      { name: Em.I18n.t('jobs.column.id'), index: 0 },
+      { name: Em.I18n.t('jobs.column.user'), index: 1 },
+      { name: Em.I18n.t('jobs.column.start.time'), index: 2 },
+      { name: Em.I18n.t('jobs.column.end.time'), index: 3 },
+      { name: Em.I18n.t('jobs.column.duration'), index: 4 }
+    ],
+    columnsCount: function () {
+      return this.get('content.length') + 1;
+    }.property('content.length')
+  }),*/
+
+  lastIDSuccessCallback: function(data) {
+    if(!data.entities[0]){
+      return;
+    }
+    var lastReceivedID = data.entities[0].entity;
+    if(this.get('lastJobID') == '') {
+      this.set('lastJobID', lastReceivedID);
+      if (this.get('loaded') && App.HiveJob.find().get('length') < 1) {
+        this.set('hasNewJobs', true);
+      }
+    }
+    else
+      if (this.get('lastJobID') !== lastReceivedID) {
+        this.set('lastJobID', lastReceivedID);
+        if(!App.HiveJob.find().findProperty('id', lastReceivedID)) {
+          this.set('hasNewJobs', true);
+        }
+      }
+  },
+
+  lastIDErrorCallback: function(data, jqXHR, textStatus) {
+    console.debug(jqXHR);
+  },
+
+  checkDataLoadingError: function (jqXHR){
+    /*var atsComponent = App.HostComponent.find().findProperty('componentName','APP_TIMELINE_SERVER');
+    if(atsComponent && atsComponent.get('workStatus') != "STARTED") {
+      this.set('jobsMessage', Em.I18n.t('jobs.error.ats.down'));
+    }else if (jqXHR && jqXHR.status == 400) {
+      this.set('jobsMessage', Em.I18n.t('jobs.error.400'));
+    }else if ((!jqXHR && this.get('loaded') && !this.get('loading')) || (jqXHR && jqXHR.status == 500)) {
+      this.set('jobsMessage', Em.I18n.t('jobs.nothingToShow'));
+    }else{
+      this.set('jobsMessage', Em.I18n.t('jobs.loadingTasks'));
+    }*/
+  },
+
+  init: function() {
+    this.set('interval', 6000);
+    this.loop('loadJobs');
+  },
+
+  loadJobs : function() {
+      //var yarnService = App.YARNService.find().objectAt(0),
+      //atsComponent = App.HostComponent.find().findProperty('componentName','APP_TIMELINE_SERVER'),
+      //atsInValidState = !!atsComponent && atsComponent.get('workStatus') === "STARTED",
+      //retryLoad = this.checkDataLoadingError();
+    //if (yarnService != null && atsInValidState) {
+    this.set('loading', true);
+    /*var historyServerHostName = yarnService.get('appTimelineServer.hostName'),
+      filtersLink = this.get('filterObject').createJobsFiltersLink(),
+      hiveQueriesUrl = App.get('testMode') ? "/scripts/assets/hive-queries.json" : "/proxy?url=http://" + historyServerHostName
+        + ":" + yarnService.get('ahsWebPort') + "/ws/v1/timeline/HIVE_QUERY_ID" + filtersLink;*/
+    /*App.ajax.send({
+      name: 'jobs.lastID',
+      sender: self,
+      data: {
+        historyServerHostName: '',//historyServerHostName,
+        ahsWebPort: ''//yarnService.get('ahsWebPort')
+      },
+      success: 'lastIDSuccessCallback',
+      error : 'lastIDErrorCallback'
+    });*/
+    App.ajax.send({
+      name: 'load_jobs',
+      sender: this,
+      data: {
+        historyServerHostName: '',
+        ahsWebPort: '',
+        filtersLink: this.get('filterObject').createJobsFiltersLink()
+      },
+      success: 'loadJobsSuccessCallback',
+      error : 'loadJobsErrorCallback'
+    });
+  },
+
+  loadJobsSuccessCallback: function(data) {
+    App.hiveJobsMapper.map(data);
+    this.set('loading', false);
+    if(this.get('loaded') == false || this.get('resetPagination') == true) {
+      this.initializePagination();
+      this.set('resetPagination', false);
+    }
+    this.set('loaded', true);
+  },
+
+  loadJobsErrorCallback: function(jqXHR) {
+    App.hiveJobsMapper.map({entities : []});
+    this.checkDataLoadingError(jqXHR);
+  },
+
+  initializePagination: function() {
+    var back_link_IDs = this.get('navIDs.backIDs.[]');
+    if(!back_link_IDs.contains(this.get('lastJobID'))) {
+      back_link_IDs.push(this.get('lastJobID'));
+    }
+    this.set('filterObject.backFromId', this.get('lastJobID'));
+    this.get('filterObject').set('fromTs', new Date().getTime());
+  },
+
+  navigateNext: function() {
+    this.set("filterObject.backFromId", '');
+    var back_link_IDs = this.get('navIDs.backIDs.[]');
+    var lastBackID = this.get('navIDs.nextID');
+    if(!back_link_IDs.contains(lastBackID)) {
+      back_link_IDs.push(lastBackID);
+    }
+    this.set('navIDs.backIDs.[]', back_link_IDs);
+    this.set("filterObject.nextFromId", this.get('navIDs.nextID'));
+    this.set('navIDs.nextID', '');
+    this.loadJobs();
+  },
+
+  navigateBack: function() {
+    this.set("filterObject.nextFromId", '');
+    var back_link_IDs = this.get('navIDs.backIDs.[]');
+    back_link_IDs.pop();
+    var lastBackID = back_link_IDs[back_link_IDs.length - 1];
+    this.set('navIDs.backIDs.[]', back_link_IDs);
+    this.set("filterObject.backFromId", lastBackID);
+    this.loadJobs();
+  },
+
+  refreshLoadedJobs : function() {
+    this.loadJobs();
+  }.observes(
+      'filterObject.id',
+      'filterObject.jobsLimit',
+      'filterObject.user',
+      'filterObject.windowStart',
+      'filterObject.windowEnd'
+    )
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
index 42bae63..2c301ad 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
@@ -28,7 +28,21 @@
  *
  * @type {Object}
  */
-var urls = {};
+var urls = {
+
+  'load_jobs': {
+    real: '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/HIVE_QUERY_ID{filtersLink}',
+    mock: '/scripts/assets/hive-queries.json',
+    apiPrefix: ''
+  },
+
+  'jobs_lastID': {
+    real: '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true',
+    mock: '/scripts/assets/hive-queries.json',
+    apiPrefix: ''
+  }
+
+};
 /**
  * Replace data-placeholders to its values
  *

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/misc.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/misc.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/misc.js
index 25af752..f26d658 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/misc.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/misc.js
@@ -28,7 +28,7 @@ App.Helpers.misc = {
       } else {
         if (value < 1048576) {
           value = (value / 1024).toFixed(1) + 'KB';
-        } else  if (value >= 1048576 && value < 1073741824){
+        } else if (value >= 1048576 && value < 1073741824) {
           value = (value / 1048576).toFixed(1) + 'MB';
         } else {
           value = (value / 1073741824).toFixed(2) + 'GB';
@@ -36,6 +36,32 @@ App.Helpers.misc = {
       }
     }
     return value;
+  },
+
+  /**
+   * Convert ip address to integer
+   * @param ip
+   * @return integer
+   */
+  ipToInt: function (ip) {
+    // *     example 1: ipToInt('192.0.34.166');
+    // *     returns 1: 3221234342
+    // *     example 2: ipToInt('255.255.255.256');
+    // *     returns 2: false
+    // Verify IP format.
+    if (!/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip)) {
+      return false; // Invalid format.
+    }
+    // Reuse ip variable for component counter.
+    var d = ip.split('.');
+    return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]);
   }
 
 };
+
+App.tooltip = function (self, options) {
+  self.tooltip(options);
+  self.on("remove DOMNodeRemoved", function () {
+    $(this).trigger('mouseleave');
+  });
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
index 9a1fc07..49b373c 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/mappers/jobs/hive_jobs_mapper.js
@@ -17,12 +17,46 @@
 
 App.hiveJobsMapper = App.QuickDataMapper.create({
 
-  model: App.HiveJob,
+  json_map: {
+    id: 'entity',
+    name: 'entity',
+    user: 'primaryfilters.user',
+    hasTezDag: {
+      custom: function(source) {
+        var query = Ember.get(source, 'otherinfo.query');
+        return Ember.isNone(query) ? false : query.match("\"Tez\".*\"DagName:\"");
+      }
+    },
+    queryText: {
+      custom: function(source) {
+        var query = Ember.get(source, 'otherinfo.query');
+        return Ember.isNone(query) ? '' : $.parseJSON(query).queryText;
+      }
+    },
+    failed: {
+      custom: function(source) {
+        return Ember.get(source ,'otherinfo.status') === false;
+      }
+    },
+    startTime: {
+      custom: function(source) {
+        return source.starttime > 0 ? source.starttime : null
+      }
+    },
+    endTime: {
+      custom: function(source) {
+        return source.endtime > 0 ? source.endtime : null
+      }
+    }
+  },
 
   map: function (json) {
 
     var model = this.get('model'),
+      jobsToDelete = App.HiveJob.store.all('hiveJob').get('content').mapProperty('id'),
+      map = this.get('json_map'),
       hiveJobs = [];
+
     if (json) {
       if (!json.entities) {
         json.entities = [];
@@ -30,67 +64,35 @@ App.hiveJobsMapper = App.QuickDataMapper.create({
           json.entities = [json];
         }
       }
-      var currentEntityMap = {};
+
       json.entities.forEach(function (entity) {
-        currentEntityMap[entity.entity] = entity.entity;
-        var hiveJob = {
-          id: entity.entity,
-          name: entity.entity,
-          user: entity.primaryfilters.user
-        };
-        hiveJob.has_tez_dag = false;
-        hiveJob.query_text = '';
-        if (entity.otherinfo && entity.otherinfo.query) {
-          // Explicit false match needed for when failure hook not set
-          hiveJob.failed = entity.otherinfo.status === false;
-          hiveJob.has_tez_dag = entity.otherinfo.query.match("\"Tez\".*\"DagName:\"");
-          var queryJson = $.parseJSON(entity.otherinfo.query);
-          if (queryJson && queryJson.queryText) {
-            hiveJob.query_text = queryJson.queryText;
-          }
-        }
+        var hiveJob = Ember.JsonMapper.map(entity, map);
+
         if (entity.events != null) {
           entity.events.forEach(function (event) {
             switch (event.eventtype) {
               case "QUERY_SUBMITTED":
-                hiveJob.start_time = event.timestamp;
+                hiveJob.startTime = event.timestamp;
                 break;
               case "QUERY_COMPLETED":
-                hiveJob.end_time = event.timestamp;
+                hiveJob.endTime = event.timestamp;
                 break;
               default:
                 break;
             }
           });
         }
-        if (!hiveJob.start_time && entity.starttime > 0) {
-          hiveJob.start_time = entity.starttime;
-        }
-        if (!hiveJob.end_time && entity.endtime > 0) {
-          hiveJob.end_time = entity.endtime;
-        }
         hiveJobs.push(hiveJob);
-        hiveJob = null;
-        entity = null;
+        jobsToDelete = jobsToDelete.without(hiveJob.id);
       });
 
-      /*if(hiveJobs.length > App.router.get('mainJobsController.filterObject.jobsLimit')) {
-       var lastJob = hiveJobs.pop();
-       if(App.router.get('mainJobsController.navIDs.nextID') != lastJob.id) {
-       App.router.set('mainJobsController.navIDs.nextID', lastJob.id);
-       }
-       currentEntityMap[lastJob.id] = null;
-       }*/
+      jobsToDelete.forEach(function (id) {
+        var r = App.HiveJob.store.getById('hiveJob', id);
+        if(r) r.destroyRecord();
+      });
 
-      // Delete IDs not seen from server
-      /*var hiveJobsModel = model.find().toArray();
-       hiveJobsModel.forEach(function(job) {
-       if (job && !currentEntityMap[job.get('id')]) {
-       this.deleteRecord(job);
-       }
-       }, this);*/
     }
     App.HiveJob.store.pushMany('hiveJob', hiveJobs);
-  },
-  config: {}
+  }
+
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/mixins/run_periodically.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/mixins/run_periodically.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/mixins/run_periodically.js
new file mode 100644
index 0000000..a6c4bbf
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/mixins/run_periodically.js
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ */
+
+/**
+ * Allow to run object method periodically and stop it
+ * Example:
+ *  <code>
+ *    var obj = Ember.Object.createWithMixins(App.RunPeriodically, {
+ *      method: Ember.K
+ *    });
+ *    obj.set('interval', 10000); // override default value
+ *    obj.loop('method'); // run periodically
+ *    obj.stop(); // stop running
+ *  </code>
+ * @type {Ember.Mixin}
+ */
+App.RunPeriodically = Ember.Mixin.create({
+
+  /**
+   * Interval for loop
+   * @type {number}
+   */
+  interval: 5000,
+
+  /**
+   * setTimeout's return value
+   * @type {number}
+   */
+  timer: null,
+
+  /**
+   * Run <code>methodName</code> periodically with <code>interval</code>
+   * @param {string} methodName method name to run periodically
+   * @param {bool} initRun should methodName be run before setInterval call (default - true)
+   * @method run
+   */
+  loop: function(methodName, initRun) {
+    initRun = Em.isNone(initRun) ? true : initRun;
+    var self = this,
+      interval = this.get('interval');
+    Ember.assert('Interval should be numeric and greated than 0', $.isNumeric(interval) && interval > 0);
+    if (initRun) {
+      this[methodName]();
+    }
+    this.set('timer',
+      setInterval(function () {
+        self[methodName]();
+      }, interval)
+    );
+  },
+
+  /**
+   * Stop running <code>timer</code>
+   * @method stop
+   */
+  stop: function() {
+    var timer = this.get('timer');
+    if (!Em.isNone(timer)) {
+      clearTimeout(timer);
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
index a3784e8..53d309e 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/models/jobs/hive_job.js
@@ -21,8 +21,6 @@ App.HiveJob = App.AbstractJob.extend({
 
   queryText : DS.attr('string'),
 
-  stages : DS.attr('array'),
-
   hasTezDag: DS.attr('boolean'),
 
   tezDag : DS.belongsTo('tezDag', {async:true}),

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
new file mode 100644
index 0000000..a656c97
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/translations.js
@@ -0,0 +1,81 @@
+/**
+ * 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.
+ */
+
+Ember.I18n.translations = {
+
+  'any': 'Any',
+  'apply': 'Apply',
+
+  'jobs.type':'Jobs Type',
+  'jobs.type.hive':'Hive',
+  'jobs.show.up.to':'Show up to',
+  'jobs.filtered.jobs':'%@ jobs showing',
+  'jobs.filtered.clear':'clear filters',
+  'jobs.column.id':'Id',
+  'jobs.column.user':'User',
+  'jobs.column.start.time':'Start Time',
+  'jobs.column.end.time':'End Time',
+  'jobs.column.duration':'Duration',
+  'jobs.new_jobs.info':'New jobs available on server.',
+  'jobs.loadingTasks': 'Loading...',
+
+  'jobs.nothingToShow': 'No jobs to display',
+  'jobs.error.ats.down': 'Jobs data cannot be shown since YARN App Timeline Server is not running.',
+  'jobs.error.400': 'Unable to load data.',
+  'jobs.table.custom.date.am':'AM',
+  'jobs.table.custom.date.pm':'PM',
+  'jobs.table.custom.date.header':'Select Custom Dates',
+  'jobs.table.job.fail':'Job failed to run',
+  'jobs.customDateFilter.error.required':'This field is required',
+  'jobs.customDateFilter.error.date.order':'End Date must be after Start Date',
+  'jobs.customDateFilter.startTime':'Start Time',
+  'jobs.customDateFilter.endTime':'End Time',
+  'jobs.hive.failed':'JOB FAILED',
+  'jobs.hive.more':'show more',
+  'jobs.hive.less':'show less',
+  'jobs.hive.query':'Hive Query',
+  'jobs.hive.stages':'Stages',
+  'jobs.hive.yarnApplication':'YARN&nbsp;Application',
+  'jobs.hive.tez.tasks':'Tez Tasks',
+  'jobs.hive.tez.hdfs':'HDFS',
+  'jobs.hive.tez.localFiles':'Local Files',
+  'jobs.hive.tez.spilledRecords':'Spilled Records',
+  'jobs.hive.tez.records':'Records',
+  'jobs.hive.tez.reads':'{0} reads',
+  'jobs.hive.tez.writes':'{0} writes',
+  'jobs.hive.tez.records.count':'{0} Records',
+  'jobs.hive.tez.operatorPlan':'Operator Plan',
+  'jobs.hive.tez.dag.summary.metric':'Summary Metric',
+  'jobs.hive.tez.dag.error.noDag.title':'No Tez Information',
+  'jobs.hive.tez.dag.error.noDag.message':'This job does not identify any Tez information.',
+  'jobs.hive.tez.dag.error.noDagId.title':'No Tez Information',
+  'jobs.hive.tez.dag.error.noDagId.message':'No Tez information was found for this job. Either it is waiting to be run, or has exited unexpectedly.',
+  'jobs.hive.tez.dag.error.noDagForId.title':'No Tez Information',
+  'jobs.hive.tez.dag.error.noDagForId.message':'No details were found for the Tez ID given to this job.',
+  'jobs.hive.tez.metric.input':'Input',
+  'jobs.hive.tez.metric.output':'Output',
+  'jobs.hive.tez.metric.recordsRead':'Records Read',
+  'jobs.hive.tez.metric.recordsWrite':'Records Written',
+  'jobs.hive.tez.metric.tezTasks':'Tez Tasks',
+  'jobs.hive.tez.metric.spilledRecords':'Spilled Records',
+  'jobs.hive.tez.edge.':'Unknown',
+  'jobs.hive.tez.edge.contains':'Contains',
+  'jobs.hive.tez.edge.broadcast':'Broadcast',
+  'jobs.hive.tez.edge.scatter_gather':'Shuffle',
+
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/views/filter_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/filter_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/filter_view.js
new file mode 100644
index 0000000..0506286
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/filter_view.js
@@ -0,0 +1,488 @@
+/**
+ * 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.
+ */
+
+/**
+ * Wrapper View for all filter components. Layout template and common actions are located inside of it.
+ * Logic specific for data component(input, select, or custom multi select, which fire any changes on interface) are
+ * located in inner view - <code>filterView</code>.
+ *
+ * If we want to have input filter, put <code>textFieldView</code> to it.
+ * All inner views implemented below this view.
+ * @type {*}
+ */
+
+var wrapperView = Ember.View.extend({
+  classNames: ['view-wrapper'],
+  layoutName: 'wrapper_layout',
+  templateName: 'wrapper_template',
+
+  value: null,
+
+  /**
+   * Column index
+   */
+  column: null,
+
+  /**
+   * If this field is exists we dynamically create hidden input element and set value there.
+   * Used for some cases, where this values will be used outside of component
+   */
+  fieldId: null,
+
+  clearFilter: function(){
+    this.set('value', this.get('emptyValue'));
+    if(this.get('setPropertyOnApply')){
+      this.setValueOnApply();
+    }
+    return false;
+  },
+
+  setValueOnApply: function() {
+    if(this.get('value') == null){
+      this.set('value', '')
+    }
+    this.set(this.get('setPropertyOnApply'), this.get('value'));
+    return false;
+  },
+
+  actions: {
+    actionSetValueOnApply: function() {
+      this.setValueOnApply();
+    }
+  },
+
+  /**
+   * Use to determine whether filter is clear or not. Also when we want to set empty value
+   */
+  emptyValue: '',
+
+  /**
+   * Whether our <code>value</code> is empty or not
+   * @return {Boolean}
+   */
+  isEmpty: function(){
+    if(this.get('value') === null){
+      return true;
+    }
+    return this.get('value').toString() === this.get('emptyValue').toString();
+  },
+
+  /**
+   * Show/Hide <code>Clear filter</code> button.
+   * Also this method updates computed field related to <code>fieldId</code> if it exists.
+   * Call <code>onChangeValue</code> callback when everything is done.
+   */
+  showClearFilter: function(){
+    if(!this.get('parentNode')){
+      return;
+    }
+    // get the sort view element in the same column to current filter view to highlight them together
+    var relatedSort = $(this.get('element')).parents('thead').find('.sort-view-' + this.get('column'));
+    if(this.isEmpty()){
+      this.get('parentNode').removeClass('active-filter');
+      this.get('parentNode').addClass('notActive');
+      relatedSort.removeClass('active-sort');
+    } else {
+      this.get('parentNode').removeClass('notActive');
+      this.get('parentNode').addClass('active-filter');
+      relatedSort.addClass('active-sort');
+    }
+
+    if(this.get('fieldId')){
+      this.$('> input').eq(0).val(this.get('value'));
+    }
+
+    this.onChangeValue();
+  }.observes('value'),
+
+  /**
+   * Callback for value changes
+   */
+  onChangeValue: function(){
+
+  },
+
+  /**
+   * Filter components is located here. Should be redefined
+   */
+  filterView: Em.View,
+
+  /**
+   * Update class of parentNode(hide clear filter button) on page load
+   */
+  didInsertElement: function(){
+    var parent = this.$().parent();
+    this.set('parentNode', parent);
+    parent.addClass('notActive');
+  }
+});
+
+/**
+ * Simple input control for wrapperView
+ */
+var textFieldView = Ember.TextField.extend({
+  type:'text',
+  placeholder: Em.I18n.t('any'),
+  valueBinding: "parentView.value"
+});
+
+/**
+ * Simple multiselect control for wrapperView.
+ * Used to render blue button and popup, which opens on button click.
+ * All content related logic should be implemented manually outside of it
+ */
+var componentFieldView = Ember.View.extend({
+  classNames: ['btn-group'],
+  classNameBindings: ['isFilterOpen:open:'],
+
+  /**
+   * Whether popup is shown or not
+   */
+  isFilterOpen: false,
+
+  /**
+   * We have <code>value</code> property similar to inputs <code>value</code> property
+   */
+  valueBinding: 'parentView.value',
+
+  /**
+   * Clear filter to initial state
+   */
+  clearFilter: function(){
+    this.set('value', '');
+  },
+
+  /**
+   * Onclick handler for <code>cancel filter</code> button
+   */
+  closeFilter:function () {
+    $(document).unbind('click');
+    this.set('isFilterOpen', false);
+  },
+
+  /**
+   * Onclick handler for <code>apply filter</code> button
+   */
+  applyFilter:function() {
+    this.closeFilter();
+  },
+
+  /**
+   * Onclick handler for <code>show component filter</code> button.
+   * Also this function is used in some other places
+   */
+  clickFilterButton:function () {
+    var self = this;
+    this.set('isFilterOpen', !this.get('isFilterOpen'));
+    if (this.get('isFilterOpen')) {
+
+      var dropDown = this.$('.filter-components');
+      var firstClick = true;
+      $(document).bind('click', function (e) {
+        if (!firstClick && $(e.target).closest(dropDown).length == 0) {
+          self.set('isFilterOpen', false);
+          $(document).unbind('click');
+        }
+        firstClick = false;
+      });
+    }
+  }
+});
+
+/**
+ * Simple select control for wrapperView
+ */
+var selectFieldView = Ember.Select.extend({
+  selectionBinding: 'parentView.value',
+  contentBinding: 'parentView.content'
+});
+
+/**
+ * Result object, which will be accessible outside
+ * @type {Object}
+ */
+App.Filters = {
+  /**
+   * You can access wrapperView outside
+   */
+  wrapperView : wrapperView,
+
+  /**
+   * And also controls views if need it
+   */
+  textFieldView : textFieldView,
+  selectFieldView: selectFieldView,
+  componentFieldView: componentFieldView,
+
+  /**
+   * Quick create input filters
+   * @param config parameters of <code>wrapperView</code>
+   */
+  createTextView : function(config){
+    config.fieldType = config.fieldType || 'input-medium';
+    config.filterView = textFieldView.extend({
+      classNames : [ config.fieldType ]
+    });
+
+    return wrapperView.extend(config);
+  },
+
+  /**
+   * Quick create multiSelect filters
+   * @param config parameters of <code>wrapperView</code>
+   */
+  createComponentView : function(config){
+    config.clearFilter = function(){
+      this.forEachChildView(function(item){
+        if(item.clearFilter){
+          item.clearFilter();
+        }
+      });
+      return false;
+    };
+
+    return wrapperView.extend(config);
+  },
+
+  /**
+   * Quick create select filters
+   * @param config parameters of <code>wrapperView</code>
+   */
+  createSelectView: function(config){
+
+    config.fieldType = config.fieldType || 'input-medium';
+    config.filterView = selectFieldView.extend({
+      classNames : [ config.fieldType ],
+      attributeBindings: ['disabled','multiple'],
+      disabled: false
+    });
+    config.emptyValue = Em.I18n.t('any');
+
+    return wrapperView.extend(config);
+  },
+  /**
+   * returns the filter function, which depends on the type of property
+   * @param type
+   * @param isGlobal check is search global
+   * @return {Function}
+   */
+  getFilterByType: function(type, isGlobal){
+    switch (type){
+      case 'ambari-bandwidth':
+        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 'g':
+              compareValue *= 1073741824;
+              break;
+            case 'm':
+              compareValue *= 1048576;
+              break;
+            case 'k':
+              compareValue *= 1024;
+              break;
+            default:
+              //default value in GB
+              compareValue *= 1073741824;
+          }
+          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;
+            }
+          }
+
+          switch (compareChar) {
+            case '<':
+              if (compareValue > convertedRowValue) match = true;
+              break;
+            case '>':
+              if (compareValue < convertedRowValue) match = true;
+              break;
+            case false:
+            case '=':
+              if (compareValue == convertedRowValue) match = true;
+              break;
+          }
+          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 = App.dateTime() - rowValue;
+          switch (rangeExp) {
+            case 'Past 1 hour':
+              match = timePassed <= 3600000;
+              break;
+            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);
+          var compareValue;
+          var match = false;
+          if (rangeExp.length == 1) {
+            if (isNaN(parseInt(compareChar))) {
+              // User types only '=' or '>' or '<', so don't filter column values
+              match = true;
+              return match;
+            }
+            else {
+              compareValue = parseFloat(parseFloat(rangeExp).toFixed(2));
+            }
+          }
+          else {
+            if (isNaN(parseInt(compareChar))) {
+              compareValue = parseFloat(parseFloat(rangeExp.substr(1, rangeExp.length)).toFixed(2));
+            }
+            else {
+              compareValue = parseFloat(parseFloat(rangeExp.substr(0, rangeExp.length)).toFixed(2));
+            }
+          }
+          rowValue = parseFloat((jQuery(rowValue).text()) ? jQuery(rowValue).text() : rowValue);
+          match = false;
+          switch (compareChar) {
+            case '<':
+              if (compareValue > rowValue) match = true;
+              break;
+            case '>':
+              if (compareValue < rowValue) match = true;
+              break;
+            case '=':
+              if (compareValue == rowValue) match = true;
+              break;
+            default:
+              if (rangeExp == rowValue) match = true;
+          }
+          return match;
+        };
+        break;
+      case 'multiple':
+        return function(origin, compareValue){
+          var options = compareValue.split(','),
+            rowValue = (typeof (origin) === "string") ? origin : origin.mapProperty('componentName').join(" ");
+          var str = new RegExp(compareValue, "i");
+          for (var i = 0; i < options.length; i++) {
+            if(!isGlobal) {
+              str = new RegExp('(\\W|^)' + options[i] + '(\\W|$)');
+            }
+            if (rowValue.search(str) !== -1) {
+              return true;
+            }
+          }
+          return false;
+        };
+        break;
+      case 'boolean':
+        return function (origin, compareValue){
+          return origin === compareValue;
+        };
+        break;
+      case 'string':
+      default:
+        return function(origin, compareValue){
+          var regex = new RegExp(compareValue,"i");
+          return regex.test(origin);
+        }
+    }
+  }
+
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
new file mode 100644
index 0000000..eae86c2
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/job_view.js
@@ -0,0 +1,19 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.JobView = Ember.View.extend({});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
new file mode 100644
index 0000000..26124b5
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/jobs_view.js
@@ -0,0 +1,305 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.JobsView = App.TableView.extend({
+
+  templateName: 'jobs',
+
+  content: [],
+
+
+  /**
+   * If no jobs table rows to show.
+   */
+  noDataToShow: true,
+
+  filterCondition:[],
+
+  /*
+   If no jobs to display set noDataToShow to true, else set emptyData to false.
+   */
+  noDataToShowObserver: function () {
+    if(this.get("controller.content.length") > 0){
+      this.set("noDataToShow",false);
+    }else{
+      this.set("noDataToShow",true);
+    }
+  }.observes("controller.content.length"),
+
+  willInsertElement: function () {
+    this._super();
+    this.clearFilters();
+    this.onApplyIdFilter();
+    this.set('tableFilteringComplete', true);
+  },
+
+  didInsertElement: function () {
+    if(!this.get('controller.sortingColumn')){
+      var columns = this.get('childViews')[0].get('childViews');
+      if(columns && columns.findProperty('name', 'startTime')){
+        columns.findProperty('name','startTime').set('status', 'sorting_desc');
+        this.get('controller').set('sortingColumn', columns.findProperty('name','startTime'))
+      }
+    }
+  },
+
+  onApplyIdFilter: function() {
+    var isIdFilterApplied = this.get('controller.filterObject.isIdFilterApplied');
+    this.get('childViews').forEach(function(childView) {
+      if (childView['clearFilter'] && childView.get('column') != 1) {
+        if(isIdFilterApplied){
+          childView.clearFilter();
+        }
+        var childOfChild = childView.get('childViews')[0];
+        if(childOfChild){
+          Em.run.next(function() {
+            childOfChild.set('disabled', isIdFilterApplied);
+          })
+        }
+      }
+    });
+  }.observes('controller.filterObject.isIdFilterApplied'),
+
+  saveFilter: function () {
+    if(this.get('tableFilteringComplete')){
+      this.updateFilter(1, this.get('controller.filterObject.id'), 'string');
+      this.updateFilter(2, this.get('controller.filterObject.user'), 'string');
+      this.updateFilter(4, this.get('controller.filterObject.windowEnd'), 'date');
+    }
+  }.observes(
+      'controller.filterObject.id',
+      'controller.filterObject.user',
+      'controller.filterObject.windowEnd'
+    ),
+
+  sortView: App.Sorts.wrapperView,
+
+  idSort: App.Sorts.fieldView.extend({
+    column: 1,
+    name: 'id',
+    displayName: Em.I18n.t('jobs.column.id'),
+    type: 'string'
+  }),
+
+  userSort: App.Sorts.fieldView.extend({
+    column: 2,
+    name: 'user',
+    displayName: Em.I18n.t('jobs.column.user'),
+    type: 'string'
+  }),
+
+  startTimeSort: App.Sorts.fieldView.extend({
+    column: 3,
+    name: 'startTime',
+    displayName: Em.I18n.t('jobs.column.start.time'),
+    type: 'number'
+  }),
+
+  endTimeSort: App.Sorts.fieldView.extend({
+    column: 4,
+    name: 'endTime',
+    displayName: Em.I18n.t('jobs.column.end.time'),
+    type: 'number'
+  }),
+
+  durationSort: App.Sorts.fieldView.extend({
+    column: 5,
+    name: 'duration',
+    displayName: Em.I18n.t('jobs.column.duration'),
+    type: 'number'
+  }),
+
+  /**
+   * Select View with list of "rows-per-page" options
+   * @type {Ember.View}
+   */
+  rowsPerPageSelectView: Ember.Select.extend({
+    content: ['10', '25', '50', '100', "250", "500"],
+    valueBinding: "controller.filterObject.jobsLimit",
+    attributeBindings: ['disabled'],
+    disabled: false,
+    disabledObserver: function () {
+      this.set('disabled', !!this.get("parentView.hasBackLinks"));
+    }.observes('parentView.hasBackLinks'),
+    change: function () {
+      this.get('controller').set('navIDs.nextID', '');
+    }
+  }),
+
+  /**
+   * return filtered number of all content number information displayed on the page footer bar
+   * @returns {String}
+   */
+  filteredJobs: function () {
+    return Em.I18n.t('jobs.filtered.jobs').fmt(this.get('controller.content.length'));
+  }.property('controller.content.length', 'controller.totalOfJobs'),
+
+  pageContentObserver: function () {
+    if (!this.get('controller.loading')) {
+      var tooltip = $('.tooltip');
+      if (tooltip.length) {
+        Ember.run.later(this, function() {
+          if (tooltip.length > 1) {
+            tooltip.first().remove();
+          }
+        }, 500);
+      }
+    }
+  }.observes('controller.loading'),
+
+  init: function() {
+    this._super();
+    App.tooltip($('body'), {
+      selector: '[rel="tooltip"]'
+    });
+  },
+
+  willDestroyElement : function() {
+    $('.tooltip').remove();
+  },
+
+  /**
+   * Filter-field for Jobs ID.
+   * Based on <code>filters</code> library
+   */
+  jobsIdFilterView: App.Filters.createTextView({
+    column: 1,
+    showApply: true,
+    setPropertyOnApply: 'controller.filterObject.id'
+  }),
+
+  /**
+   * Filter-list for User.
+   * Based on <code>filters</code> library
+   */
+  userFilterView: App.Filters.createTextView({
+    column: 2,
+    fieldType: 'input-small',
+    showApply: true,
+    setPropertyOnApply: 'controller.filterObject.user'
+  }),
+
+  /**
+   * Filter-field for Start Time.
+   * Based on <code>filters</code> library
+   */
+  startTimeFilterView: App.Filters.createSelectView({
+    fieldType: 'input-120',
+    column: 3,
+    content: ['Any', 'Past 1 hour',  'Past 1 Day', 'Past 2 Days', 'Past 7 Days', 'Past 14 Days', 'Past 30 Days', 'Custom'],
+    valueBinding: "controller.filterObject.startTime",
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'date');
+    }
+  }),
+
+  jobNameView: Em.View.extend({
+
+    isLink: 'is-not-link',
+
+    isLinkObserver: function () {
+      this.refreshLinks();
+    }.observes('controller.sortingDone'),
+
+    refreshLinks: function () {
+      this.set('isLink', this.get('job.hasTezDag') ? "" : "is-not-link");
+    },
+
+    templateName: 'jobs/jobs_name',
+
+    click: function(event) {
+      /*if (this.get('job.hasTezDag')) {
+        App.router.transitionTo('main.jobs.jobDetails', this.get('job'));
+      }*/
+      return false;
+    },
+
+    didInsertElement: function () {
+      this.refreshLinks();
+    }
+  }),
+
+  /**
+   * associations between content (jobs list) property and column index
+   */
+  colPropAssoc: function () {
+    var associations = [];
+    associations[1] = 'id';
+    associations[2] = 'user';
+    associations[3] = 'startTime';
+    associations[4] = 'endTime';
+    return associations;
+  }.property(),
+
+  clearFilters: function() {
+    this.get('childViews').forEach(function(childView) {
+      if (childView['clearFilter']) {
+        childView.clearFilter();
+      }
+    });
+  },
+
+  jobFailMessage: function() {
+    return Em.I18n.t('jobs.table.job.fail');
+  }.property(),
+
+  jobsPaginationLeft: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_left',
+    classNameBindings: ['class'],
+    class: function () {
+      if (this.get("parentView.hasBackLinks") && !this.get('controller.filterObject.isAnyFilterApplied')) {
+        return "paginate_previous";
+      }
+      return "paginate_disabled_previous";
+    }.property('parentView.hasBackLinks', 'controller.filterObject.isAnyFilterApplied'),
+
+    click: function () {
+      if (this.get("parentView.hasBackLinks") && !this.get('controller.filterObject.isAnyFilterApplied')) {
+        this.get('controller').navigateBack();
+      }
+    }
+  }),
+
+  jobsPaginationRight: Ember.View.extend({
+    tagName: 'a',
+    templateName: 'table/navigation/pagination_right',
+    classNameBindings: ['class'],
+    class: function () {
+      if (this.get("parentView.hasNextJobs") && !this.get('controller.filterObject.isAnyFilterApplied')) {
+        return "paginate_next";
+      }
+      return "paginate_disabled_next";
+    }.property("parentView.hasNextJobs", 'controller.filterObject.isAnyFilterApplied'),
+
+    click: function () {
+      if (this.get("parentView.hasNextJobs") && !this.get('controller.filterObject.isAnyFilterApplied')) {
+        this.get('controller').navigateNext();
+      }
+    }
+  }),
+
+  hasNextJobs: function() {
+    return (this.get("controller.navIDs.nextID.length") > 0);
+  }.property('controller.navIDs.nextID'),
+
+  hasBackLinks: function() {
+    return (this.get("controller.navIDs.backIDs").length > 1);
+  }.property('controller.navIDs.backIDs.[].length')
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/5659f0a2/contrib/views/jobs/src/main/resources/ui/app/scripts/views/sort_view.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/views/sort_view.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/sort_view.js
new file mode 100644
index 0000000..a8d5d39
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/views/sort_view.js
@@ -0,0 +1,253 @@
+/**
+ * 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.
+ */
+
+/**
+ * Wrapper View for all sort components. Layout template and common actions are located inside of it.
+ * Logic specific for sort fields
+ * located in inner view - <code>fieldView</code>.
+ *
+ * @type {*}
+ */
+var wrapperView = Em.View.extend({
+  tagName: 'tr',
+
+  classNames: ['sort-wrapper'],
+
+  willInsertElement: function () {
+    if (this.get('parentView.tableFilteringComplete')) {
+      this.get('parentView').set('filteringComplete', true);
+    }
+  },
+
+  /**
+   * Load sort statuses from local storage
+   * Works only after finish filtering in the parent View
+   */
+  loadSortStatuses: function () {
+
+  }.observes('parentView.filteringComplete'),
+
+  /**
+   * Save sort statuses to local storage
+   * Works only after finish filtering in the parent View
+   */
+  saveSortStatuses: function () {
+    if (!this.get('parentView.filteringComplete')) return;
+
+    var statuses = [];
+    this.get('childViews').forEach(function (childView) {
+      statuses.push({
+        name: childView.get('name'),
+        status: childView.get('status')
+      });
+    });
+  },
+
+  /**
+   * sort content by property
+   * @param property {object}
+   * @param order {Boolean} true - DESC, false - ASC
+   * @param returnSorted {Boolean}
+   */
+  sort: function (property, order, returnSorted) {
+    var content = this.get('content').toArray();
+    var sortFunc = this.getSortFunc(property, order);
+    var status = order ? 'sorting_desc' : 'sorting_asc';
+
+    this.resetSort();
+    this.get('childViews').findProperty('name', property.get('name')).set('status', status);
+    this.saveSortStatuses(property, order);
+    content.sort(sortFunc);
+
+    if (!!returnSorted) {
+      return content;
+    } else {
+      this.set('content', content);
+    }
+  },
+
+  isSorting: false,
+
+  onContentChange: function () {
+    if (!this.get('isSorting') && this.get('content.length')) {
+      this.get('childViews').forEach(function (view) {
+        if (view.status !== 'sorting') {
+          var status = view.get('status');
+          this.set('isSorting', true);
+          this.sort(view, status == 'sorting_desc');
+          this.set('isSorting', false);
+          view.set('status', status);
+        }
+      }, this);
+    }
+  }.observes('content.length'),
+
+  /**
+   * reset all sorts fields
+   */
+  resetSort: function () {
+    this.get('childViews').setEach('status', 'sorting');
+  },
+  /**
+   * determines sort function depending on the type of sort field
+   * @param property
+   * @param order
+   * @return {*}
+   */
+  getSortFunc: function (property, order) {
+    var func;
+    switch (property.get('type')) {
+      case 'ip':
+        func = function (a, b) {
+          a = App.Helpers.misc.ipToInt(a.get(property.get('name')));
+          b = App.Helpers.misc.ipToInt(b.get(property.get('name')));
+          return order ? (b - a) : (a - b);
+        };
+        break;
+      case 'number':
+        func = function (a, b) {
+          a = parseFloat(a.get(property.get('name')));
+          b = parseFloat(b.get(property.get('name')));
+          return order ? (b - a) : (a - b);
+        };
+        break;
+      default:
+        func = function (a, b) {
+          if (order) {
+            if (a.get(property.get('name')) > b.get(property.get('name')))
+              return -1;
+            if (a.get(property.get('name')) < b.get(property.get('name')))
+              return 1;
+            return 0;
+          } else {
+            if (a.get(property.get('name')) < b.get(property.get('name')))
+              return -1;
+            if (a.get(property.get('name')) > b.get(property.get('name')))
+              return 1;
+            return 0;
+          }
+        }
+    }
+    return func;
+  }
+});
+
+/**
+ * view that carry on sorting on server-side via <code>refresh()</code> in parentView
+ * @type {*}
+ */
+var serverWrapperView = Em.View.extend({
+  tagName: 'tr',
+
+  classNames: ['sort-wrapper'],
+
+  willInsertElement: function () {
+    this.loadSortStatuses();
+  },
+
+  /**
+   * Initialize and save sorting statuses: publicHostName sorting_asc
+   */
+  loadSortStatuses: function () {
+    var statuses = [];
+    var childViews = this.get('childViews');
+    childViews.forEach(function (childView) {
+      var sortStatus = (childView.get('name') == 'publicHostName' && childView.get('status') == 'sorting') ? 'sorting_asc' : childView.get('status');
+      statuses.push({
+        name: childView.get('name'),
+        status: sortStatus
+      });
+      childView.set('status', sortStatus);
+    });
+    this.get('controller').set('sortingColumn', childViews.findProperty('name', 'publicHostName'));
+  },
+
+  /**
+   * Save sort statuses to local storage
+   * Works only after finish filtering in the parent View
+   */
+  saveSortStatuses: function () {
+    var statuses = [];
+    this.get('childViews').forEach(function (childView) {
+      statuses.push({
+        name: childView.get('name'),
+        status: childView.get('status')
+      });
+    });
+  },
+
+  /**
+   * sort content by property
+   * @param property {object}
+   * @param order {Boolean} true - DESC, false - ASC
+   */
+  sort: function (property, order) {
+    var status = order ? 'sorting_desc' : 'sorting_asc';
+
+    this.resetSort();
+    this.get('childViews').findProperty('name', property.get('name')).set('status', status);
+    this.saveSortStatuses();
+    this.get('parentView').refresh();
+  },
+
+  /**
+   * reset all sorts fields
+   */
+  resetSort: function () {
+    this.get('childViews').setEach('status', 'sorting');
+  }
+});
+
+/**
+ * particular view that contain sort field properties:
+ * name - name of property in content table
+ * type(optional) - specific type to sort
+ * displayName - label to display
+ * @type {*}
+ */
+var fieldView = Em.View.extend({
+  templateName: 'sort_field_template',
+  classNameBindings: ['viewNameClass'],
+  tagName: 'th',
+  name: null,
+  displayName: null,
+  status: 'sorting',
+  viewNameClass: function () {
+    return 'sort-view-' + this.get('column');
+  }.property(),
+  type: null,
+  column: 0,
+  /**
+   * callback that run sorting and define order of sorting
+   * @param event
+   */
+  click: function (event) {
+    this.get('parentView').sort(this, (this.get('status') !== 'sorting_desc'));
+    this.get('controller').set('sortingColumn', this);
+  }
+});
+
+/**
+ * Result object, which will be accessible outside
+ * @type {Object}
+ */
+App.Sorts = {
+  serverWrapperView: serverWrapperView,
+  wrapperView: wrapperView,
+  fieldView: fieldView
+};


[3/3] git commit: Merge remote-tracking branch 'origin/trunk' into trunk

Posted by on...@apache.org.
Merge remote-tracking branch 'origin/trunk' into trunk


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/366bcbb6
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/366bcbb6
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/366bcbb6

Branch: refs/heads/trunk
Commit: 366bcbb6998a87dbd59bd4defabff266079c9461
Parents: 5659f0a 39a92eb
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Fri Jul 18 17:21:11 2014 +0300
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Fri Jul 18 17:21:11 2014 +0300

----------------------------------------------------------------------
 .../resources/AlertDefResourceDefinition.java   |  44 ++++
 .../resources/ClusterResourceDefinition.java    |   1 +
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../api/services/AlertDefinitionService.java    |  93 ++++++++
 .../server/api/services/AmbariMetaInfo.java     |  43 ++++
 .../server/api/services/ClusterService.java     |   9 +
 .../server/api/util/StackExtensionHelper.java   |  12 +-
 .../ambari/server/controller/AmbariServer.java  |   3 +
 .../AbstractControllerResourceProvider.java     |   2 +
 .../AlertDefinitionResourceProvider.java        | 178 ++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   6 +-
 .../server/orm/dao/AlertDefinitionDAO.java      |  27 ++-
 .../ambari/server/orm/dao/AlertDispatchDAO.java | 230 ++++++++++++++++++-
 .../apache/ambari/server/orm/dao/AlertsDAO.java | 138 +++++++++++
 .../server/orm/entities/AlertCurrentEntity.java |  27 +++
 .../orm/entities/AlertDefinitionEntity.java     |  78 ++++++-
 .../server/orm/entities/AlertGroupEntity.java   |  46 +++-
 .../server/orm/entities/AlertHistoryEntity.java |  36 ++-
 .../server/orm/entities/AlertNoticeEntity.java  |  29 +++
 .../server/orm/entities/AlertTargetEntity.java  |  38 ++-
 .../apache/ambari/server/state/ServiceInfo.java |  18 +-
 .../server/state/alert/AlertDefinition.java     | 117 ++++++++++
 .../ambari/server/state/alert/MetricAlert.java  |  57 +++++
 .../apache/ambari/server/state/alert/Scope.java |  33 +++
 .../ambari/server/state/alert/SourceType.java   |  40 ++++
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |   1 +
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |   1 +
 .../src/main/resources/key_properties.json      |   4 +
 .../src/main/resources/properties.json          |  11 +
 .../stacks/HDP/2.0.6/services/HDFS/alerts.json  |  59 +++++
 .../ClusterResourceDefinitionTest.java          |   3 +-
 .../server/api/services/AmbariMetaInfoTest.java |  22 ++
 .../AlertDefinitionResourceProviderTest.java    | 162 +++++++++++++
 .../server/orm/dao/AlertDefinitionDAOTest.java  |   6 +-
 .../stacks/HDP/2.0.5/services/HDFS/alerts.json  |  52 +++++
 35 files changed, 1594 insertions(+), 36 deletions(-)
----------------------------------------------------------------------