tez-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ss...@apache.org
Subject [18/35] tez git commit: TEZ-2236. Tez UI: Support loading of all tasks in the dag tasks page (Sreenath Somarajapuram via pramachandran)
Date Tue, 07 Apr 2015 20:12:36 GMT
TEZ-2236. Tez UI: Support loading of all tasks in the dag tasks page (Sreenath Somarajapuram via pramachandran)


Project: http://git-wip-us.apache.org/repos/asf/tez/repo
Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/9316fe62
Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/9316fe62
Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/9316fe62

Branch: refs/heads/TEZ-2003
Commit: 9316fe627a9153a5caf8c653e97300c7b4bb8095
Parents: 62a348c
Author: Prakash Ramachandran <pramachandran@hortonworks.com>
Authored: Wed Apr 8 01:06:50 2015 +0530
Committer: Prakash Ramachandran <pramachandran@hortonworks.com>
Committed: Wed Apr 8 01:06:50 2015 +0530

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 tez-ui/src/main/webapp/app/scripts/app.js       |   5 +-
 .../basic-table/basic-table-component.js        |  91 ++++
 .../scripts/components/basic-table/cell-view.js |  43 ++
 .../components/basic-table/column-definition.js |  46 ++
 .../components/basic-table/header-cell-view.js  |  52 +++
 .../components/basic-table/pagination-view.js   |  62 +++
 .../webapp/app/scripts/controllers/dag_tasks.js | 247 ++++-------
 .../controllers/table-page-controller.js        |  39 ++
 .../app/scripts/helpers/handlebarHelpers.js     |   8 +
 .../src/main/webapp/app/scripts/helpers/misc.js |  60 ++-
 .../webapp/app/scripts/helpers/misc.js.orig     | 436 +++++++++++++++++++
 .../scripts/mixins/data-array-loader-minxin.js  | 117 +++++
 .../src/main/webapp/app/scripts/models/dag.js   |   4 +
 tez-ui/src/main/webapp/app/scripts/router.js    |  15 +-
 .../scripts/views/extra-table-buttons-view.js   |  23 +
 tez-ui/src/main/webapp/app/styles/colors.less   |   7 +-
 tez-ui/src/main/webapp/app/styles/main.less     | 225 +++++++++-
 tez-ui/src/main/webapp/app/styles/shared.less   |  24 +
 .../main/webapp/app/templates/common/table.hbs  |  47 ++
 .../app/templates/components/basic-table.hbs    |  58 +++
 .../components/basic-table/basic-cell.hbs       |  19 +
 .../components/basic-table/header-cell.hbs      |  24 +
 .../components/basic-table/linked-cell.hbs      |  25 ++
 .../components/basic-table/logs-cell.hbs        |  35 ++
 .../components/basic-table/pagination-view.hbs  |  38 ++
 .../components/basic-table/status-cell.hbs      |  29 ++
 .../basic-table/task-actions-cell.hbs           |  26 ++
 .../app/templates/views/extra-table-buttons.hbs |  19 +
 29 files changed, 1641 insertions(+), 184 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 0eb02a7..f7295f2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,7 @@ INCOMPATIBLE CHANGES
   TEZ-1993. Implement a pluggable InputSizeEstimator for grouping fairly
 
 ALL CHANGES:
+  TEZ-2236. Tez UI: Support loading of all tasks in the dag tasks page
   TEZ-2159. Tez UI: download timeline data for offline use.
   TEZ-2269. DAGAppMaster becomes unresponsive (post TEZ-2149).
   TEZ-2243. documentation should explicitly specify protobuf 2.5.0.

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/app.js b/tez-ui/src/main/webapp/app/scripts/app.js
index 1a2af0e..1d8036d 100644
--- a/tez-ui/src/main/webapp/app/scripts/app.js
+++ b/tez-ui/src/main/webapp/app/scripts/app.js
@@ -183,8 +183,9 @@ require('scripts/router');
 require('scripts/views/**/*');
 require('scripts/models/**/*');
 
+require('scripts/controllers/table-page-controller');
 require('scripts/controllers/**/*');
 
