You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tez.apache.org by je...@apache.org on 2014/12/10 04:33:32 UTC

[09/53] tez git commit: TEZ-1615. Skeleton framework for Tez UI (Prakash Ramachandran via jeagles)

TEZ-1615. Skeleton framework for Tez UI (Prakash Ramachandran via jeagles)


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

Branch: refs/heads/master
Commit: 2f2739ddb4819c57871ca52b963b5a03fe2872ee
Parents: fb365f4
Author: Jonathan Eagles <je...@gmail.com>
Authored: Fri Oct 3 21:10:56 2014 -0500
Committer: Jonathan Eagles <je...@gmail.com>
Committed: Fri Oct 3 21:10:56 2014 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 tez-ui/.bowerrc                                 |   3 +
 tez-ui/.gitattributes                           |   1 +
 tez-ui/.gitignore                               |   9 +
 tez-ui/.jshintrc                                |  24 +
 tez-ui/Gruntfile.js                             | 450 +++++++++++++++++++
 tez-ui/README.TXT                               |  17 +
 tez-ui/app/img/apache_tez_logo_lowres.png       | Bin 0 -> 70978 bytes
 tez-ui/app/img/glyphicons-halflings.png         | Bin 0 -> 13826 bytes
 tez-ui/app/index.html                           |  69 +++
 tez-ui/app/scripts/app.js                       |  53 +++
 tez-ui/app/scripts/components/page-nav.js       |  31 ++
 .../app/scripts/controllers/dag_controller.js   |  40 ++
 .../scripts/controllers/dag_index_controller.js |  63 +++
 .../app/scripts/controllers/dags_controller.js  | 190 ++++++++
 tez-ui/app/scripts/helpers/ajax.js              | 175 ++++++++
 tez-ui/app/scripts/helpers/date.js              | 216 +++++++++
 tez-ui/app/scripts/helpers/handlebarHelpers.js  |  48 ++
 tez-ui/app/scripts/helpers/number.js            | 101 +++++
 tez-ui/app/scripts/helpers/translation.js       | 119 +++++
 tez-ui/app/scripts/mappers/dag_mapper.js        |  56 +++
 tez-ui/app/scripts/mixins/display_helpers.js    |  39 ++
 tez-ui/app/scripts/mixins/run_periodically.js   |  78 ++++
 .../app/scripts/models/TimelineRestAdapter.js   | 124 +++++
 tez-ui/app/scripts/models/abstract_entity.js    |  27 ++
 tez-ui/app/scripts/models/dag.js                | 231 ++++++++++
 tez-ui/app/scripts/router.js                    |  88 ++++
 tez-ui/app/scripts/store.js                     |  21 +
 tez-ui/app/styles/main.css                      |  70 +++
 tez-ui/app/templates/application.hbs            |  21 +
 tez-ui/app/templates/components/page-nav.hbs    |  22 +
 tez-ui/app/templates/dag.hbs                    |  36 ++
 tez-ui/app/templates/dag/index.hbs              |  41 ++
 tez-ui/app/templates/dag/swimlane.hbs           |   1 +
 tez-ui/app/templates/dag/vertex.hbs             |   1 +
 tez-ui/app/templates/dags.hbs                   |  58 +++
 tez-ui/app/templates/error_.hbs                 |   6 +
 tez-ui/app/templates/utils/_loadingSpinner.hbs  |   3 +
 tez-ui/app/templates/utils/_pageHeader.hbs      |   7 +
 tez-ui/bower.json                               |  32 ++
 tez-ui/package.json                             |  36 ++
 41 files changed, 2608 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index ced524a..a0c0ae8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -7,6 +7,7 @@ INCOMPATIBLE CHANGES
 
 ALL CHANGES:
   TEZ-1634. Fix compressed IFile shuffle errors