-require('scripts/components/*');
-require('scripts/components/dag-view/*');
+require('scripts/components/basic-table/basic-table-component');
+require('scripts/components/**/*');
 require('scripts/adapters/*');

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/components/basic-table/basic-table-component.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/basic-table-component.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/basic-table-component.js
new file mode 100644
index 0000000..5a47c78
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/basic-table-component.js
@@ -0,0 +1,91 @@
+/**
+ * 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.BasicTableComponent = Em.Component.extend({
+  layoutName: 'components/basic-table',
+
+  statusMessage: null,
+  internalStatusMessage: null,
+
+  pageNum: 1,
+  rowCount: 10,
+  rowCountOptions: [5, 10, 25, 50, 100],
+  pageNavOnFooterAt: 25,
+
+  totalPages: function () {
+    return Math.ceil(this.get('rows.length') / this.get('rowCount'));
+  }.property('rows.length', 'rowCount'),
+
+  hasPageNavOnFooter: function () {
+    return this.get('enablePagination') && this.get('_rows.length') >= this.get('pageNavOnFooterAt');
+  }.property('enablePagination', '_rows.length', 'pageNavOnFooterAt'),
+
+  _showHeader: function () {
+    return this.get('enablePagination') ||
+        this.get('extraHeaderItem') ||
+        this.get('_statusMessage');
+  }.property('enablePagination', 'extraHeaderItem', '_statusMessage'),
+
+  _statusMessage: function() {
+    return this.get('internalStatusMessage') || this.get('statusMessage');
+  }.property('internalStatusMessage', 'statusMessage'),
+
+  _pageNumResetObserver: function () {
+    this.set('pageNum', 1);
+  }.observes('rowCount'),
+
+  _columns: function () {
+    var columns = this.get('columns'),
+        widthPercentageToFit = 100 / columns.length;
+
+      columns.map(function (column) {
+        var templateName = column.get('templateName'),
+            cellOptions = {
+              column: column
+            };
+
+        if(templateName) {
+          cellOptions.templateName = templateName;
+        }
+
+        column.setProperties({
+          width: widthPercentageToFit + "%",
+          cellView: App.BasicTableComponent.CellView.extend(cellOptions),
+          headerCellView: App.BasicTableComponent.HeaderCellView.extend({
+            column: column,
+            table: this
+          })
+        });
+      });
+
+    return columns;
+  }.property('columns'),
+
+  _rows: function () {
+    var startIndex = (this.get('pageNum') - 1) * this.get('rowCount');
+    return this.get('rows').slice(startIndex, startIndex + this.get('rowCount'));
+  }.property('rows.@each', 'rowCount', 'pageNum'),
+
+  actions: {
+    changePage: function (pageNum) {
+      this.set('pageNum', pageNum);
+    }
+  }
+});
+
+Em.Handlebars.helper('basic-table-component', App.BasicTableComponent);

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/components/basic-table/cell-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/cell-view.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/cell-view.js
new file mode 100644
index 0000000..4d50cf1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/cell-view.js
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin),
+    naCellContent = {
+      toString: function (){
+        return 'Not available!';
+      },
+      _notAvailable: true
+    };
+
+App.BasicTableComponent.CellView = Ember.View.extend({
+  templateName: 'components/basic-table/basic-cell',
+
+  classNames: ['cell-content'],
+
+  cellContent: function () {
+    var cellContent = this.get('column').getCellContent(this.get('row'));
+
+    if(cellContent && $.isFunction(cellContent.then)) {
+      cellContent = ObjectPromiseController.create({
+        promise: cellContent
+      });
+    }
+
+    return cellContent || naCellContent;
+  }.property('row', 'column')
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/components/basic-table/column-definition.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/column-definition.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/column-definition.js
new file mode 100644
index 0000000..fa0a47d
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/column-definition.js
@@ -0,0 +1,46 @@
+/**
+ * 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.BasicTableComponent.ColumnDefinition = (function () {
+  function getContentAtPath(row) {
+    var contentPath = this.get('contentPath');
+
+    if(contentPath) {
+      return row.get(contentPath);
+    }
+    else {
+      throw new Error("contentPath not set!");
+    }
+  }
+
+  return Em.Object.extend({
+    contentPath: null,
+    headerCellName: "Not Available!",
+
+    width: "",
+
+    customStyle: function () {
+      return 'width:%@'.fmt(this.get('width'));
+    }.property('width'),
+
+    getSearchValue: getContentAtPath,
+    getSortValue: getContentAtPath,
+    getCellContent: getContentAtPath
+  });
+})();
+

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/components/basic-table/header-cell-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/header-cell-view.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/header-cell-view.js
new file mode 100644
index 0000000..d6773d6
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/header-cell-view.js
@@ -0,0 +1,52 @@
+/**
+ * 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.BasicTableComponent.HeaderCellView = Ember.View.extend({
+  templateName: 'components/basic-table/header-cell',
+
+  _onColResize: function (event) {
+    var data = event.data;
+
+    if(!data.startEvent) {
+      data.startEvent = event;
+    }
+
+    data.thisHeader.set(
+      'column.width',
+      (data.startWidth + event.clientX - data.startEvent.clientX) + 'px'
+    );
+  },
+
+  _endColResize: function (event) {
+    var thisHeader = event.data.thisHeader;
+    $(document).off('mousemove', thisHeader._onColResize);
+    $(document).off('mouseup', thisHeader._endColResize);
+  },
+
+  actions: {
+    startColResize: function () {
+      var mouseTracker = {
+        thisHeader: this,
+        startWidth: $(this.get('element')).width(),
+        startEvent: null
+      };
+      $(document).on('mousemove', mouseTracker, this._onColResize);
+      $(document).on('mouseup', mouseTracker, this._endColResize);
+    }
+  }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/components/basic-table/pagination-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/pagination-view.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/pagination-view.js
new file mode 100644
index 0000000..4f136b1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/pagination-view.js
@@ -0,0 +1,62 @@
+/**
+ * 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.BasicTableComponent.PaginationView = Ember.View.extend({
+  templateName: 'components/basic-table/pagination-view',
+
+  classNames: ['pagination-view'],
+
+  atFirst: function () {
+    return this.get('pageNum') == 1;
+  }.property('pageNum'),
+
+  atLast: function () {
+    return this.get('pageNum') == this.get('totalPages');
+  }.property('pageNum', 'totalPages'),
+
+  _possiblePages: function () {
+    var pageNum = this.get('pageNum'),
+        totalPages = this.get('totalPages'),
+        possiblePages = [],
+        startPage = 1,
+        endPage = totalPages,
+        delta = 0;
+
+    if(totalPages > 5) {
+      startPage = pageNum - 2, endPage = pageNum + 2;
+
+      if(startPage < 1) {
+        delta = 1 - startPage;
+      }
+      else if(endPage > totalPages) {
+        delta = totalPages - endPage;
+      }
+
+      startPage += delta, endPage += delta;
+    }
+
+    while(startPage <= endPage) {
+      possiblePages.push({
+        isCurrent: startPage == pageNum,
+        pageNum: startPage++
+      });
+    }
+
+    return possiblePages;
+  }.property('pageNum', 'totalPages'),
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/controllers/dag_tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/dag_tasks.js b/tez-ui/src/main/webapp/app/scripts/controllers/dag_tasks.js
index 519f06d..267eac5 100644
--- a/tez-ui/src/main/webapp/app/scripts/controllers/dag_tasks.js
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/dag_tasks.js
@@ -16,185 +16,133 @@
  * limitations under the License.
  */
 