+  TEZ-1615. Skeleton framework for Tez UI
 
 Release 0.5.2: Unreleased
 

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.bowerrc
----------------------------------------------------------------------
diff --git a/tez-ui/.bowerrc b/tez-ui/.bowerrc
new file mode 100644
index 0000000..ba0accc
--- /dev/null
+++ b/tez-ui/.bowerrc
@@ -0,0 +1,3 @@
+{
+    "directory": "app/bower_components"
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.gitattributes
----------------------------------------------------------------------
diff --git a/tez-ui/.gitattributes b/tez-ui/.gitattributes
new file mode 100644
index 0000000..2125666
--- /dev/null
+++ b/tez-ui/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.gitignore
----------------------------------------------------------------------
diff --git a/tez-ui/.gitignore b/tez-ui/.gitignore
new file mode 100644
index 0000000..ee72141
--- /dev/null
+++ b/tez-ui/.gitignore
@@ -0,0 +1,9 @@
+node
+node_modules
+temp
+dist
+.sass-cache
+.tmp
+app/bower_components
+test/bower_components
+.editorconfig

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.jshintrc
----------------------------------------------------------------------
diff --git a/tez-ui/.jshintrc b/tez-ui/.jshintrc
new file mode 100644
index 0000000..780790d
--- /dev/null
+++ b/tez-ui/.jshintrc
@@ -0,0 +1,24 @@
+{
+    "node": true,
+    "browser": true,
+    "esnext": true,
+    "bitwise": false,
+    "curly": false,
+    "eqeqeq": true,
+    "eqnull": true,
+    "immed": true,
+    "latedef": true,
+    "newcap": true,
+    "noarg": true,
+    "undef": true,
+    "strict": false,
+    "trailing": false,
+    "smarttabs": true,
+    "globals": {
+        "App": true,
+        "jQuery": true,
+        "Ember": true,
+        "Handlebars": true,
+        "DS": true
+    }
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/Gruntfile.js
----------------------------------------------------------------------
diff --git a/tez-ui/Gruntfile.js b/tez-ui/Gruntfile.js
new file mode 100644
index 0000000..4e7fbde
--- /dev/null
+++ b/tez-ui/Gruntfile.js
@@ -0,0 +1,450 @@
+/**
+ * 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.
+ */
+// Generated on 2014-06-24 using generator-ember 0.8.4
+'use strict';
+var LIVERELOAD_PORT = 35729;
+var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT});
+var mountFolder = function (connect, dir) {
+  return connect.static(require('path').resolve(dir));
+};
+
+// # Globbing
+// for performance reasons we're only matching one level down:
+// 'test/spec/{,*/}*.js'
+// use this if you want to match all subfolders:
+// 'test/spec/**/*.js'
+
+module.exports = function (grunt) {
+  // show elapsed time at the end
+  require('time-grunt')(grunt);
+  // load all grunt tasks
+  require('load-grunt-tasks')(grunt);
+
+  // configurable paths
+  var yeomanConfig = {
+    app: 'app',
+    dist: 'dist'
+  };
+
+  grunt.initConfig({
+    yeoman: yeomanConfig,
+    watch: {
+      emberTemplates: {
+        files: '<%= yeoman.app %>/templates/**/*.hbs',
+        tasks: ['emberTemplates']
+      },
+      neuter: {
+        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
+        tasks: ['neuter']
+      },
+      less: {
+        files: '<%= yeoman.app %>/styles/**/*.less',
+        tasks: ['less:development']
+      },
+      livereload: {
+        options: {
+          livereload: LIVERELOAD_PORT
+        },
+        files: [
+          '.tmp/scripts/**/*.js',
+          '<%= yeoman.app %>/*.html',
+          '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
+          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
+        ]
+      },
+      css: {
+        files: '<%= yeoman.app %>/styles/*.css',
+        tasks: ['copy:development']
+      }
+    },
+    connect: {
+      options: {
+        port: 9000,
+        // change this to '0.0.0.0' to access the server from outside
+        hostname: 'localhost'
+      },
+      livereload: {
+        options: {
+          middleware: function (connect) {
+            return [
+              lrSnippet,
+              mountFolder(connect, '.tmp'),
+              mountFolder(connect, yeomanConfig.app)
+            ];
+          }
+        }
+      },
+      test: {
+        options: {
+          middleware: function (connect) {
+            return [
+              mountFolder(connect, 'test'),
+              mountFolder(connect, '.tmp')
+            ];
+          }
+        }
+      },
+      dist: {
+        options: {
+          middleware: function (connect) {
+            return [
+              mountFolder(connect, yeomanConfig.dist)
+            ];
+          }
+        }
+      }
+    },
+    open: {
+      server: {
+        path: 'http://localhost:<%= connect.options.port %>'
+      }
+    },
+    clean: {
+      dist: {
+        files: [
+          {
+            dot: true,
+            src: [
+              '.tmp',
+              '<%= yeoman.dist %>/*',
+              '!<%= yeoman.dist %>/.git*'
+            ]
+          }
+        ]
+      },
+      server: '.tmp'
+    },
+    jshint: {
+      options: {
+        jshintrc: '.jshintrc',
+        reporter: require('jshint-stylish')
+      },
+      all: [
+        'Gruntfile.js',
+        '<%= yeoman.app %>/scripts/{,*/}*.js',
+        '!<%= yeoman.app %>/scripts/vendor/*',
+        'test/spec/{,*/}*.js'
+      ]
+    },
+    mocha: {
+      all: {
+        options: {
+          run: true,
+          urls: ['http://localhost:<%= connect.options.port %>/index.html']
+        }
+      }
+    },
+    // not used since Uglify task does concat,
+    // but still available if needed
+    /*concat: {
+     dist: {}
+     },*/
+    // not enabled since usemin task does concat and uglify
+    // check index.html to edit your build targets
+    // enable this task if you prefer defining your build targets here
+    /*uglify: {
+     dist: {}
+     },*/
+    rev: {
+      dist: {
+        files: {
+          src: [
+            '<%= yeoman.dist %>/scripts/{,*/}*.js',
+            '<%= yeoman.dist %>/styles/{,*/}*.css',
+            '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}',
+            '<%= yeoman.dist %>/styles/fonts/*'
+          ]
+        }
+      }
+    },
+    useminPrepare: {
+      html: '.tmp/index.html',
+      options: {
+        dest: '<%= yeoman.dist %>'
+      }
+    },
+    usemin: {
+      html: ['<%= yeoman.dist %>/{,*/}*.html'],
+      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
+      options: {
+        dirs: ['<%= yeoman.dist %>']
+      }
+    },
+    svgmin: {
+      dist: {
+        files: [
+          {
+            expand: true,
+            cwd: '<%= yeoman.app %>/images',
+            src: '{,*/}*.svg',
+            dest: '<%= yeoman.dist %>/images'
+          }
+        ]
+      }
+    },
+    cssmin: {
+      dist: {
+        files: {
+          '<%= yeoman.dist %>/styles/main.css': [
+            '.tmp/styles/{,*/}*.css',
+            '<%= yeoman.app %>/styles/{,*/}*.css',
+            '<%= yeoman.app %>/bower_components/bootstrap/dist/css/bootstrap.css',
+            '<%= yeoman.app %>/bower_components/ember-table/dist/ember-table.css'
+          ]
+        }
+      }
+    },
+    htmlmin: {
+      dist: {
+        options: {
+          /*removeCommentsFromCDATA: true,
+           // https://github.com/yeoman/grunt-usemin/issues/44
+           //collapseWhitespace: true,
+           collapseBooleanAttributes: true,
+           removeAttributeQuotes: true,
+           removeRedundantAttributes: true,
+           useShortDoctype: true,
+           removeEmptyAttributes: true,
+           removeOptionalTags: true*/
+        },
+        files: [
+          {
+            expand: true,
+            cwd: '<%= yeoman.app %>',
+            src: '*.html',
+            dest: '<%= yeoman.dist %>'
+          }
+        ]
+      }
+    },
+    replace: {
+      app: {
+        options: {
+          variables: {
+            ember: 'bower_components/ember/ember.js',
+            ember_data: 'bower_components/ember-data/ember-data.js'
+          }
+        },
+        files: [
+          {src: '<%= yeoman.app %>/index.html', dest: '.tmp/index.html'}
+        ]
+      },
+      dist: {
+        options: {
+          variables: {
+            ember: 'bower_components/ember/ember.prod.js',
+            ember_data: 'bower_components/ember-data/ember-data.prod.js'
+          }
+        },
+        files: [
+          {src: '<%= yeoman.app %>/index.html', dest: '.tmp/index.html'}
+        ]
+      }
+    },
+    // Put files not handled in other tasks here
+    copy: {
+      dist: {
+        files: [
+          {
+            expand: true,
+            dot: true,
+            cwd: '<%= yeoman.app %>',
+            dest: '<%= yeoman.dist %>',
+            src: [
+              '*.{ico,txt}',
+              '.htaccess',
+              'img/*',
+              'styles/fonts/*',
+              'scripts/assets/**/*'
+            ]
+          },
+          {
+            expand: true,
+            flatten: true,
+            src: '<%= yeoman.app %>/bower_components/jquery-ui/themes/base/images/*',
+            dest: '<%= yeoman.dist %>/styles/images/'
+          },
+          {
+            expand: true,
+            flatten: true,
+            src: '<%= yeoman.app %>/bower_components/font-awesome/font/*',
+            dest: '<%= yeoman.dist %>/font/'
+          }
+        ]
+      },
+      development: {
+        files: [
+          {
+            expand: true,
+            dot: true,
+            cwd: '<%= yeoman.app %>',
+            dest: '.tmp',
+            src: [
+              '*.{ico,txt}',
+              '.htaccess',
+              'img/*',
+              'styles/*.css',
+              'styles/fonts/*',
+              'scripts/assets/**/*'
+            ]
+          },
+          {
+            expand: true,
+            flatten: true,
+            src: '<%= yeoman.app %>/bower_components/jquery-ui/themes/base/images/*',
+            dest: '.tmp/styles/images/'
+          },
+          {
+            expand: true,
+            flatten: true,
+            src: '<%= yeoman.app %>/bower_components/font-awesome/font/*',
+            dest: '.tmp/font/'
+          },
+          {
+            expand: true,
+            flatten: false,
+            cwd: '<%= yeoman.app %>',
+            src: 'bower_components/**',
+            dest: '.tmp/'
+          }
+        ]
+      }
+    },
+    concurrent: {
+      server: [
+        'emberTemplates'
+      ],
+      test: [
+        'emberTemplates'
+      ],
+      dist: [
+        'emberTemplates',
+        'svgmin',
+        'htmlmin'
+      ]
+    },
+    emberTemplates: {
+      options: {
+        templateName: function (sourceFile) {
+          var templatePath = yeomanConfig.app + '/templates/';
+          return sourceFile.replace(templatePath, '');
+        }
+      },
+      dist: {
+        files: {
+          '.tmp/scripts/compiled-templates.js': '<%= yeoman.app %>/templates/**/*.hbs'
+        }
+      },
+      development: {
+        files: {
+          '.tmp/scripts/compiled-templates.js': '<%= yeoman.app %>/templates/**/*.hbs'
+        }
+      }
+    },
+
+    less: {
+      development: {
+        options: {
+          paths: ["<%= yeoman.app %>/styles"],
+          sourceMap: true,
+        },
+        files: [{
+          expand: true,
+          cwd: "<%= yeoman.app %>/styles",
+          src: ['styles/*.less', '**/*.less'],
+          dest: ".tmp/styles",
+          ext: ".css" 
+        }]
+      },
+      production: {
+        options: {
+          paths: ["<%= yeoman.app %>/styles"],
+          cleancss: true
+        },
+        files: {
+          ".tmp/styles/styles.css": "<%= yeoman.app %>/styles/**/*.less"
+        }
+      }
+    },
+
+    neuter: {
+      app: {
+        options: {
+          filepathTransform: function (filepath) {
+            return yeomanConfig.app + '/' + filepath;
+          }
+        },
+        src: '<%= yeoman.app %>/scripts/**/*.js',
+        dest: '.tmp/scripts/combined-scripts.js'
+      }
+    }
+  });
+
+  grunt.registerTask('server', function (target) {
+    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
+    grunt.task.run(['serve:' + target]);
+  });
+
+  grunt.registerTask('serve', function (target) {
+    if (target === 'dist') {
+      return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
+    }
+
+    grunt.task.run([
+      'clean:server',
+      'replace:app',
+      'concurrent:server',
+      'neuter:app',
+      'less:development',
+      'connect:livereload',
+      'open',
+      'copy:development',
+      'watch'
+    ]);
+  });
+
+  grunt.registerTask('test', [
+    'clean:server',
+    'replace:app',
+    'concurrent:test',
+    'connect:test',
+    'neuter:app',
+    'mocha'
+  ]);
+
+  grunt.registerTask('build', [
+    'clean:dist',
+    'replace:app',
+    'useminPrepare',
+    'concurrent:dist',
+    'neuter:app',
+    'less:production',
+    'concat',
+    'cssmin',
+    //'uglify',
+    'copy:dist',
+    //'rev',
+    'usemin'
+  ]);
+
+  grunt.registerTask('default', [
+    'jshint',
+    'test',
+    'build'
+  ]);
+};

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/README.TXT
----------------------------------------------------------------------
diff --git a/tez-ui/README.TXT b/tez-ui/README.TXT
new file mode 100644
index 0000000..4e00df0
--- /dev/null
+++ b/tez-ui/README.TXT
@@ -0,0 +1,17 @@
+Dev setup instructions
+----------------------
+install the development dependencies nodejs, npm, grunt and bower
+from the top level directory run the following commands
+  * npm install
+  * bower install
+
+The timeline server (ATS) url by default points to http://localhost:8188. This can be
+changed to point to another host by editing the App.AtsBaseUrl entry in the
+app/scripts/app.js file.
+
+For development run 'grunt serve'. This runs a dev server on port 9000. 
+navigate to http://localhost:9000 if a browser does not open automatically.
+Any changes made will be live-reloaded on the browser.
+
+For creating a distribution version, run 'grunt build'. The distributable 
+version will be placed under the dist directory.

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/img/apache_tez_logo_lowres.png
----------------------------------------------------------------------
diff --git a/tez-ui/app/img/apache_tez_logo_lowres.png b/tez-ui/app/img/apache_tez_logo_lowres.png
new file mode 100644
index 0000000..45fd701
Binary files /dev/null and b/tez-ui/app/img/apache_tez_logo_lowres.png differ

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/img/glyphicons-halflings.png
----------------------------------------------------------------------
diff --git a/tez-ui/app/img/glyphicons-halflings.png b/tez-ui/app/img/glyphicons-halflings.png
new file mode 100644
index 0000000..79bc568
Binary files /dev/null and b/tez-ui/app/img/glyphicons-halflings.png differ

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/index.html
----------------------------------------------------------------------
diff --git a/tez-ui/app/index.html b/tez-ui/app/index.html
new file mode 100644
index 0000000..75ddf6d
--- /dev/null
+++ b/tez-ui/app/index.html
@@ -0,0 +1,69 @@
+<!--
+* 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.
+-->
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Tez UI</title>
+
+    <!-- build:css styles/main.css -->
+    <link rel="stylesheet" href="styles/main.css">
+    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
+    <link rel="stylesheet" href="bower_components/ember-table/dist/ember-table.css">
+    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css">
+    <!-- endbuild -->
+
+  </head>
+
+  <body>
+    <!-- build:js(app) scripts/components.js -->
+    <script src="bower_components/cldr/plurals.js"></script>
+    <script src="bower_components/jquery/jquery.js"></script>
+    <script src="bower_components/jquery-ui/jquery-ui.js"></script>
+    <script src="bower_components/bootstrap/js/dropdown.js"></script>
+    <script src="bower_components/bootstrap/js/button.js"></script>
+    <script src="bower_components/bootstrap/js/tooltip.js"></script>
+    <script src="bower_components/jquery-ui/ui/datepicker.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>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-core.min.js"></script>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-button.min.js"></script>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-modal.min.js"></script>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-nav.min.js"></script>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-items-action-bar.min.js"></script>
+    <script src="bower_components/antiscroll/antiscroll.js"></script>
+    <script src="bower_components/jquery-mousewheel/jquery.mousewheel.js"></script>
+    <script src="bower_components/ember-table/dist/ember-table.js"></script>
+    <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-nav.min.js"></script>
+    <script src="bower_components/d3/d3.v2.js"></script>
+    <!-- endbuild -->
+
+    <!-- build:js(.tmp) scripts/templates.js -->
+    <script src="scripts/compiled-templates.js"></script>
+    <!-- endbuild -->
+
+    <!-- build:js(.tmp) scripts/main.js -->
+    <script src="scripts/combined-scripts.js"></script>
+    <!-- endbuild -->
+  </body>
+</html>
+    

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/app.js b/tez-ui/app/scripts/app.js
new file mode 100644
index 0000000..a3d9d5c
--- /dev/null
+++ b/tez-ui/app/scripts/app.js
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = window.App = Em.Application.createWithMixins(Bootstrap, {
+	// Basic logging, e.g. "Transitioned into 'post'"
+  LOG_TRANSITIONS: true, 
+
+  // Extremely detailed logging, highlighting every internal
+  // step made while transitioning into a route, including
+  // `beforeModel`, `model`, and `afterModel` hooks, and
+  // information about redirects and aborted transitions
+  LOG_TRANSITIONS_INTERNAL: true
+});
+
+App.AtsBaseUrl = "http://localhost:8188";
+
+require('scripts/router');
+require('scripts/store');
+
+App.Helpers = Em.Namespace.create();
+App.Mappers = Em.Namespace.create();
+
+//TODO: initializer. 
+
+/* Order and include */
+/* TODO: cleanup */
+require('scripts/translations');
+require('scripts/mixins/*');
+require('scripts/helpers/*');
+require('scripts/models/**/*');
+require('scripts/mappers/server_data_mapper.js');
+require('scripts/mappers/**/*');
+require('scripts/controllers/**/*');
+require('scripts/components/*');
+require('scripts/adapters/*');
+
+App.ApplicationAdapter = App.TimelineRESTAdapter.extend();
+App.ApplicationSerializer = App.TimelineSerializer.extend();

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/components/page-nav.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/components/page-nav.js b/tez-ui/app/scripts/components/page-nav.js
new file mode 100644
index 0000000..b7c21a6
--- /dev/null
+++ b/tez-ui/app/scripts/components/page-nav.js
@@ -0,0 +1,31 @@
+/**
+ * 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.PageNavComponent = Em.Component.extend({
+	actions: {
+		gotoNext: function() {
+			this.sendAction('navNext');
+		},
+		gotoPrev: function() {
+			this.sendAction('navPrev');
+		},
+		gotoFirst: function() {
+			this.sendAction('navFirst');
+		}
+	}
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dag_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dag_controller.js b/tez-ui/app/scripts/controllers/dag_controller.js
new file mode 100644
index 0000000..6d601a2
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dag_controller.js
@@ -0,0 +1,40 @@
+/**
+ * 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.DagController = Em.ObjectController.extend(App.Helpers.DisplayHelper, {
+	controllerName: "DagsController",
+
+	pageTitle: "Dag",
+
+	loading: true,
+
+	updateLoading: function() {
+    this.set('loading', false);
+  }.observes('content'),
+
+	pageSubTitle: function() {
+		return this.get('name');
+	}.property(),
+
+	childDisplayViews: [
+		Ember.Object.create({title: 'Details', linkTo: 'dag.index'}),
+		Ember.Object.create({title: 'Vertex', linkTo: 'dag.vertex'}),
+		Ember.Object.create({title: 'Dag View', linkTo: 'dag.swimlane'})
+	],
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dag_index_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dag_index_controller.js b/tez-ui/app/scripts/controllers/dag_index_controller.js
new file mode 100644
index 0000000..4ecae7d
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dag_index_controller.js
@@ -0,0 +1,63 @@
+/**
+ * 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.DagIndexController = Em.ObjectController.extend({
+	controllerName: "DagsController",
+
+	counterTableColumns: function() {
+		var groupColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Group',
+      getCellContent: function(row) {
+      	return row.get('counterGroup');
+      }
+    });
+
+		var nameColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Counter Name',
+      getCellContent: function(row) {
+      	return row.get('name');
+      }
+    });
+
+		var valueColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Counter Name',
+      getCellContent: function(row) {
+      	return row.get('value');
+      }
+    });
+
+    return [groupColumn, nameColumn, valueColumn];
+	}.property(),
+
+	counterContent: function() {
+		var allCounters = [];
+		this.get('content').get('counterGroups').forEach(function(cg){
+			cg.get('counters').forEach(function(counter){
+				allCounters.push({
+					counterGroup: cg.get('displayName'),
+					name: counter.get('displayName'),
+					value: counter.get('value')
+				});
+			});
+		});
+		return allCounters;
+	}.property(),
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dags_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dags_controller.js b/tez-ui/app/scripts/controllers/dags_controller.js
new file mode 100644
index 0000000..70ef7f3
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dags_controller.js
@@ -0,0 +1,190 @@
+/**
+ * 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.DagsController = Em.ArrayController.extend({
+	controllerName: "DagsController",
+
+	pageTitle: "Dags",
+
+	pageSubTitle: "All Dags",
+
+  /* filtering and sorting related */
+  queryParams: {
+    count: true,
+    fromID: true
+  },
+
+  count: 10,
+
+  fromID: '',
+
+  fromTS: '',
+
+  fields: 'events,primaryfilters,otherinfo',
+  /* end sort & filter related */
+
+  // content is being loaded.
+  loading: true,
+
+  /* There is currently no efficient way in ATS to get pagination data, so we fake one.
+   * store the first dag id on a page so that we can navigate back and store the last one 
+   * (not shown on page to get the id where next page starts)
+   */
+  navIDs: {
+    prevIDs: [],
+    currentID: undefined,
+    nextID: undefined
+  },
+
+  updateLoading: function() {
+    this.set('loading', false);
+  }.observes('content'),
+
+  sortedContent: function() {
+    var sorted = Em.ArrayController.create({
+      model: this.get('content'),
+      sortProperties: ['startTime'],
+      sortAscending: false
+    });
+    this.updatePagination(sorted.toArray());
+    return sorted.slice(0, this.count);
+  }.property('content.isUpdating', 'content.isLoading'),
+
+  updatePagination: function(currentPageDagIDs) {
+    if (!!currentPageDagIDs && currentPageDagIDs.length > 0) {
+      this.set('navIDs.currentID', currentPageDagIDs[0].id);
+      var nextID = undefined;
+      if (currentPageDagIDs.length > this.count) {
+        // save the last id, so that we can use that as firt id on next page.
+        nextID = currentPageDagIDs[this.count].id;
+      }
+      this.set('navIDs.nextID', nextID);
+    }
+  },
+
+  hasPrev: function() {
+    return this.navIDs.prevIDs.length > 0;
+  }.property('navIDs.prevIDs.[]'),
+
+  hasNext: function() {
+    return !!this.navIDs.nextID;
+  }.property('navIDs.nextID'),
+
+  actions:{
+    // go to previous page
+    navigatePrev: function () {
+      var prevPageId = this.navIDs.prevIDs.popObject();
+      this.set('fromID', prevPageId);
+      this.set('loading', true);
+      this.transitionToRoute('dags');
+    },
+
+    // goto first page.
+    navigateFirst: function() {
+      var firstPageId = this.navIDs.prevIDs[0];
+      this.set('navIDs.prevIDs', []);
+      this.set('fromID', firstPageId);
+      this.set('loading', true);
+      this.transitionToRoute('dags');
+    },
+
+    // go to next page
+    navigateNext: function () {
+      this.navIDs.prevIDs.pushObject(this.navIDs.currentID);
+      this.set('fromID', this.get('navIDs.nextID'));
+      this.set('loading', true);
+      this.transitionToRoute('dags');
+    },
+  },
+
+  getFilterParams: function(params) {
+    var filterParams = {
+      limit: (parseInt(params.count) || this.get('count')) + 1,
+      fields: this.get('fields')
+    };
+    var fromID = params.fromID || this.get('fromID'), 
+        fromTS = params.fromTS || this.get('fromTS'),
+        user = params.user;
+    
+    if (fromID) {
+      filterParams['fromId'] = fromID; 
+    }
+
+    if (fromTS) {
+      filterParams['fromTs'] = fromTS;
+    }
+
+    if (user) {
+      filterParams['primaryFilter'] = 'user:' + user;
+    }
+
+    return filterParams;
+  },
+
+	/* table view for dags */
+  columns: function() {
+    var idColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Dag Id',
+      tableCellViewClass: Em.Table.TableCell.extend({
+      	template: Em.Handlebars.compile("{{#link-to 'dag' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+      }),
+      getCellContent: function(row) {
+      	return row.get('id');
+      }
+    });
+    var nameColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Name',
+      getCellContent: function(row) {
+      	return row.get('name');
+      }
+    });
+    var userColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Submitter',
+      getCellContent: function(row) {
+      	return row.get('user');
+      }
+    });
+    var statusColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Status',
+      getCellContent: function(row) {
+      	return row.get('status');
+      }
+    });
+    var startTimeColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'SubmissionTime',
+      getCellContent: function(row) {
+      	return App.Helpers.date.dateFormat(row.get('startTime'));
+      }
+    });
+    var appIdColumn = Em.Table.ColumnDefinition.create({
+      textAlign: 'text-align-left',
+      headerCellName: 'Application Id',
+      getCellContent: function(row) {
+      	return row.get('applicationId');
+      }
+    });
+    return [idColumn, nameColumn, userColumn, statusColumn, startTimeColumn, appIdColumn];
+  }.property(),
+
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/ajax.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/ajax.js b/tez-ui/app/scripts/helpers/ajax.js
new file mode 100644
index 0000000..7b56135
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/ajax.js
@@ -0,0 +1,175 @@
+/**
+ * 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.
+ */
+
+/**
+ * Config for each ajax-request
+ *
+ * Fields example:
+ *  mock - testMode url
+ *  real - real url (without API prefix)
+ *  type - request type (also may be defined in the format method)
+ *  format - function for processing ajax params after default formatRequest. May be called with one or two parameters (data, opt). Return ajax-params object
+ *  testInProduction - can this request be executed on production tests (used only in tests)
+ *
+ * @type {Object}
+ */
+var urls = {
+  load_dags: {
+    real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_DAG_ID/{queryParams}',
+    mock: '',
+    apiPrefix: ''
+  },
+
+  load_dag_details: {
+    real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_DAG_ID/{dag_id}?fields=events,otherinfo,primaryfilters',
+    mock: '',
+    apiPrefix: ''
+  },
+
+  load_vertex_details: {
+    real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_VERTEX_ID/?primaryFilter=TEZ_DAG_ID:{dag_id}'
+  }
+};
+
+/**
+ * Replace data-placeholders to its values
+ *
+ * @param {String} url
+ * @param {Object} data
+ * @return {String}
+ */
+var formatUrl = function (url, data) {
+  if (!url) return null;
+  var keys = url.match(/\{\w+\}/g);
+  keys = (keys === null) ? [] : keys;
+  if (keys) {
+    keys.forEach(function (key) {
+      var raw_key = key.substr(1, key.length - 2);
+      var replace;
+      if (!data || !data[raw_key]) {
+        replace = '';
+      }
+      else {
+        replace = data[raw_key];
+      }
+      url = url.replace(new RegExp(key, 'g'), replace);
+    });
+  }
+  return url;
+};
+
+/**
+ * this = object from config
+ * @return {Object}
+ */
+var formatRequest = function (data) {
+  var opt = {
+    type: this.type || 'GET',
+    dataType: 'json',
+    async: true,
+    headers: this.headers
+  };
+  if (App.get('testMode')) {
+    opt.url = formatUrl(this.mock ? this.mock : '', data);
+    opt.type = 'GET';
+  }
+  else {
+    var prefix = this.apiPrefix != null ? this.apiPrefix : App.get('urlPrefix');
+    opt.url = prefix + formatUrl(this.real, data);
+  }
+
+  if (this.format) {
+    jQuery.extend(opt, this.format(data, opt));
+  }
+  return opt;
+};
+
+/**
+ * Wrapper for all ajax requests
+ *
+ * @type {Object}
+ */
+var ajax = Em.Object.extend({
+  /**
+   * Send ajax request
+   *
+   * @param {Object} config
+   * @return {$.ajax} jquery ajax object
+   *
+   * config fields:
+   *  name - url-key in the urls-object *required*
+   *  sender - object that send request (need for proper callback initialization) *required*
+   *  data - object with data for url-format
+   *  beforeSend - method-name for ajax beforeSend response callback
+   *  success - method-name for ajax success response callback
+   *  error - method-name for ajax error response callback
+   */
+  send: function (config) {
+
+    Ember.assert('Ajax sender should be defined!', config.sender);
+    Ember.assert('Invalid config.name provided - ' + config.name, urls[config.name]);
+
+    var opt = {},
+      params = {clusterName: App.get('clusterName')};
+
+    if (config.data) {
+      jQuery.extend(params, config.data);
+    }
+
+    opt = formatRequest.call(urls[config.name], params);
+    opt.context = this;
+
+    // object sender should be provided for processing beforeSend, success, error and complete responses
+    opt.beforeSend = function (xhr) {
+      if (config.beforeSend) {
+        config.sender[config.beforeSend](opt, xhr, params);
+      }
+    };
+
+    opt.success = function (data) {
+      console.log("TRACE: The url is: " + opt.url);
+      if (config.success) {
+        config.sender[config.success](data, opt, params);
+      }
+    };
+
+    opt.error = function (request, ajaxOptions, error) {
+      if (config.error) {
+        config.sender[config.error](request, ajaxOptions, error, opt, params);
+      }
+    };
+
+    opt.complete = function (xhr, status) {
+      if (config.complete) {
+        config.sender[config.complete](xhr, status);
+      }
+    };
+
+    return $.ajax(opt);
+  },
+
+  formatUrlFor: function(endpoint, data) {
+    Ember.assert('Ajax sender should be defined!', !!endpoint);
+    Ember.assert('Invalid config.name provided - ' + endpoint, urls[endpoint]);
+
+    return formatUrl(urls[endpoint].real, data);
+  }
+
+});
+
+App.ajax = ajax.create({});

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/date.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/date.js b/tez-ui/app/scripts/helpers/date.js
new file mode 100644
index 0000000..9bf49ce
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/date.js
@@ -0,0 +1,216 @@
+/**
+ * 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.date = {
+
+  /**
+   * List of monthes short names
+   * @type {string[]}
+   */
+  dateMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+
+  /**
+   * List of days short names
+   * @type {string[]}
+   */
+  dateDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+  /**
+   * Add leading zero
+   *
+   * @param {string} time
+   * @returns {string}
+   * @method dateFormatZeroFirst
+   */
+  dateFormatZeroFirst: function (time) {
+    if (time < 10) return '0' + time;
+    return ""  + time;
+  },
+
+  /**
+   * Convert timestamp to date-string 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES'
+   *
+   * @param {number} timestamp
+   * @param {bool} showSeconds should seconds be added to result string
+   * @param {bool} showMilliseconds should miliseconds be added to result string (if <code>showSeconds</code> is false, milliseconds wouldn't be added)
+   * @return {*} date
+   * @method dateFormat
+   */
+  dateFormat: function (timestamp, showSeconds, showMilliseconds) {
+    if (!App.Helpers.number.isValidInt(timestamp)) {
+      return timestamp;
+    }
+    var format = 'ddd, MMM DD, YYYY HH:mm';
+    if (showSeconds) {
+      format += ':ss';
+      if (showMilliseconds) {
+        format += ':SSS';
+      }
+    }
+    return moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+  },
+
+  /**
+   * Convert timestamp to date-string 'DAY_OF_THE_WEEK MONTH DAY YEAR'
+   *
+   * @param {string} timestamp
+   * @return {string}
+   * @method dateFormatShort
+   */
+  dateFormatShort: function (timestamp) {
+    if (!App.Helpers.number.isValidInt(timestamp)) {
+      return timestamp;
+    }
+    var format = 'ddd MMM DD YYYY';
+    var date = moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+    var today = moment((new Date()).toISOString().replace('Z', '')).format(format);
+    if (date === today) {
+      return 'Today ' + (new Date(timestamp)).toLocaleTimeString();
+    }
+    return date;
+  },
+
+  /**
+   * Convert starTimestamp to 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES', except for the case: year equals 1969
+   *
+   * @param {string} startTimestamp
+   * @return {string} startTimeSummary
+   * @method startTime
+   */
+  startTime: function (startTimestamp) {
+    if (!App.Helpers.number.isValidInt(startTimestamp)) {
+      return '';
+    }
+    var startDate = new Date(startTimestamp);
+    var months = this.dateMonths;
+    var days = this.dateDays;
+    // generate start time
+    if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+      return 'Not started';
+    }
+    var startTimeSummary = '';
+    if (new Date(startTimestamp).setHours(0, 0, 0, 0) == new Date().setHours(0, 0, 0, 0)) { //today
+      startTimeSummary = 'Today ' + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+    } else {
+      startTimeSummary = days[startDate.getDay()] + ' ' + months[startDate.getMonth()] + ' ' +
+        this.dateFormatZeroFirst(startDate.getDate()) + ' ' + startDate.getFullYear() + ' '
+        + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+    }
+    return startTimeSummary;
+  },
+
+  /**
+   * Provides the duration between the given start and end timestamp. If start time
+   * not valid, duration will be ''. If end time is not valid, duration will
+   * be till now, showing 'Lasted for xxx secs'.
+   *
+   * @param {string} startTimestamp
+   * @param {string} endTimestamp
+   * @return {string} durationSummary
+   * @method durationSummary
+   */
+  durationSummary: function (startTimestamp, endTimestamp) {
+    // generate duration
+    var durationSummary = '';
+    var startDate = new Date(startTimestamp);
+    var endDate = new Date(endTimestamp);
+    if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+      // not started
+      return Em.I18n.t('common.na');
+    }
+    if (endDate.getFullYear() != 1969 && endTimestamp > 0) {
+      return '' + this.timingFormat(endTimestamp - startTimestamp, 1); //lasted for xx secs
+    } else {
+      // still running, duration till now
+      var t = new Date().getTime(),
+        time = (t - startTimestamp) < 0 ? 0 : (t - startTimestamp);
+      durationSummary = '' + this.timingFormat(time, 1);
+    }
+    return durationSummary;
+  },
+
+  /**
+   * Convert time in mseconds to
+   * 30 ms = 30 ms
+   * 300 ms = 300 ms
+   * 999 ms = 999 ms
+   * 1000 ms = 1.00 secs
+   * 3000 ms = 3.00 secs
+   * 35000 ms = 35.00 secs
+   * 350000 ms = 350.00 secs
+   * 999999 ms = 999.99 secs
+   * 1000000 ms = 16.66 mins
+   * 3500000 secs = 58.33 mins
+   *
+   * @param {number} time
+   * @param {bool} zeroValid for the case to show 0 when time is 0, not null
+   * @return {string|null} formatted date
+   * @method timingFormat
+   */
+  timingFormat: function (time, /* optional */ zeroValid) {
+    var intTime = parseInt(time);
+    if (zeroValid && intTime == 0) {
+      return 0 + ' secs';
+    }
+    if (!intTime) {
+      return null;
+    }
+    var timeStr = intTime.toString();
+    var lengthOfNumber = timeStr.length;
+    var oneMinMs = 60000;
+    var oneHourMs = 3600000;
+    var oneDayMs = 86400000;
+
+    if (lengthOfNumber < 4) {
+      return time + ' ms';
+    } else if (lengthOfNumber < 7) {
+      time = (time / 1000).toFixed(2);
+      return time + ' secs';
+    } else if (time < oneHourMs) {
+      time = (time / oneMinMs).toFixed(2);
+      return time + ' mins';
+    } else if (time < oneDayMs) {
+      time = (time / oneHourMs).toFixed(2);
+      return time + ' hours';
+    } else {
+      time = (time / oneDayMs).toFixed(2);
+      return time + ' days';
+    }
+  },
+
+  /**
+   * Provides the duration between the given start and end time. If start time
+   * is not given, duration will be 0. If end time is not given, duration will
+   * be till now.
+   *
+   * @param {Number} startTime Start time from epoch
+   * @param {Number} endTime End time from epoch
+   * @return {Number} duration
+   * @method duration
+   */
+  duration: function (startTime, endTime) {
+    var duration = 0;
+    if (startTime && startTime > 0) {
+      if (!endTime || endTime < 1) {
+        endTime = new Date().getTime();
+      }
+      duration = endTime - startTime;
+    }
+    return duration;
+  }
+};

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/handlebarHelpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/handlebarHelpers.js b/tez-ui/app/scripts/helpers/handlebarHelpers.js
new file mode 100644
index 0000000..5ed5e8e
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/handlebarHelpers.js
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+
+/**
+   * formats the given unix timestamp. returns NaN if its not a number.
+   *
+   * @param {number} unixtimestamp 
+   * @returns {string} 
+   * @method formatUnixTimestamp
+   */
+Em.Handlebars.helper('formatUnixTimestamp', function(timestamp) {
+	if (!App.Helpers.number.isValidInt(timestamp)) return 'NaN';
+	if (timestamp > 0) {
+		return App.Helpers.date.dateFormat(timestamp);
+	}
+	return '';
+});
+
+/*
+ * formats the duration.
+ *
+ * @param {duration} duration in milliseconds
+ * @return {Number}
+ * @method formatDuration
+ */
+Em.Handlebars.helper('formatDuration', function(startTime, endTime) {
+	// unixtimestamp is in seconds. javascript expects milliseconds.
+	if (endTime < startTime || !!endTime) {
+		end = new Date().getTime();
+	}
+
+	return App.Helpers.date.durationSummary(startTime, endTime);
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/number.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/number.js b/tez-ui/app/scripts/helpers/number.js
new file mode 100644
index 0000000..66b3e8e
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/number.js
@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+App.Helpers.number = {
+
+  /**
+   * Convert byte size to other metrics.
+   * 
+   * @param {Number} bytes to convert to string
+   * @param {Number} precision Number to adjust precision of return value. Default is 0.
+   * @param {String} parseType
+   *           JS method name for parse string to number. Default is "parseInt".
+   * @param {Number} multiplyBy bytes by this number if given. This is needed
+   *          as <code>null * 1024 = 0</null>
+   * @remarks The parseType argument can be "parseInt" or "parseFloat".
+   * @return {String} Returns converted value with abbreviation.
+   */
+  bytesToSize: function (bytes, precision, parseType, multiplyBy) {
+    if (isNaN(bytes)) bytes = 0;
+    if (Em.isNone(bytes)) {
+      return 'n/a';
+    } else {
+      if (arguments[2] === undefined) {
+        parseType = 'parseInt';
+      }
+      if (arguments[3] === undefined) {
+        multiplyBy = 1;
+      }
+      var value = bytes * multiplyBy;
+      var sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB' ];
+      var posttxt = 0;
+      while (value >= 1024) {
+        posttxt++;
+        value = value / 1024;
+      }
+      if (value === 0) {
+        precision = 0;
+      }
+      var parsedValue = window[parseType](value);
+      return parsedValue.toFixed(precision) + " " + sizes[posttxt];
+    }
+  },
+
+  /**
+   * Validates if the given string or number is an integer between the
+   * values of min and max (inclusive). The minimum and maximum
+   * checks are ignored if their valid is NaN.
+   *
+   * @method validateInteger
+   * @param {string|number} str - input string
+   * @param {string|number} [min]
+   * @param {string|number} [max]
+   */
+  validateInteger : function(str, min, max) {
+    if (Em.isNone(str) || (str + "").trim().length < 1) {
+      return Em.I18n.t('number.validate.empty');
+    }
+    str = (str + "").trim();
+    var number = parseInt(str);
+    if (isNaN(number)) {
+      return Em.I18n.t('number.validate.notValidNumber');
+    }
+    if (str.length != (number + "").length) {
+      // parseInt("1abc") returns 1 as integer
+      return Em.I18n.t('number.validate.notValidNumber');
+    }
+    if (!isNaN(min) && number < min) {
+      return Em.I18n.t('number.validate.lessThanMinumum').fmt(min);
+    }
+    if (!isNaN(max) && number > max) {
+      return Em.I18n.t('number.validate.moreThanMaximum').fmt(max);
+    }
+    return null;
+  },
+
+  /**
+   * Checks if the value is an integer or can be converted to an integer.
+   * a value of NaN returns false.
+   * @method: isValidInt
+   * @param {string|number} value to check
+   * @return {boolean} 
+   */
+  isValidInt: function(value) {
+    return value % 1 == 0;
+  }
+
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/translation.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/translation.js b/tez-ui/app/scripts/helpers/translation.js
new file mode 100644
index 0000000..ed32a39
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/translation.js
@@ -0,0 +1,119 @@
+/**
+ * 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',
+  'ok': 'Ok',
+  'cancel': 'Cancel',
+
+  'common.id': 'Entity Id',
+  'common.applicationId': 'Application Id',
+  'common.status':'Status',
+  'common.time.start': 'Start Time',
+  'common.time.end': 'End Time',
+  'common.name': 'Name',
+  'common.tasks':'Tasks',
+  'common.na': 'n/a',
+  'common.value': 'Value',
+  'common.user': 'User',
+  'common.time.duration': 'Duration',
+
+  'number.validate.empty': 'cannot be empty',
+  'number.validate.notValidNumber': 'not a valid number',
+  'number.validate.lessThanMinumum': 'value less than %@1',
+  'number.validate.moreThanMaximum': 'value greater than %@1',
+
+  'common.loading': 'Loading...',
+  'http.error.400': 'Unable to load data.',
+  'dags.nothingToShow': 'No Dags to display',
+
+  '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':'%@1 reads',
+  'jobs.hive.tez.writes':'%@1 writes',
+  'jobs.hive.tez.records.count':'%@1 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',
+
+  'app.loadingPlaceholder': 'Loading...',
+  'apps.item.dag.job': 'Job',
+  'apps.item.dag.jobId': 'Job Id',
+  'apps.item.dag.type': 'Job Type',
+  'apps.item.dag.status': 'Status',
+  'apps.item.dag.num_stages': 'Total Stages',
+  'apps.item.dag.stages': 'Tasks per Stage',
+  'apps.item.dag.maps': 'Maps',
+  'apps.item.dag.reduces': 'Reduces',
+  'apps.item.dag.input': 'Input',
+  'apps.item.dag.output': 'Output',
+  'apps.item.dag.duration': 'Duration',
+
+  'menu.item.jobs':'Jobs'
+
+};

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mappers/dag_mapper.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mappers/dag_mapper.js b/tez-ui/app/scripts/mappers/dag_mapper.js
new file mode 100644
index 0000000..aa862f9
--- /dev/null
+++ b/tez-ui/app/scripts/mappers/dag_mapper.js
@@ -0,0 +1,56 @@
+/**
+ * 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 jsonToDagMap = {
+	id: 'entity',
+	startTime: 'otherinfo.startTime', //TODO: what is the difference between this and the one in the otherinfo
+	endTime: 'otherinfo.endTime',
+	name: 'primaryfilters.dagName',
+	user: 'primaryfilters.user',
+	applicationId: 'otherinfo.applicationId',
+	status: 'otherinfo.status',
+	diagnostics: 'otherinfo.diagnostics'
+};
+
+App.Mappers.Dag = {
+	mapSingle : function(json) {
+		return Em.JsonMapper.map(json, jsonToDagMap);
+	},
+
+	mapMany : function(json) {
+		if (Array.isArray(json.entities)) {
+			return json.entities.map(this.mapSingle);
+		}
+
+		return [];
+	}
+}
+
+App.Mappers.Vertex = {
+	mapSingle: function(json) {
+		return Em.JsonMapper.map(json, jsonToVertexMap);
+	},
+
+	mapMany: function(json) {
+		if (Array.isArray(json.entities)) {
+			return json.entities.map(this.mapSingle);
+		}
+
+		return [];
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mixins/display_helpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mixins/display_helpers.js b/tez-ui/app/scripts/mixins/display_helpers.js
new file mode 100644
index 0000000..a89b597
--- /dev/null
+++ b/tez-ui/app/scripts/mixins/display_helpers.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.
+ */
+
+// TODO : document
+App.Helpers.DisplayHelper = Em.Mixin.create({
+	startTimeDisplay: function() {
+		var startTime = this.get('startTime');
+		return startTime > 0 ? App.Helpers.date.dateFormat(startTime) : '';
+	}.property('startTime'),
+
+	endtimeDisplay: function() {
+		var endTime = this.get('endTime');
+		return endTime > 0 ?  App.Helpers.date.dateFormat(endTime) : '';
+	}.property('endTime'),
+
+	duration: function() {
+		var startTime = this.get('startTime');
+    var endTime = this.get('endTime');
+    if(endTime < startTime || endTime == undefined) {
+      endTime =  new Date().getTime();
+    }
+    return App.Helpers.date.duration(startTime, endTime);
+	}.property('startTime', 'endTime')
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mixins/run_periodically.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mixins/run_periodically.js b/tez-ui/app/scripts/mixins/run_periodically.js
new file mode 100644
index 0000000..6a534a9
--- /dev/null
+++ b/tez-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: 10000,
+
+  /**
+   * 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/tez/blob/2f2739dd/tez-ui/app/scripts/models/TimelineRestAdapter.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/TimelineRestAdapter.js b/tez-ui/app/scripts/models/TimelineRestAdapter.js
new file mode 100644
index 0000000..2f82371
--- /dev/null
+++ b/tez-ui/app/scripts/models/TimelineRestAdapter.js
@@ -0,0 +1,124 @@
+var typeToPathMap = {
+	dag: 'TEZ_DAG_ID',
+	vertex: 'TEZ_VERTEX_ID'
+};
+
+App.TimelineRESTAdapter = DS.RESTAdapter.extend({
+	host: App.AtsBaseUrl,
+	namespace: 'ws/v1/timeline',
+
+	pathForType: function(type) {
+		return typeToPathMap[type];
+	},
+
+});
+
+App.TimelineSerializer = DS.RESTSerializer.extend({
+	primaryKey: 'entity',
+
+	attrs: {
+		key: 'entity',
+	},
+
+	extractSingle: function(store, primaryType, rawPayload, recordId) {
+		// rest serializer expects singular form of model as the root key.
+		var payload = {};
+		payload[primaryType.typeKey] = rawPayload;
+		return this._super(store, primaryType, payload, recordId);
+	},
+
+	extractArray: function(store, primaryType, rawPayload) {
+		// restserializer expects a plural of the model but TimelineServer returns 
+		// it in entities.
+		var payload = {};
+		payload[primaryType.typeKey.pluralize()] = rawPayload.entities;
+		return this._super(store, primaryType, payload);
+	},
+
+});
+
+
+var timelineJsonToDagMap = {
+  id: 'entity',
+  startTime: 'otherinfo.startTime',
+  endTime: 'otherinfo.endTime',
+  name: 'primaryfilters.dagName.0',
+  user: 'primaryfilters.user.0',
+  applicationId: 'otherinfo.applicationId',
+  status: 'otherinfo.status',
+  diagnostics: 'otherinfo.diagnostics',
+  counterGroups: 'counterGroups'
+};
+
+
+App.DagSerializer = App.TimelineSerializer.extend({
+  normalizePayload: function(rawPayload){
+    // no normalization required for multiple case (findAll)
+    if (!!rawPayload.dags) {
+      return rawPayload;
+    }
+
+    var dag = rawPayload.dag;
+    var counterGroups = dag.otherinfo.counters.counterGroups;
+    delete dag.otherinfo.counters;
+
+    // todo move counter parsing outside
+    // flatten the dag -> countergroup -> counter structure
+    var dagID = dag.entity;
+    var allCounters = [];
+
+    // put counter group id in the list
+    dag['counterGroups'] = counterGroups.map(function(cg) {
+      cg.entity = dagID + '/' + cg.counterGroupName;
+      cg.parentID = {
+        type: 'dag',
+        id: dagID
+      };
+
+      // replace the counters with thier id.
+      cg.counters = cg.counters.map(function(counter){
+        counter.entity = cg.entity + '/' + counter.counterName;
+        counter.cgID = cg.entity;
+        allCounters.push(counter);
+        return counter.entity;
+      });
+
+      return cg.entity;
+    });
+
+    var normalizedPayload = {
+      'dag': dag,
+      'counterGroups': counterGroups,
+      'counters': allCounters
+    };
+    return normalizedPayload;
+  },
+
+  normalize: function(type, hash, prop) {
+    return Em.JsonMapper.map(hash, timelineJsonToDagMap);
+  },
+});
+
+App.CounterGroupSerializer = DS.JSONSerializer.extend({
+  normalize: function(type, hash, prop) {
+    return {
+      'id': hash.entity,
+      'name': hash.counterGroupName,
+      'displayName': hash.counterGroupDisplayName,
+      'counters': hash.counters,
+      'parent': hash.parentID
+    };
+  }
+});
+
+App.CounterSerializer = DS.JSONSerializer.extend({
+  normalize: function(type, hash, prop) {
+    return {
+      'id': hash.entity,
+      'name': hash.counterName,
+      'displayName': hash.counterDisplayName,
+      'value': hash.counterValue,
+      'parent': hash.cgID
+    };
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/models/abstract_entity.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/abstract_entity.js b/tez-ui/app/scripts/models/abstract_entity.js
new file mode 100644
index 0000000..a3357ff
--- /dev/null
+++ b/tez-ui/app/scripts/models/abstract_entity.js
@@ -0,0 +1,27 @@
+/**
+ * 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.AbstractEntity = DS.Model.extend({
+	// type of the entity. should be one of App.EntityType
+	entityType: DS.attr('string')
+});
+
+App.EntityType = {
+	DAG: 'dag',
+	VERTEX: 'vertex',
+	TASK: 'task'
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/models/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/dag.js b/tez-ui/app/scripts/models/dag.js
new file mode 100644
index 0000000..7acf984
--- /dev/null
+++ b/tez-ui/app/scripts/models/dag.js
@@ -0,0 +1,231 @@
+/**
+ * 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.Dag = App.AbstractEntity.extend({
+
+  // start time of the entity
+  startTime: DS.attr('number'),
+
+  // end time of the entity
+  endTime: DS.attr('number'),
+
+	// set type to DAG
+	entityType: App.EntityType.DAG,
+
+	// Name of the dag.
+	name: DS.attr('string'),
+
+	// user name who ran this dag.
+	user: DS.attr('string'),
+
+	// application ID of this dag.
+	applicationId: DS.attr('string'),
+
+	// status
+	status: DS.attr('string'),
+
+	// diagnostics info if any.
+	diagnostics: DS.attr('string'),
+
+	//vertices: DS.hasMany('dagVertex'),
+
+	//edges: DS.hasMany('dagEdge'),
+
+  counterGroups: DS.hasMany('counterGroup', { inverse: 'parent' })
+});
+
+App.CounterGroup = DS.Model.extend({
+  name: DS.attr('string'),
+
+  displayName: DS.attr('string'),
+
+  counters: DS.hasMany('counter', { inverse: 'parent' }),
+
+  parent: DS.belongsTo('abstractEntity', { polymorphic: true })
+});
+
+App.Counter = DS.Model.extend({
+  name: DS.attr('string'),
+
+  displayName: DS.attr('string'),
+
+  value: DS.attr('number'),
+
+  parent: DS.belongsTo('counterGroup')
+});
+
+App.DagEdge = DS.Model.extend({
+
+  fromVertex: DS.belongsTo('dagVertex'),
+
+  toVertex: DS.belongsTo('dagVertex'),
+
+  /**
+   * Type of this edge connecting vertices. Should be one of constants defined
+   * in 'App.DagEdgeType'.
+   */
+  edgeType: DS.attr('string'),
+
+  dag: DS.belongsTo('dag')
+});
+
+App.DagVertex = DS.Model.extend({
+  name: DS.attr('string'),
+
+  dag: DS.belongsTo('dag'),
+
+  /**
+   * State of this vertex. Should be one of constants defined in
+   * App.DagVertexState.
+   */
+  state: DS.attr('string'),
+
+  /**
+   * Vertex type has to be one of the types defined in 'App.DagVertexType'
+   * @return {string}
+   */
+  type: DS.attr('string'),
+
+  /**
+   * A vertex can have multiple incoming edges.
+   */
+  incomingEdges: DS.hasMany('dagEdge'),
+
+  /**
+   * This vertex can have multiple outgoing edges.
+   */
+  outgoingEdges: DS.hasMany('dagEdge'),
+
+  startTime: DS.attr('number'),
+
+  endTime: DS.attr('number'),
+
+  /**
+   * Provides the duration of this job. If the job has not started, duration
+   * will be given as 0. If the job has not ended, duration will be till now.
+   *
+   * @return {Number} Duration in milliseconds.
+   */
+  duration: function () {
+    return App.Helpers.date.duration(this.get('startTime'), this.get('endTime'))
+  }.property('startTime', 'endTime'),
+
+  /**
+   * Each Tez vertex can perform arbitrary application specific computations
+   * inside. The application can provide a list of operations it has provided in
+   * this vertex.
+   *
+   * Array of strings. [{string}]
+   */
+  operations: DS.attr('array'),
+
+  /**
+   * Provides additional information about the 'operations' performed in this
+   * vertex. This is shown directly to the user.
+   */
+  operationPlan: DS.attr('string'),
+
+  /**
+   * Number of actual Map/Reduce tasks in this vertex
+   */
+  tasksCount: DS.attr('number'),
+
+  tasksNumber: function () {
+    return this.getWithDefault('tasksCount', 0);
+  }.property('tasksCount'),
+
+  /**
+   * Local filesystem usage metrics for this vertex
+   */
+  fileReadBytes: DS.attr('number'),
+
+  fileWriteBytes: DS.attr('number'),
+
+  fileReadOps: DS.attr('number'),
+
+  fileWriteOps: DS.attr('number'),
+
+  /**
+   * Spilled records
+   */
+  spilledRecords: DS.attr('number'),
+
+  /**
+   * HDFS usage metrics for this vertex
+   */
+  hdfsReadBytes: DS.attr('number'),
+
+  hdfsWriteBytes: DS.attr('number'),
+
+  hdfsReadOps: DS.attr('number'),
+
+  hdfsWriteOps: DS.attr('number'),
+
+  /**
+   * Record metrics for this vertex
+   */
+  recordReadCount: DS.attr('number'),
+
+  recordWriteCount: DS.attr('number'),
+
+  totalReadBytes: function () {
+    return this.get('fileReadBytes') + this.get('hdfsReadBytes');
+  }.property('fileReadBytes', 'hdfsReadBytes'),
+
+  totalWriteBytes: function () {
+    return this.get('fileWriteBytes') + this.get('hdfsWriteBytes');
+  }.property('fileWriteBytes', 'hdfsWriteBytes'),
+
+  totalReadBytesDisplay: function () {
+    return  App.Helpers.number.bytesToSize(this.get('totalReadBytes'));
+  }.property('totalReadBytes'),
+
+  totalWriteBytesDisplay: function () {
+    return  App.Helpers.number.bytesToSize(this.get('totalWriteBytes'));
+  }.property('totalWriteBytes'),
+
+  durationDisplay: function () {
+    return App.Helpers.date.timingFormat(this.get('duration'), true);
+  }.property('duration')
+});
+
+
+
+App.DagVertexState = {
+  NEW: "NEW",
+  INITIALIZING: "INITIALIZING",
+  INITED: "INITED",
+  RUNNING: "RUNNING",
+  SUCCEEDED: "SUCCEEDED",
+  FAILED: "FAILED",
+  KILLED: "KILLED",
+  ERROR: "ERROR",
+  TERMINATING: "TERMINATING",
+  JOBFAILED: "JOB FAILED"
+};
+
+App.DagVertexType = {
+  MAP: 'MAP',
+  REDUCE: 'REDUCE',
+  UNION: 'UNION'
+};
+
+App.DagEdgeType = {
+  SCATTER_GATHER: "SCATTER_GATHER",
+  BROADCAST: "BROADCAST",
+  CONTAINS: "CONTAINS"
+};

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/router.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/router.js b/tez-ui/app/scripts/router.js
new file mode 100644
index 0000000..28e279d
--- /dev/null
+++ b/tez-ui/app/scripts/router.js
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.Router.map(function() {
+	this.resource('dags', { path: '/' });
+	this.resource('dag', { path: '/dag/:dag_id'}, function() {
+		this.route('vertex'),
+		this.route('swimlane')
+	});
+	//this.resource('error', {path: '/error'});
+});
+
+/*
+App.ApplicationRoute = Em.Route.extend({
+	actions: {
+		//TODO: handle error and show proper error message.
+		error: function(error, transition) {
+			return;
+			this.transitionTo('error').then(function(newRoute) {
+				newRoute.controller.set('err', {
+					target: transition.targetName, 
+					statusText: error.message, 
+					responseText: error.stack
+				});
+				newRoute.controller.set('pageTitle', "Error");
+			});
+		}
+	}
+});*/
+
+App.DagsRoute = Em.Route.extend({
+	queryParams:  {
+		count: {
+			refreshModel: true,
+			replace: true
+		},
+		fromID: {
+			refreshModel: true,
+			replace: true
+		},
+		fromTS: {
+			refreshModel: true,
+			replace: true,
+		},
+		user: {
+			refreshModel: true,
+			replace: true
+		}
+	},
+
+	model: function(params) {
+		var controllerClass = this.controllerFor('dags');
+		var queryParams = controllerClass.getFilterParams(params);
+
+		//TODO remove this
+		this.store.unloadAll('dag');
+		return this.store.findQuery('dag', queryParams);
+	},
+
+	setupController: function(controller, model) {
+		this._super(controller, model);
+	},
+});
+
+App.DagRoute = Em.Route.extend({
+	model: function(params) {
+		return this.store.find('dag', params.dag_id);
+	},
+
+	setupController: function(controller, model) {
+		this._super(controller, model);
+	}
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/store.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/store.js b/tez-ui/app/scripts/store.js
new file mode 100644
index 0000000..21cae3e
--- /dev/null
+++ b/tez-ui/app/scripts/store.js
@@ -0,0 +1,21 @@
+/**
+ * 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.Store = DS.Store.extend();
+//App.ApplicationAdapter = DS.FixtureAdapter.extend();
+//App.ApplicationAdapter = App.TimelineRestAdapter.extend();

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/styles/main.css
----------------------------------------------------------------------
diff --git a/tez-ui/app/styles/main.css b/tez-ui/app/styles/main.css
new file mode 100644
index 0000000..67082e9
--- /dev/null
+++ b/tez-ui/app/styles/main.css
@@ -0,0 +1,70 @@
+
+/* misc helpers */
+.align-right {
+	float: right;
+}
+
+.align-left {
+	float: left;
+}
+
+.align-clear {
+	clear: both;
+}
+
+.margin-small {
+	margin: 15px;
+}
+
+.margin-small-horizontal {
+	margin: 15px 0px;
+}
+
+.margin-medium {
+	margin: 30px;
+}
+
+.type-table {
+	display: table;
+}
+
+.fill-full {
+	width: 100%;
+	height: 100%;
+}
+
+/* Navigation */
+.page-nav-link {
+	cursor: pointer;
+	color: black;
+}
+
+.page-nav-link.disabled {
+	pointer-events: none;
+	color:lightgray;
+}
+
+.pill-container {
+
+}
+
+.pill-container {
+	display: table-cell;
+	margin-right: 30px;
+}
+
+.pill-container a {
+	height: 30px !important;
+	padding: 5px 15px !important;
+}
+
+/* dag page */
+.detail-list {
+	table-layout: fixed;
+	overflow: hidden;
+	white-space: nowrap;
+}
+
+.detail-list td:first-child {
+	width:120px;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/application.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/application.hbs b/tez-ui/app/templates/application.hbs
new file mode 100644
index 0000000..7b35f42
--- /dev/null
+++ b/tez-ui/app/templates/application.hbs
@@ -0,0 +1,21 @@
+{{!
+ * 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 style="width: 95%; margin: 0 auto;">
+  {{outlet}}
+</div>

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/components/page-nav.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/components/page-nav.hbs b/tez-ui/app/templates/components/page-nav.hbs
new file mode 100644
index 0000000..04cad94
--- /dev/null
+++ b/tez-ui/app/templates/components/page-nav.hbs
@@ -0,0 +1,22 @@
+{{!
+* 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.
+}}
+
+{{!TODO: change to classes. and disable rather than hiding}}
+ <i {{bind-attr class=':fa-arrow-circle-o-left :fa :fa-3x :page-nav-link hasPrev:enabled:disabled'}} {{action 'gotoFirst'}}></i>
+ <i {{bind-attr class=':fa-arrow-circle-left :fa :fa-3x :page-nav-link hasPrev:enabled:disabled'}} {{action 'gotoPrev'}}></i>
+ <i {{bind-attr class=':fa-arrow-circle-right :fa :fa-3x :page-nav-link hasNext:enabled:disabled'}} {{action 'gotoNext'}}></i>

http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/dag.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/dag.hbs b/tez-ui/app/templates/dag.hbs
new file mode 100644
index 0000000..e0ccd05
--- /dev/null
+++ b/tez-ui/app/templates/dag.hbs
@@ -0,0 +1,36 @@
+{{partial "utils/pageHeader"}}
+{{#unless loading}}
+	<div class='margin-small-horizontal'>
+		{{#link-to 'dags'}}<i class='fa-arrow-circle-left fa fa-2x'>&nbsp;Dags</i>{{/link-to}}
+	</div>
+
+	<div class='type-table fill-full margin-small'>
+		<div class='align-left'>
+			{{!simple-kv-list data=this columns=commonDisplayProperties}}
+
+			<table class='detail-list'>
+				<tbody>
+					<tr>
+						<td>{{t common.id}}</td>
+						<td>{{id}}</td>
+					</tr>
+					<tr>
+						<td>{{t common.applicationId}}</td>
+						<td>{{applicationId}}</td>
+					</tr>
+				</tbody>
+			</table>
+
+		</div>
+
+		<div class='pill-container align-right'>
+			{{bs-pills contentBinding='childDisplayViews' selectedBinding='childDisplayViewSelected' size='lg'}}
+		</div>
+
+	</div>
+	<div class='margin-small'>
+		{{outlet}}
+	</div>
+{{else}}
+		{{partial 'utils/loadingSpinner'}}	
+{{/unless}}
\ No newline at end of file