-App.DagTasksController = Em.ObjectController.extend(App.PaginatedContentMixin, App.ColumnSelectorMixin, {
-  needs: "dag",
+App.DagTasksController = App.TablePageController.extend({
 
   controllerName: 'DagTasksController',
+  needs: "dag",
 
-  // required by the PaginatedContentMixin
-  childEntityType: 'task',
+  entityType: 'task',
+  filterEntityType: 'dag',
+  filterEntityId: Ember.computed.alias('controllers.dag.id'),
 
-  queryParams: {
-    status_filter: 'status',
-    vertex_name_filter: 'vertex_name',
+  beforeLoad: function () {
+    var dagController = this.get('controllers.dag'),
+        model = dagController.get('model');
+    return model.reload().then(function () {
+      return dagController.loadAdditional(model);
+    });
   },
-  status_filter: null,
-  vertex_name_filter: null,
 
-  loadData: function() {
-    var primaryFilter = {
-      TEZ_DAG_ID : this.get('controllers.dag.id')
-    };
-    if (!!this.vertex_name_filter) {
-      var vertexIdMap = this.get('controllers.dag.vertexIdToNameMap');
-      var vertexId = App.Helpers.misc.getVertexIdFromName(vertexIdMap, this.vertex_name_filter)
-        || 'unknown';
-      primaryFilter = { TEZ_VERTEX_ID : vertexId };
-    }
+  afterLoad: function () {
+    var data = this.get('data'),
+        isUnsuccessfulDag = App.Helpers.misc.isStatusInUnsuccessful(
+          this.get('controllers.dag.status')
+        );
+
+    data.forEach(function (task) {
+      var taskStatus = App.Helpers.misc.getFixedupDisplayStatus(task.get('status'));
 
-    var filters = {
-      primary: primaryFilter,
-      secondary: {
-        status: this.status_filter
+      if (taskStatus == 'RUNNING' && isUnsuccessfulDag) {
+        taskStatus = 'KILLED'
       }
-    }
-    this.setFiltersAndLoadEntities(filters);
+      if (taskStatus != task.get('status')) {
+        task.set('status', taskStatus);
+      }
+    });
+
+    return this._super();
   },
 
-  load: function () {
-    var dag = this.get('controllers.dag.model'),
-        controller = this.get('controllers.dag'),
-        t = this;
-    t.set('loading', true);
-    dag.reload().then(function () {
-      return controller.loadAdditional(dag);
-    }).then(function () {
-      t.resetNavigation();
-      t.loadEntities();
-    }).catch(function(error){
-      Em.Logger.error(error);
-      var err = App.Helpers.misc.formatError(error, defaultErrMsg);
-      var msg = 'error code: %@, message: %@'.fmt(err.errCode, err.msg);
-      App.Helpers.ErrorBar.getInstance().show(msg, err.details);
-    });
-  }.observes('count'),
+  columns: function() {
+    var visibleColumnConfigs = this.get('columnConfigs').filter(function (column) {
+      return this.visibleColumnIds[column.id];
+    }, this);
+
+    return App.Helpers.misc.createColumnDescription(visibleColumnConfigs);
+  }.property('visibleColumnIds'),
 
-  loadEntities: function() {
+  defaultColumnConfigs: function() {
     var that = this,
-    store = this.get('store'),
-    fetcher;
-    childEntityType = this.get('childEntityType');
-    var defaultErrMsg = 'Error while loading tasks.';
+        vertexIdToNameMap = this.get('controllers.dag.vertexIdToNameMap') || {};
 
-    that.set('loading', true);
-    store.unloadAll(childEntityType);
-    store.findQuery(childEntityType, this.getFilterProperties())
-      .then(function(entities){
+    function getLogContent(attempt) {
+      var yarnAppState = that.get('controllers.dag.yarnAppState'),
+          suffix,
+          link,
+          cellContent = {};
 
-      var pivotLoaders = [];
-      var dagStatus = that.get('controllers.dag.status');
-      entities.forEach(function (task) {
-        var taskStatus = App.Helpers.misc
-          .getFixedupDisplayStatus(task.get('status'));
-        if (taskStatus == 'RUNNING' &&
-          App.Helpers.misc.isStatusInUnsuccessful(dagStatus)) {
-          taskStatus = 'KILLED'
-        }
-        if (taskStatus != task.get('status')) {
-          task.set('status', taskStatus);
+      if(attempt) {
+        suffix = "/syslog_" + attempt.get('id'),
+        link = attempt.get('inProgressLog') || attempt.get('completedLog');
+
+        if(link) {
+          cellContent.viewUrl = link + suffix;
         }
-        var taskAttemptId = task.get('successfulAttemptId') ||
-            task.get('attempts.lastObject');
-        if (!!taskAttemptId) {
-          // Pivot attempt selection logic
-          App.Helpers.misc.removeRecord(store, 'taskAttempt', taskAttemptId);
-          fetcher = store.find('taskAttempt', taskAttemptId);
-          fetcher.then(function (attempt) {
-            task.set('pivotAttempt', attempt);
-          });
-          pivotLoaders.push(fetcher);
+        link = attempt.get('completedLog');
+        if (link && yarnAppState === 'FINISHED' || yarnAppState === 'KILLED' || yarnAppState === 'FAILED') {
+          cellContent.downloadUrl = link + suffix;
         }
-      });
-      Em.RSVP.allSettled(pivotLoaders).then(function(){
-        that.set('entities', entities);
-        that.set('loading', false);
-      });
-    }).catch(function(error){
-      Em.Logger.error(error);
-      var err = App.Helpers.misc.formatError(error, defaultErrMsg);
-      var msg = 'error code: %@, message: %@'.fmt(err.errCode, err.msg);
-      App.Helpers.ErrorBar.getInstance().show(msg, error.details);
-    });
-  },
-
-  actions : {
-    filterUpdated: function(filterID, value) {
-      // any validations required goes here.
-      if (!!value) {
-        this.set(filterID, value);
-      } else {
-        this.set(filterID, null);
       }
-      this.loadData();
+
+      cellContent.notAvailable = cellContent.viewUrl || cellContent.downloadUrl;
+
+      return cellContent;
     }
-  },
 
-  defaultColumnConfigs: function() {
-    var that = this,
-        vertexIdToNameMap = this.get('controllers.dag.vertexIdToNameMap') || {};
     return [
       {
         id: 'id',
         headerCellName: 'Task Index',
-        tableCellViewClass: Em.Table.TableCell.extend({
-          template: Em.Handlebars.compile(
-            "{{#link-to 'task' view.cellContent.id class='ember-table-content'}}{{view.cellContent.displayId}}{{/link-to}}")
-        }),
+        templateName: 'components/basic-table/linked-cell',
+        contentPath: 'id',
         getCellContent: function (row) {
           var id = row.get('id'),
               idPrefix = 'task_%@_'.fmt(row.get('dagID').substr(4));
           return {
-            id: id,
-            displayId: id.indexOf(idPrefix) == 0 ? id.substr(idPrefix.length) : id
+            linkTo: 'task',
+            entityId: id,
+            displayText: id.indexOf(idPrefix) == 0 ? id.substr(idPrefix.length) : id
           };
-        }
+        },
       },
       {
         id: 'vertexName',
         headerCellName: 'Vertex Name',
-        filterID: 'vertex_name_filter',
+        contentPath: 'vertexID',
         getCellContent: function(row) {
           var vertexId = row.get('vertexID');
           return vertexIdToNameMap[vertexId] || vertexId;
-        }
+        },
       },
       {
         id: 'startTime',
         headerCellName: 'Start Time',
+        contentPath: 'startTime',
         getCellContent: function(row) {
           return App.Helpers.date.dateFormat(row.get('startTime'));
-        }
+        },
       },
       {
         id: 'endTime',
         headerCellName: 'End Time',
+        contentPath: 'endTime',
         getCellContent: function(row) {
           return App.Helpers.date.dateFormat(row.get('endTime'));
-        }
+        },
       },
       {
         id: 'duration',
         headerCellName: 'Duration',
+        contentPath: 'duration',
         getCellContent: function(row) {
-          var st = row.get('startTime');
-          var et = row.get('endTime');
-          if (st && et) {
-            return App.Helpers.date.durationSummary(st, et);
-          }
-        }
+          return App.Helpers.date.timingFormat(row.get('duration'), 1);
+        },
       },
       {
         id: 'status',
         headerCellName: 'Status',
-        filterID: 'status_filter',
-        filterType: 'dropdown',
-        dropdownValues: App.Helpers.misc.taskStatusUIOptions,
-        tableCellViewClass: Em.Table.TableCell.extend({
-          template: Em.Handlebars.compile(
-            '<span class="ember-table-content">&nbsp;\
-            <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>\
-            &nbsp;&nbsp;{{view.cellContent.status}}</span>')
-        }),
+        templateName: 'components/basic-table/status-cell',
+        contentPath: 'status',
         getCellContent: function(row) {
           var status = row.get('status');
           return {
@@ -206,59 +154,20 @@ App.DagTasksController = Em.ObjectController.extend(App.PaginatedContentMixin, A
       {
         id: 'actions',
         headerCellName: 'Actions',
-        tableCellViewClass: Em.Table.TableCell.extend({
-          template: Em.Handlebars.compile(
-            '<span class="ember-table-content">\
-            {{#link-to "task.counters" view.cellContent}}counters{{/link-to}}&nbsp;\
-            {{#link-to "task.attempts" view.cellContent}}attempts{{/link-to}}\
-            </span>'
-            )
-        }),
-        contentPath: 'id'
+        templateName: 'components/basic-table/task-actions-cell',
+        contentPath: 'id',
       },
       {
         id: 'logs',
-        textAlign: 'text-align-left',
         headerCellName: 'Logs',
-        tableCellViewClass: Em.Table.TableCell.extend({
-          template: Em.Handlebars.compile(
-            '<span class="ember-table-content">\
-              {{#unless view.cellContent.notAvailable}}\
-                Not Available\
-              {{else}}\
-                {{#if view.cellContent.viewUrl}}\
-                  <a target="_blank" href="//{{unbound view.cellContent.viewUrl}}">View</a>\
-                  &nbsp;\
-                {{/if}}\
-                {{#if view.cellContent.downloadUrl}}\
-                  <a target="_blank" href="{{unbound view.cellContent.downloadUrl}}?start=0" download type="application/octet-stream">Download</a>\
-                {{/if}}\
-              {{/unless}}\
-            </span>')
-        }),
+        templateName: 'components/basic-table/logs-cell',
         getCellContent: function(row) {
-          var yarnAppState = that.get('controllers.dag.yarnAppState'),
-              attempt = row.get('pivotAttempt'),
-              suffix,
-              link,
-              cellContent = {};
-
-          if(attempt) {
-            suffix = "/syslog_" + attempt.get('id'),
-            link = attempt.get('inProgressLog') || attempt.get('completedLog');
+          var taskAttemptId = row.get('successfulAttemptId') || row.get('attempts.lastObject'),
+              store = that.get('store');
 
-            if(link) {
-              cellContent.viewUrl = link + suffix;
-            }
-            link = attempt.get('completedLog');
-            if (link && yarnAppState === 'FINISHED' || yarnAppState === 'KILLED' || yarnAppState === 'FAILED') {
-              cellContent.downloadUrl = link + suffix;
-            }
+          if (taskAttemptId) {
+            return store.find('taskAttempt', taskAttemptId).then(getLogContent);
           }
-
-          cellContent.notAvailable = cellContent.viewUrl || cellContent.downloadUrl;
-
-          return cellContent;
         }
       }
     ];

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js
new file mode 100644
index 0000000..002c6b5
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js
@@ -0,0 +1,39 @@
+/**
+ * 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.TablePageController = Em.ObjectController.extend(
+    App.DataArrayLoaderMixin,
+    App.ColumnSelectorMixin, {
+      queryParams: ['pageNum', 'rowCount'],
+
+      pageNum: 1,
+      rowCount: 25,
+
+      isRefreshable: true,
+
+      statusMessage: function () {
+        return this.get('loading') ? "Loading all records..." : null;
+      }.property('loading'),
+
+      actions: {
+        refresh: function () {
+          this.loadData(true);
+        }
+      }
+    }
+);

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js b/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
index 0cd6b9d..5fe475f 100644
--- a/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/handlebarHelpers.js
@@ -72,3 +72,11 @@ Em.Handlebars.helper('formatDiagnostics', function(diagnostics) {
   x = replaceAll(x, ']', '</div>');
   return new Handlebars.SafeString(x);
 });
+
+/**
+ * Returns first-item class if called from inside a loop/each.
+ * @param view Will be _view in hbs
+ */
+Em.Handlebars.helper('firstItemCSS', function(view) {
+  return view && view.contentIndex == 0 ? 'first-item' : '';
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
index fdd69bd..27275dd 100644
--- a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
@@ -115,7 +115,10 @@ App.Helpers.misc = {
       configuration.headerCellName = configuration.counterName || configuration.counterId;
       configuration.id = '%@/%@'.fmt(configuration.counterGroupName || configuration.groupId,
           configuration.counterName || configuration.counterId),
-      configuration.getCellContent = App.Helpers.misc.getCounterCellContent;
+
+      configuration.getSortValue = App.Helpers.misc.getCounterCellContent;
+      configuration.getCellContent =
+          configuration.getSearchValue = App.Helpers.misc.getCounterCellContentFormatted;
       return configuration;
     });
   },
@@ -138,6 +141,12 @@ App.Helpers.misc = {
     });
   },
 
+  createColumnDescription: function (columnConfigs) {
+    return columnConfigs.map(function (column) {
+      return App.BasicTableComponent.ColumnDefinition.create(column);
+    });
+  },
+
   /*
    * Returns a counter value from for a row
    * @param row
@@ -145,19 +154,26 @@ App.Helpers.misc = {
    */
   getCounterCellContent: function (row) {
     var contentPath = this.id.split('/'),
-        group = contentPath[0],
-        counter = contentPath[1],
-        id = row.get('id'),
-        value = 'Not Available';
+        value = null;
 
     try{
       value = row.get('counterGroups').
-          findBy('id', '%@/%@'.fmt(id, group)).
-          get('counters').
-          findBy('id', '%@/%@/%@'.fmt(id, group, counter)).
-          get('value');
+          findBy('counterGroupName', contentPath[0])
+          ['counters'].
+          findBy('counterName', contentPath[1])
+          ['counterValue'];
     }catch(e){}
 
+    return value;
+  },
+
+  /*
+   * Returns a counter value from for a row
+   * @param row
+   * @return value
+   */
+  getCounterCellContentFormatted: function (row) {
+    var value = App.Helpers.misc.getCounterCellContent.call(this, row);
     return App.Helpers.number.formatNumThousands(value);
   },
 
@@ -393,6 +409,32 @@ App.Helpers.misc = {
     }
   },
 
+  timelinePathForType: (function () {
+    var typeToPathMap = {
+      dag: 'TEZ_DAG_ID',
+      vertex: 'TEZ_VERTEX_ID',
+      task: 'TEZ_TASK_ID',
+      taskAttempt: 'TEZ_TASK_ATTEMPT_ID',
+      tezApp: 'TEZ_APPLICATION'
+    };
+    return function (type) {
+      return typeToPathMap[type];
+    };
+  })(),
+
+  getTimelineFilterForType: (function () {
+    var typeToPathMap = {
+      dag: 'TEZ_DAG_ID',
+      vertex: 'TEZ_VERTEX_ID',
+      task: 'TEZ_TASK_ID',
+      taskAttempt: 'TEZ_TASK_ATTEMPT_ID',
+      tezApp: 'applicationId'
+    };
+    return function (type) {
+      return typeToPathMap[type];
+    };
+  })(),
+
   dagStatusUIOptions: [
     { label: 'All', id: null },
     { label: 'Submitted', id: 'SUBMITTED' },

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/helpers/misc.js.orig
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js.orig b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js.orig
new file mode 100644
index 0000000..fdd69bd
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js.orig
@@ -0,0 +1,436 @@
+/**
+ * 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.Helpers.misc = {
+  getStatusClassForEntity: function(status, hasFailedTasks) {
+    if(!status) return '';
+
+    switch(status) {
+      case 'FAILED':
+        return 'failed';
+      case 'KILLED':
+        return 'killed';
+      case 'RUNNING':
+        return 'running';
+      case 'ERROR':
+        return 'error';
+      case 'SUCCEEDED':
+        if (!!hasFailedTasks) {
+          return 'warning';
+        }
+        /*
+        TODO: TEZ-2113
+        var counterGroups = dag.get('counterGroups');
+        var numFailedTasks = this.getCounterValueForDag(counterGroups,
+          dag.get('id'), 'org.apache.tez.common.counters.DAGCounter',
+          'NUM_FAILED_TASKS'
+        ); 
+
+        if (numFailedTasks > 0) {
+          return 'warning';
+        }*/
+
+        return 'success';
+      case 'UNDEFINED':
+        return 'unknown';
+      default:
+        return 'submitted';
+    }
+  },
+
+  getRealStatus: function(entityState, yarnAppState, yarnAppFinalState) {
+    if (entityState != 'RUNNING' || (yarnAppState != 'FINISHED' && yarnAppState != 'KILLED' && yarnAppState != 'FAILED')) {
+      return entityState;
+    }
+
+    if (yarnAppState == 'KILLED' || yarnAppState == 'FAILED') {
+      return yarnAppState;
+    }
+
+    return yarnAppFinalState;
+  },
+
+	getCounterValueForDag: function(counterGroups, dagID, counterGroupName, counterName) {
+		if (!counterGroups) {
+			return 0;
+		}
+
+		var cgName = dagID + '/' + counterGroupName;
+		var cg = 	counterGroups.findBy('id', cgName);
+		if (!cg) {
+			return 0;
+		}
+		var counters = cg.get('counters');
+		if (!counters) {
+			return 0;
+		}
+		
+		var counter = counters.findBy('id', cgName + '/' + counterName);
+		if (!counter) return 0;
+
+		return counter.get('value');
+	},
+
+  isValidDagStatus: function(status) {
+    return $.inArray(status, ['SUBMITTED', 'INITING', 'RUNNING', 'SUCCEEDED',
+      'KILLED', 'FAILED', 'ERROR']) != -1;
+  },
+
+  isValidTaskStatus: function(status) {
+    return $.inArray(status, ['RUNNING', 'SUCCEEDED', 'FAILED', 'KILLED']) != -1;
+  },
+
+  isStatusInUnsuccessful: function(status) {
+    return $.inArray(status, ['FAILED', 'KILLED', 'UNDEFINED']) != -1;
+  },
+
+  /**
+   * To trim a complete class path with namespace to the class name.
+   */
+  getClassName: function (classPath) {
+    return classPath.substr(classPath.lastIndexOf('.') + 1);
+  },
+
+  /*
+   * Normalizes counter style configurations
+   * @param counterConfigs Array
+   * @return Normalized configurations
+   */
+  normalizeCounterConfigs: function (counterConfigs) {
+    return counterConfigs.map(function (configuration) {
+      configuration.headerCellName = configuration.counterName || configuration.counterId;
+      configuration.id = '%@/%@'.fmt(configuration.counterGroupName || configuration.groupId,
+          configuration.counterName || configuration.counterId),
+      configuration.getCellContent = App.Helpers.misc.getCounterCellContent;
+      return configuration;
+    });
+  },
+
+  /*
+   * Creates column definitions form configuration object array
+   * @param columnConfigs Array
+   * @return columnDefinitions Array
+   */
+  createColumnsFromConfigs: function (columnConfigs) {
+    return columnConfigs.map(function (columnConfig) {
+      if(columnConfig.getCellContentHelper) {
+        columnConfig.getCellContent = App.Helpers.get(columnConfig.getCellContentHelper);
+      }
+      columnConfig.minWidth = columnConfig.minWidth || 135;
+
+      return columnConfig.filterID ?
+          App.ExTable.ColumnDefinition.createWithMixins(App.ExTable.FilterColumnMixin, columnConfig) :
+          App.ExTable.ColumnDefinition.create(columnConfig);
+    });
+  },
+
+  /*
+   * Returns a counter value from for a row
+   * @param row
+   * @return value
+   */
+  getCounterCellContent: function (row) {
+    var contentPath = this.id.split('/'),
+        group = contentPath[0],
+        counter = contentPath[1],
+        id = row.get('id'),
+        value = 'Not Available';
+
+    try{
+      value = row.get('counterGroups').
+          findBy('id', '%@/%@'.fmt(id, group)).
+          get('counters').
+          findBy('id', '%@/%@/%@'.fmt(id, group, counter)).
+          get('value');
+    }catch(e){}
+
+    return App.Helpers.number.formatNumThousands(value);
+  },
+
+  /* 
+   * returns a formatted message, the real cause is unknown and the error object details
+   * depends on the error cause. the function tries to handle ajax error or a native errors
+   */
+  formatError: function(error, defaultErrorMessage) {
+    var msg;
+    // for cross domain requests, the error is not set if no access control headers were found.
+    // this could be either because there was a n/w error or the cors headers being not set.
+    if (error.status === 0 && error.statusText === 'error') {
+      msg = defaultErrorMessage ;
+    } else {
+      msg = error.statusText || error.message;
+    }
+    msg = msg || 'Unknown error';
+    if (!!error.responseText) {
+      msg += error.responseText;
+    }
+
+    if(error.requestOptions) {
+      msg = '%@<br/>Could not retrieve expected data from %@ @ %@'.fmt(
+        msg,
+        error.requestOptions.targetServer,
+        error.requestOptions.url
+      )
+    }
+
+    return {
+      errCode: error.status || 'Unknown', 
+      msg: msg,
+      details: error.stack
+    };
+  },
+
+  /**
+   * Normalize path
+   * @param path {String}
+   * @return normalized path {String}
+   */
+  normalizePath: function (path) {
+    if(path && path.charAt(path.length - 1) == '/') {
+      path = path.slice(0, -1);
+    }
+    return path;
+  },
+
+  // Tez originally shows the status for task and task attempt only on
+  // completion. this causes confusion to the user as the status would not be
+  // displayed. so if status is not set return the status as 'RUNNING'. We do
+  // not diffentiate between running and scheduled.
+  getFixedupDisplayStatus: function(originalStatus) {
+    // if status is not set show it as running, since originally the task did
+    // not have a status set on scheduled/running.
+    // with the new version we set the status of task as scheduled and that of
+    // task attempt as running
+    if (!originalStatus || originalStatus == 'SCHEDULED') {
+      originalStatus = 'RUNNING';
+    }
+    return originalStatus;
+  },
+
+  /**
+   * Merge content of obj2 into obj2, array elements will be concated.
+   * @param obj1 {Object}
+   * @param obj2 {Object}
+   */
+  merge: function objectMerge(obj1, obj2) {
+    $.each(obj2, function (key, val) {
+      if(Array.isArray(obj1[key]) && Array.isArray(val)) {
+        $.merge(obj1[key], val);
+      }
+      else if($.isPlainObject(obj1[key]) && $.isPlainObject(val)) {
+        objectMerge(obj1[key], val);
+      }
+      else {
+        obj1[key] = val;
+      }
+    });
+  },
+
+  getTaskIndex: function(dagID, taskID) {
+    var idPrefix = 'task_%@_'.fmt(dagID.substr(4));
+    return taskID.indexOf(idPrefix) == 0 ? taskID.substr(idPrefix.length) : id;
+  },
+
+  getVertexIdFromName: function(idToNameMap, vertexName) {
+    idToNameMap = idToNameMap || {};
+    var vertexId = undefined;
+    $.each(idToNameMap, function(id, name) {
+      if (name === vertexName) {
+        vertexId = id;
+        return false;
+      }
+    });
+    return vertexId;
+  },
+
+  /**
+   * Remove the specific record from store
+   * @param store {DS.Store}
+   * @param type {String}
+   * @param id {String}
+   */
+  removeRecord: function (store, type, id) {
+    var record = store.getById(type, id);
+    if(record) {
+      store.unloadRecord(record);
+    }
+  },
+
+  downloadDAG: function(dagID, options) {
+    var opts = options || {},
+        batchSize = opts.batchSize || 1000,
+        baseurl = '%@/%@'.fmt(App.env.timelineBaseUrl, App.Configs.restNamespace.timeline),
+        itemsToDownload = [
+          {
+            url: getUrl('TEZ_DAG_ID', dagID),
+            context: { name: 'dag', type: 'TEZ_DAG_ID' },
+            onItemFetched: processSingleItem
+          },
+          {
+            url: getUrl('TEZ_VERTEX_ID', dagID),
+            context: { name: 'vertices', type: 'TEZ_VERTEX_ID', part: 0 },
+            onItemFetched: processMultipleItems
+          },
+          {
+            url: getUrl('TEZ_TASK_ID', dagID),
+            context: { name: 'tasks', type: 'TEZ_TASK_ID', part: 0 },
+            onItemFetched: processMultipleItems
+          },
+          {
+            url: getUrl('TEZ_TASK_ATTEMPT_ID', dagID),
+            context: { name: 'task_attempts', type: 'TEZ_TASK_ATTEMPT_ID', part: 0 },
+            onItemFetched: processMultipleItems
+          }
+        ],
+        numItemTypesToDownload = itemsToDownload.length,
+        downloader = App.Helpers.io.fileDownloader(),
+        zipHelper = App.Helpers.io.zipHelper({
+          onProgress: function(filename, current, total) {
+            Em.Logger.debug('%@: %@ of %@'.fmt(filename, current, total));
+          },
+          onAdd: function(filename) {
+            Em.Logger.debug('adding %@ to Zip'.fmt(filename));
+          }
+        });
+
+    function getUrl(type, dagID, fromID) {
+      var url;
+      if (type == 'TEZ_DAG_ID') {
+        url = '%@/%@/%@'.fmt(baseurl, type, dagID);
+      } else {
+        url = '%@/%@?primaryFilter=TEZ_DAG_ID:%@&limit=%@'.fmt(baseurl, type, dagID, batchSize + 1);
+        if (!!fromID) {
+          url = '%@&fromId=%@'.fmt(url, fromID);
+        }
+      }
+      return url;
+    }
+
+    function checkIfAllDownloaded() {
+      numItemTypesToDownload--;
+      if (numItemTypesToDownload == 0) {
+        downloader.finish();
+      }
+    }
+
+    function processSingleItem(data, context) {
+      var obj = {};
+      obj[context.name] = data;
+
+      zipHelper.addFile({name: '%@.json'.fmt(context.name), data: JSON.stringify(obj, null, 2)});
+      checkIfAllDownloaded();
+    }
+
+    function processMultipleItems(data, context) {
+      var obj = {};
+      var nextBatchStart = undefined;
+
+      if (!$.isArray(data.entities)) {
+        throw "invalid data";
+      }
+
+      // need to handle no more entries , zero entries
+      if (data.entities.length > batchSize) {
+        nextBatchStart = data.entities.pop().entity;
+      }
+      obj[context.name] = data.entities;
+
+      zipHelper.addFile({name: '%@_part_%@.json'.fmt(context.name, context.part), data: JSON.stringify(obj, null, 2)});
+
+      if (!!nextBatchStart) {
+        context.part++;
+        downloader.queueItem({
+          url: getUrl(context.type, dagID, nextBatchStart),
+          context: context,
+          onItemFetched: processMultipleItems
+        });
+      } else {
+        checkIfAllDownloaded();
+      }
+    }
+
+    downloader.queueItems(itemsToDownload);
+
+    downloader.then(function() {
+      Em.Logger.info('Finished download');
+      zipHelper.close();
+    }).catch(function() {
+      Em.Logger.error('Failed to download');
+      zipHelper.abort();
+    });
+
+    var that = this;
+    zipHelper.then(function(zippedBlob) {
+      saveAs(zippedBlob, '%@.zip'.fmt(dagID));
+      if ($.isFunction(opts.onSuccess)) {
+        opts.onSuccess();
+      }
+    }).catch(function() {
+      Em.Logger.error('zip Failed');
+      if ($.isFunction(opts.onFailure)) {
+        opts.onFailure();
+      }
+    });
+
+    return {
+      cancel: function() {
+        downloader.cancel();
+      }
+    }
+  },
+
+  dagStatusUIOptions: [
+    { label: 'All', id: null },
+    { label: 'Submitted', id: 'SUBMITTED' },
+    { label: 'Running', id: 'RUNNING' },
+    { label: 'Succeeded', id: 'SUCCEEDED' },
+    { label: 'Failed', id: 'FAILED' },
+    { label: 'Killed', id: 'KILLED' },
+    { label: 'Error', id: 'ERROR' },
+  ],
+
+  vertexStatusUIOptions: [
+    { label: 'All', id: null },
+    { label: 'Running', id: 'RUNNING' },
+    { label: 'Succeeded', id: 'SUCCEEDED' },
+    { label: 'Failed', id: 'FAILED' },
+    { label: 'Killed', id: 'KILLED' },
+    { label: 'Error', id: 'ERROR' },
+  ],
+
+  taskStatusUIOptions: [
+    { label: 'All', id: null },
+    { label: 'Running', id: 'SCHEDULED' },
+    { label: 'Succeeded', id: 'SUCCEEDED' },
+    { label: 'Failed', id: 'FAILED' },
+    { label: 'Killed', id: 'KILLED' },
+  ],
+
+  taskAttemptStatusUIOptions: [
+    { label: 'All', id: null },
+    { label: 'Running', id: 'RUNNING' },
+    { label: 'Succeeded', id: 'SUCCEEDED' },
+    { label: 'Failed', id: 'FAILED' },
+    { label: 'Killed', id: 'KILLED' },
+  ],
+
+  defaultQueryParamsConfig: {
+    refreshModel: true,
+    replace: true
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/mixins/data-array-loader-minxin.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/data-array-loader-minxin.js b/tez-ui/src/main/webapp/app/scripts/mixins/data-array-loader-minxin.js
new file mode 100644
index 0000000..977143c
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/data-array-loader-minxin.js
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var dataCache = {};
+
+App.DataArrayLoaderMixin = Em.Mixin.create({
+  data: [],
+  loading: false,
+
+  entityType: null,
+  filterEntityType: null,
+  filterEntityId: null,
+
+  isRefreshable: true,
+
+  _cacheKey: function () {
+    return [
+      this.get('filterEntityType'),
+      this.get('filterEntityId'),
+      this.get('entityType'),
+    ].join(':');
+  }.property('filterEntityType', 'filterEntityId', 'entityType'),
+
+  getFilter: function (limit) {
+    return {
+      limit: limit || Number.MAX_SAFE_INTEGER - 1,
+      primaryFilter: '%@:%@'.fmt(
+        App.Helpers.misc.getTimelineFilterForType(this.get('filterEntityType')),
+        this.get('filterEntityId')
+      )
+    };
+  },
+
+  loadData: function (skipCache) {
+    var data;
+
+    if(this.get('loading')) {
+      return false;
+    }
+
+    if(!skipCache) {
+      data = dataCache[this.get('_cacheKey')];
+    }
+
+    if(data && data.get('content.length')) {
+      this.set('data', data);
+    }
+    else {
+      this.loadAllData();
+    }
+
+    return true;
+  },
+
+  loadAllData: function () {
+    this.set('loading', true);
+
+    // Load all rows
+    return this.beforeLoad().
+      then(this.load.bind(this, this.getFilter())).
+      then(this.afterLoad.bind(this)).
+      then(this.cacheData.bind(this)).
+      then(this.set.bind(this, 'loading', false)).
+      catch(this.errorHandler.bind(this));
+  },
+
+  beforeLoad: function () {
+    return new Em.RSVP.resolve();
+  },
+
+  load: function (filter) {
+    var entityType = this.get('entityType'),
+        store = this.get('store'),
+        data = dataCache[this.get('_cacheKey')];
+
+    if(data) {
+      data.toArray().forEach(function (record) {
+        record.unloadRecord();
+      });
+      dataCache[this.get('_cacheKey')] = null;
+    }
+
+    return store.findQuery(entityType, filter).
+      then(this.set.bind(this, 'data')).
+      catch(this.errorHandler.bind(this));
+  },
+
+  cacheData: function () {
+    dataCache[this.get('_cacheKey')] = this.get('data');
+  },
+
+  errorHandler: function (error) {
+    Em.Logger.error(error);
+    var err = App.Helpers.misc.formatError(error, 'Error while loading ' + this.get('entityType'));
+    var msg = 'Error code: %@, message: %@'.fmt(err.errCode, err.msg);
+    App.Helpers.ErrorBar.getInstance().show(msg, err.details);
+  },
+
+  afterLoad: function () {
+    return new Em.RSVP.resolve();
+  },
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/models/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/models/dag.js b/tez-ui/src/main/webapp/app/scripts/models/dag.js
index b86578c..05e356b 100644
--- a/tez-ui/src/main/webapp/app/scripts/models/dag.js
+++ b/tez-ui/src/main/webapp/app/scripts/models/dag.js
@@ -333,6 +333,10 @@ App.Task = App.AbstractEntity.extend({
 
   endTime: DS.attr('number'),
 
+  duration: function () {
+    return App.Helpers.date.duration(this.get('startTime'), this.get('endTime'))
+  }.property('startTime', 'endTime'),
+
   diagnostics: DS.attr('string'),
 
   numAttempts: DS.attr('number'),

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/router.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/router.js b/tez-ui/src/main/webapp/app/scripts/router.js
index 50d47fd..6de5a5d 100644
--- a/tez-ui/src/main/webapp/app/scripts/router.js
+++ b/tez-ui/src/main/webapp/app/scripts/router.js
@@ -285,8 +285,19 @@ App.TezAppConfigsRoute = Em.Route.extend({
 
 /* --- Shared routes --- */
 
-App.DagTasksRoute =
-    App.DagVerticesRoute =
+App.DagTasksRoute = Em.Route.extend({
+  renderTemplate: function () {
+    this.render('common/table');
+  },
+  setupController: function (controller, model) {
+    this._super(controller, model);
+    if(controller.loadData) {
+      controller.loadData();
+    }
+  }
+});
+
+App.DagVerticesRoute =
     App.DagTaskAttemptsRoute =
     App.VertexTasksRoute =
     App.VertexTaskAttemptsRoute =

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/scripts/views/extra-table-buttons-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/views/extra-table-buttons-view.js b/tez-ui/src/main/webapp/app/scripts/views/extra-table-buttons-view.js
new file mode 100644
index 0000000..422a755
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/views/extra-table-buttons-view.js
@@ -0,0 +1,23 @@
+/**
+ * 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.ExtraTableButtonsView = Ember.View.extend({
+  templateName: 'views/extra-table-buttons',
+
+  classNames: ['extra-table-buttons']
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/styles/colors.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/colors.less b/tez-ui/src/main/webapp/app/styles/colors.less
index 0fee7c0..9bf4b61 100644
--- a/tez-ui/src/main/webapp/app/styles/colors.less
+++ b/tez-ui/src/main/webapp/app/styles/colors.less
@@ -18,11 +18,16 @@
 
 // Colors
 @logo-orange: #D27A22;
+
 @bg-lite: #f5f5f5;
 @bg-liter: #f5f5f5;
-@border-lite: #e5e5e5;
 @bg-red-light: #FFE6E6;
 
+@bg-grey: #f0f0f0;
+
+@border-lite: #e5e5e5;
+@border-color: #dcdcdc;
+
 @white: #fff;
 
 @text-color: #666666;

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/styles/main.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/main.less b/tez-ui/src/main/webapp/app/styles/main.less
index c800ab2..870e2ec 100644
--- a/tez-ui/src/main/webapp/app/styles/main.less
+++ b/tez-ui/src/main/webapp/app/styles/main.less
@@ -30,6 +30,9 @@ body, html, body > .ember-view {
   height: 100%;
   overflow: visible;
 }
+body, html {
+  min-width: 1024px;
+}
 
 // Styles used in both standalone and wrapped modes
 .standalone, .wrapped {
@@ -96,10 +99,15 @@ body, html, body > .ember-view {
 .standalone {
   height: 100%;
 
-  a {
+  a, .pagination > li > a, .btn-default, .clickable {
     color: @logo-orange;
   }
 
+  .is-current {
+    color: white;
+    background-color: @logo-orange;
+  }
+
   #top-nav {
     a.logo {
       img {
@@ -159,6 +167,11 @@ body, html, body > .ember-view {
   #top-nav, .footer, .push {
     display: none;
   }
+
+  .is-current {
+    color: white;
+    background-color: @brand-primary;
+  }
 }
 
 .error-bar {
@@ -519,6 +532,199 @@ div.indent {
 }
 
 .table-container {
+  .use-gpu;
+
+  margin: 10px 0px;
+
+  .table-header {
+    padding: 5px;
+
+    button:hover {
+      background-color: @bg-grey;
+    }
+
+    .table-message {
+      .inline-block;
+      font-size: 18px;
+      margin-top: 10px;
+      margin-left: 5px;
+      vertical-align: super;
+
+      overflow: hidden;
+      transform: translateZ(0);
+    }
+
+    .horizontal-half {
+      height: 33px;
+      white-space: nowrap;
+    }
+
+    .extra-table-buttons {
+      .inline-block;
+
+      .load-all {
+        .inline-block;
+        .left-divider;
+
+        margin-left: 5px;
+        text-align: center;
+
+        :first-child {
+          margin-bottom: -3px;
+          font-size: .85em;
+          color: red;
+        }
+      }
+
+      .column-selector-button {
+        .fa-action;
+        .fa-icon(cog);
+        .left-divider;
+        .inline-block;
+
+        padding-top: 5px;
+        height: 36px;
+      }
+    }
+  }
+
+  .waiting {
+    .fa;
+    .fa-icon(spinner);
+    .fa-spin;
+    .fa-fw;
+  }
+
+  .pagination-view {
+    .inline-block;
+
+    .page-list {
+      .inline-block;
+      .align-top;
+
+      border: 1px solid @border-color;
+      border-radius: 5px;
+
+      padding: 0px;
+
+      font-size: 0px;
+
+      li {
+        .inline-block;
+
+        padding: 6px 10px;
+        font-size: 14px;
+
+        border-left: 1px solid @border-color;
+
+        pointer-events: none;
+
+        &.clickable {
+          pointer-events: auto;
+          &:hover {
+            background-color: @bg-grey;
+            cursor: pointer;
+          }
+        }
+      }
+
+      .total-page-count {
+        font-size: .8em;
+      }
+
+      :first-child {
+        border-left: none;
+      }
+    }
+
+    .row-select {
+      margin-left: 5px;
+
+      display: inline-block;
+      text-align: center;
+
+      :first-child {
+        margin-bottom: -3px;
+        font-size: .85em;
+      }
+    }
+  }
+
+  .table-body-container{
+    border: 1px solid @border-color;
+    border-radius: 5px;
+
+    margin: 5px 0px;
+
+    overflow: auto;
+
+    .table-body {
+      .noise-background;
+      white-space: nowrap;
+      font-size: 0;
+
+      .table-column {
+        background-color: white;
+        display: inline-block;
+        min-width: 150px;
+        border-left: 1px solid @border-color;
+
+        &.first-item {
+          border-left: none;
+        }
+
+        .table-header-cell, .table-cell {
+          font-size: 13px;
+          padding: 5px;
+          position: relative;
+
+          .cell-content {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            height: 18px;
+          }
+
+          .resize-column {
+            .fa;
+            .fa-icon(ellipsis-v);
+
+            cursor: col-resize;
+            position: absolute;
+            right: 2px;
+            top: 10px;
+
+            opacity: .2;
+          }
+        }
+
+        .table-header-cell {
+          font-weight: bold;
+
+          padding-right: 15px;
+
+          -webkit-user-select: none;
+          -moz-user-select: none;
+          -ms-user-select: none;
+          user-select: none;
+
+          background-color: @bg-grey;
+          border-bottom: 1px solid @border-color;
+        }
+
+        .table-cell {
+          border-top: 1px dotted @border-color;
+
+          &.first-item {
+            border-top: none;
+          }
+        }
+      }
+    }
+  }
+}
+
+.ember-table-container {
   min-height: 20px;
   width: 100%;
   overflow: auto;
@@ -532,6 +738,23 @@ div.indent {
   }
 }
 
+.filter-cell, {
+  left: 0px;
+  top: 0px;
+  .sort, .filter {
+    position: absolute;
+  }
+  .filter {
+    left: 5px;
+    right: 20px;
+  }
+  .sort {
+    cursor: pointer;
+    top: 7px;
+    right: 5px;
+  }
+}
+
 .input-dirty {
   background-color: yellow;
 }

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/styles/shared.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/shared.less b/tez-ui/src/main/webapp/app/styles/shared.less
index 617c87f..cebd837 100644
--- a/tez-ui/src/main/webapp/app/styles/shared.less
+++ b/tez-ui/src/main/webapp/app/styles/shared.less
@@ -43,4 +43,28 @@
 
 .no-wrap {
   white-space: nowrap;
+}
+
+.align-top{
+  vertical-align: top;
+}
+
+.inline-block {
+  display: inline-block;
+}
+
+.noise-background {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoY
 RxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4
 wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==);
+}
+
+.absolute {
+  position: absolute;
+}
+
+.use-gpu {
+  -webkit-transform: translateZ(0);
+     -moz-transform: translateZ(0);
+      -ms-transform: translateZ(0);
+       -o-transform: translateZ(0);
+          transform: translateZ(0);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/common/table.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/common/table.hbs b/tez-ui/src/main/webapp/app/templates/common/table.hbs
new file mode 100644
index 0000000..2c54903
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/common/table.hbs
@@ -0,0 +1,47 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+{{#unless loading}}
+  {{#if data.length}}
+    {{load-time-component
+      isRefreshable=isRefreshable
+      time=data.content.0.timeStamp
+      refresh='refresh'
+    }}
+
+    {{basic-table-component
+      columns=columns
+      rows=data.content
+
+      extraHeaderItem=App.ExtraTableButtonsView
+      statusMessage=statusMessage
+
+      enablePagination=true
+
+      pageNumBinding='pageNum'
+      rowCountBinding='rowCount'
+    }}
+  {{else}}
+    <h1>No records available!</n1>
+  {{/if}}
+{{else}}
+  {{partial 'partials/loading-spinner'}}
+  <div class="text-align-center">
+    {{statusMessage}}
+  </div>
+{{/unless}}

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
new file mode 100644
index 0000000..ac4a466
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
@@ -0,0 +1,58 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class='table-container'>
+  {{#if _showHeader}}
+    <div class='table-header'>
+      <div class="horizontal-half align-top">
+      </div><div class="horizontal-half align-top align-children-right">
+        {{#if enablePagination}}
+          {{view App.BasicTableComponent.PaginationView pageNum=pageNum totalPages=totalPages}}
+        {{/if}}
+        {{#if extraHeaderItem}}
+          {{view extraHeaderItem}}
+        {{/if}}
+      </div>
+    </div>
+  {{/if}}
+  <div class='table-body-container'>
+    <div class='table-body'>
+      {{#each column in _columns}}
+        <div {{bind-attr
+            style=column.customStyle
+            class=":table-column _view.contentIndex::first-item"
+        }}>
+          {{view column.headerCellView}}
+          {{#each row in _rows}}
+            <div class='table-cell {{unbound firstItemCSS _view}}'>
+              {{view column.cellView row=row}}
+            </div>
+          {{/each}}
+        </div>
+      {{/each}}
+    </div>
+  </div>
+  <div class='table-footer'>
+    <div class="horizontal-half align-top">
+    </div><div class="horizontal-half align-top align-children-right">
+      {{#if hasPageNavOnFooter}}
+        {{view App.BasicTableComponent.PaginationView pageNum=pageNum totalPages=totalPages}}
+      {{/if}}
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/basic-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/basic-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/basic-cell.hbs
new file mode 100644
index 0000000..370637b
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/basic-cell.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.
+}}
+
+{{unbound view.cellContent}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
new file mode 100644
index 0000000..1159524
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
@@ -0,0 +1,24 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class='table-header-cell'>
+  <div class='cell-content'>
+    {{unbound column.headerCellName}}
+  </div>
+  <i class="resize-column" {{action 'startColResize' on=mouseDown target='view'}} ></i>
+</div>

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/linked-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/linked-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/linked-cell.hbs
new file mode 100644
index 0000000..3297ad7
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/linked-cell.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.cellContent._notAvailable}}
+  {{view.cellContent}}
+{{else}}
+  {{#link-to view.cellContent.linkTo view.cellContent.entityId}}
+    {{view.cellContent.displayText}}
+  {{/link-to}}
+{{/if}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/logs-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/logs-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/logs-cell.hbs
new file mode 100644
index 0000000..092b6ee
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/logs-cell.hbs
@@ -0,0 +1,35 @@
+{{!
+* 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 class="ember-table-content">
+  {{#if view.cellContent.isPending}}
+    <i class="waiting absolute"></i>&nbsp;
+  {{else}}
+    {{#if view.cellContent._notAvailable}}
+      {{view.cellContent}}
+    {{else}}
+      {{#if view.cellContent.viewUrl}}
+        <a target="_blank" href="//{{unbound view.cellContent.viewUrl}}">View</a>
+        &nbsp;
+      {{/if}}
+      {{#if view.cellContent.downloadUrl}}
+        <a target="_blank" href="{{unbound view.cellContent.downloadUrl}}?start=0" download type="application/octet-stream">Download</a>
+      {{/if}}
+    {{/if}}
+  {{/if}}
+</span>

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/pagination-view.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/pagination-view.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/pagination-view.hbs
new file mode 100644
index 0000000..808fe9d
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/pagination-view.hbs
@@ -0,0 +1,38 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<ul class="page-list">
+  <li {{bind-attr class="view.atFirst::clickable"}} {{action 'changePage' 1}}>
+    First page
+  </li>
+  {{#each page in view._possiblePages}}
+    <li {{bind-attr class="page.isCurrent:is-current:clickable"}} {{action 'changePage' page.pageNum}}>
+      {{page.pageNum}}
+    </li>
+  {{/each}}
+  <li {{bind-attr class="view.atLast::clickable"}} {{action 'changePage' view.totalPages}}>
+    Last page - {{view.totalPages}}
+  </li>
+</ul>
+<div class='row-select'>
+  <div>Rows</div>
+  {{view Ember.Select
+    content=rowCountOptions
+    value=rowCount
+  }}
+</div>

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/status-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/status-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/status-cell.hbs
new file mode 100644
index 0000000..a7caa35
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/status-cell.hbs
@@ -0,0 +1,29 @@
+{{!
+* 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.cellContent._notAvailable}}
+  {{view.cellContent}}
+{{else}}
+  <span class="ember-table-content">
+    <i {{bind-attr class=":task-status view.cellContent.statusIcon"}}></i>
+    &nbsp;&nbsp;{{view.cellContent.status}}
+    {{#if view.cellContent.progress}}
+      {{bs-badge content=view.cellContent.progress}}
+    {{/if}}
+  </span>
+{{/if}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/components/basic-table/task-actions-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/task-actions-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/task-actions-cell.hbs
new file mode 100644
index 0000000..b914b82
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/task-actions-cell.hbs
@@ -0,0 +1,26 @@
+{{!
+* 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.cellContent._notAvailable}}
+  {{view.cellContent}}
+{{else}}
+  <span class="ember-table-content">
+    {{#link-to "task.counters" view.cellContent}}counters{{/link-to}}
+    {{#link-to "task.attempts" view.cellContent}}attempts{{/link-to}}
+  </span>
+{{/if}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/9316fe62/tez-ui/src/main/webapp/app/templates/views/extra-table-buttons.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/views/extra-table-buttons.hbs b/tez-ui/src/main/webapp/app/templates/views/extra-table-buttons.hbs
new file mode 100644
index 0000000..98099db
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/views/extra-table-buttons.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='column-selector-button' {{action 'selectColumns' target=targetObject}}></i>


Mime
View raw message