http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/em-swimlane.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/em-swimlane.js 
b/tez-ui/src/main/webapp/app/components/em-swimlane.js
new file mode 100644
index 0000000..0c8b589
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/em-swimlane.js
@@ -0,0 +1,171 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import Processor from '../utils/processor';
+import Process from '../utils/process';
+
+export default Ember.Component.extend({
+
+  classNames: ["em-swimlane"],
+
+  processes: [],
+  processor: Processor.create(),
+
+  nameComponent: "em-swimlane-process-name",
+  visualComponent: "em-swimlane-process-visual",
+
+  tooltipContents: null,
+  focusedProcess: null,
+  scroll: 0,
+
+  consolidate: false,
+
+  zoom: 100,
+
+  startTime: Ember.computed("[email protected]", function () {
+    var startTime = this.get("processes.0.startEvent.time");
+    this.get("processes").forEach(function (process) {
+      var time = process.get("startEvent.time");
+      if(startTime > time){
+        startTime = time;
+      }
+    });
+    return startTime;
+  }),
+  endTime: Ember.computed("[email protected]", function () {
+    var endTime = this.get("processes.0.endEvent.time");
+    this.get("processes").forEach(function (process) {
+      var time = process.get("endEvent.time");
+      if(endTime < time){
+        endTime = time;
+      }
+    });
+    return endTime;
+  }),
+
+  processorSetup: Ember.on("init", Ember.observer("startTime", "endTime", 
"processes.length", function () {
+    this.get("processor").setProperties({
+      startTime: this.get("startTime"),
+      endTime: this.get("endTime"),
+      processCount: this.get("processes.length")
+    });
+  })),
+
+  didInsertElement: function () {
+    this.onZoom();
+    this.listenScroll();
+  },
+
+  onZoom: Ember.observer("zoom", function () {
+    var zoom = this.get("zoom");
+    this.$(".zoom-panel").css("width", `${zoom}%`);
+  }),
+
+  listenScroll: function () {
+    var that = this;
+    this.$(".process-visuals").scroll(function () {
+      that.set("scroll", Ember.$(this).scrollLeft());
+    });
+  },
+
+  willDestroy: function () {
+    // Release listeners
+  },
+
+  normalizedProcesses: Ember.computed("[email protected]", function () {
+    var processes = this.get("processes"),
+        normalizedProcesses,
+        idHash = {},
+        containsBlockers = false,
+        processor = this.get("processor");
+
+    // Validate and reset blocking
+    processes.forEach(function (process) {
+      if(!(process instanceof Process)) {
+        Ember.Logger.error("em-swimlane : Unknown type, must be of type 
Process");
+      }
+
+      if(process.get("blockers.length")) {
+        containsBlockers = true;
+      }
+      process.set("blocking", Ember.A());
+    });
+
+    if(containsBlockers) {
+      normalizedProcesses = [];
+
+      // Recreate blocking list
+      processes.forEach(function (process) {
+        var blockers = process.get("blockers");
+        if(blockers) {
+          blockers.forEach(function (blocker) {
+            blocker.get("blocking").push(process);
+          });
+        }
+      });
+
+      // Give an array of the processes in blocking order
+      processes.forEach(function (process) {
+        if(process.get("blocking.length") === 0) { // The root processes
+          normalizedProcesses.push(process);
+          normalizedProcesses.push.apply(normalizedProcesses, 
process.getAllBlockers());
+        }
+      });
+      normalizedProcesses.reverse();
+      normalizedProcesses = normalizedProcesses.filter(function (process, 
index) {
+        // Filters out the recurring processes in the list (after graph 
traversal), we just
+        // need the top processes
+        var id = process.get("_id");
+        if(idHash[id] === undefined) {
+          idHash[id] = index;
+        }
+        return idHash[id] === index;
+      });
+    }
+    else {
+      normalizedProcesses = processes;
+    }
+
+    // Set process colors & index
+    normalizedProcesses.forEach(function (process, index) {
+      process.setProperties({
+        color: processor.createProcessColor(index),
+        index: index
+      });
+    });
+
+    return Ember.A(normalizedProcesses);
+  }),
+
+  actions: {
+    showTooltip: function (type, process, options) {
+      this.set("tooltipContents", process.getTooltipContents(type, options));
+      this.set("focusedProcess", process);
+    },
+    hideTooltip: function () {
+      this.set("tooltipContents", null);
+      this.set("focusedProcess", null);
+    },
+    click: function (type, process, options) {
+      this.sendAction("click", type, process, options);
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/em-table-status-cell.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/em-table-status-cell.js 
b/tez-ui/src/main/webapp/app/components/em-table-status-cell.js
new file mode 100644
index 0000000..7751719
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/em-table-status-cell.js
@@ -0,0 +1,36 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+
+  content: null,
+
+  classNames: ["em-table-status-cell"],
+
+  statusName: Ember.computed("content", function () {
+    var status = this.get("content");
+
+    if(status) {
+      status = status.toString().dasherize();
+      status = "status-" + status;
+    }
+    return status;
+  }),
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/em-tooltip.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/em-tooltip.js 
b/tez-ui/src/main/webapp/app/components/em-tooltip.js
new file mode 100644
index 0000000..33e30cb
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/em-tooltip.js
@@ -0,0 +1,159 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+const TIP_PADDING = 15, // As in em-tooltip.css
+      FADE_TIME = 150;
+
+export default Ember.Component.extend({
+
+  title: null,
+  description: null,
+  properties: null,
+  contents: null,
+
+  classNames: ["em-tooltip"],
+  classNameBindings: ["arrowPos"],
+
+  x: 0,
+  y: 0,
+
+  _contents: null,
+  show: false,
+  arrowPos: null,
+
+  window: null,
+  tip: null,
+  bubbles: null,
+
+  _contentObserver: Ember.on("init", Ember.observer("title", "description", 
"properties", "contents", function () {
+    var contents,
+        tip = this.get("tip");
+
+    if(this.get("title") || this.get("description") || this.get("properties")){
+      contents = [{
+        title: this.get("title"),
+        description: this.get("description"),
+        properties: this.get("properties"),
+      }];
+    }
+    else if(Array.isArray(this.get("contents"))){
+      contents = this.get("contents");
+    }
+
+    this.set("show", false);
+    if(contents) {
+      if(tip) {
+        tip.hide();
+      }
+      this.set("_contents", contents);
+
+      Ember.run.later(this, function () {
+        this.set("bubbles", this.$(".bubble"));
+        this.set("show", true);
+        this.renderTip();
+      });
+    }
+    else if(tip){
+      tip.stop(true).fadeOut(FADE_TIME);
+    }
+  })),
+
+  didInsertElement: function () {
+    this.setProperties({
+      window: Ember.$(window),
+      tip: this.$(),
+    });
+    Ember.$(document).on("mousemove", this, this.onMouseMove);
+  },
+
+  willDestroyElement: function () {
+    Ember.$(document).off("mousemove", this.onMouseMove);
+  },
+
+  onMouseMove: function (event) {
+    event.data.setProperties({
+      x: event.clientX,
+      y: event.clientY
+    });
+
+    if(Ember.get(event, "data.tip")) {
+      event.data.renderTip();
+    }
+  },
+
+  getBubbleOffset: function (x, bubbleElement, winWidth) {
+    var bubbleWidth = Math.max(bubbleElement.width(), 0),
+        bubbleOffset = bubbleWidth >> 1;
+
+    if(x - bubbleOffset - TIP_PADDING < 0) {
+      bubbleOffset = x - TIP_PADDING;
+    }
+    else if(x + TIP_PADDING + bubbleOffset > winWidth) {
+      bubbleOffset = x - (winWidth - bubbleWidth) + TIP_PADDING;
+    }
+
+    return -bubbleOffset;
+  },
+
+  renderTip: function () {
+    if(this.get("show")) {
+      let x = this.get("x"),
+          y = this.get("y"),
+
+          winHeight = this.get("window").height(),
+          winWidth = this.get("window").width(),
+
+          showAbove = y < (winHeight >> 1),
+
+          that = this,
+          tip = this.get("tip");
+
+      if(x > TIP_PADDING && x < winWidth - TIP_PADDING) {
+        if(!showAbove) {
+          y -= tip.height();
+          this.set("arrowPos", "below");
+        }
+        else {
+          this.set("arrowPos", "above");
+        }
+      }
+      else {
+        this.set("arrowPos", null);
+      }
+
+      tip.css({
+        left: `${x}px`,
+        top: `${y}px`,
+      });
+
+      tip.fadeIn({
+        duration: FADE_TIME,
+        start: function () {
+          that.get("bubbles").each(function () {
+            var bubble = Ember.$(this),
+                bubbleOffset = that.getBubbleOffset(x, bubble, winWidth);
+            bubble.css("left", `${bubbleOffset}px`);
+          });
+        }
+      });
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/error-bar.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/error-bar.js 
b/tez-ui/src/main/webapp/app/components/error-bar.js
new file mode 100644
index 0000000..1086010
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/error-bar.js
@@ -0,0 +1,109 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+const DISPLAY_TIME = 30 * 1000;
+
+export default Ember.Component.extend({
+
+  error: null,
+
+  visible: false,
+  detailsAvailable: false,
+
+  classNames: ['error-bar'],
+  classNameBindings: ['visible', 'detailsAvailable'],
+
+  code: null,
+  message: null,
+  details: null,
+  stack: null,
+
+  showDetails: false,
+
+  displayTimerId: 0,
+
+  _errorObserver: Ember.observer("error", function () {
+    var error = this.get("error"),
+
+        code = Ember.get(error, "errors.0.status"),
+        title = Ember.get(error, "errors.0.title"),
+        message = error.message || "Error",
+        details = Ember.get(error, "errors.0.detail") || "",
+        stack = error.stack,
+        lineEndIndex = Math.min(message.indexOf('\n'), message.indexOf('<br'));
+
+    if(code === "0") {
+      code = "";
+    }
+
+    if(title) {
+      message += ". " + title;
+    }
+
+    if(lineEndIndex > 0) {
+      if(details) {
+        details = "\n" + details;
+      }
+      details = message.substr(lineEndIndex) + details;
+      message = message.substr(0, lineEndIndex);
+    }
+
+    if(details) {
+      details += "\n";
+    }
+
+    if(error) {
+      this.setProperties({
+        code: code,
+        message: message,
+        details: details,
+        stack: stack,
+
+        detailsAvailable: !!(details || stack),
+        visible: true
+      });
+
+      this.clearTimer();
+      this.set("displayTimerId", setTimeout(this.close.bind(this), 
DISPLAY_TIME));
+    }
+    else {
+      this.close();
+    }
+  }),
+
+  clearTimer: function () {
+    clearTimeout(this.get("displayTimerId"));
+  },
+  close: function () {
+    this.set("visible", false);
+    this.clearTimer();
+  },
+
+  actions: {
+    toggleDetailsDisplay: function () {
+      this.toggleProperty("showDetails");
+      this.clearTimer();
+    },
+    close: function () {
+      this.close();
+    }
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/stats-link.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/stats-link.js 
b/tez-ui/src/main/webapp/app/components/stats-link.js
new file mode 100644
index 0000000..0fb56df
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/stats-link.js
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  value: null,
+  routeName: null,
+  statsType: null,
+
+  searchText: Ember.computed.oneWay("statsType"),
+  _statsType: Ember.computed("statsType", function () {
+    var type = this.get("statsType");
+    if(type) {
+      return Ember.String.capitalize(type.toLowerCase());
+    }
+  })
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/tab-n-refresh.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/tab-n-refresh.js 
b/tez-ui/src/main/webapp/app/components/tab-n-refresh.js
new file mode 100644
index 0000000..6ac6454
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/tab-n-refresh.js
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  init: function () {
+    this._super();
+    this.setApplication();
+  },
+
+  autoRefreshEnabled: false,
+  autoRefreshVisible: true,
+
+  setApplication: function () {
+    var application = 
this.get("targetObject.container").lookup('controller:application');
+    this.set("application", application);
+  },
+
+  autoRefreshObserver: Ember.observer("autoRefreshEnabled", function () {
+    this.get('targetObject').send('autoRefreshChanged', 
this.get("autoRefreshEnabled"));
+  }),
+
+  normalizedTabs: Ember.computed("tabs", "application.currentPath", function 
() {
+    var tabs = this.get("tabs") || [],
+        activeRouteName = this.get("application.currentPath");
+
+    return tabs.map(function (tab) {
+      return {
+        text: tab.text,
+        routeName: tab.routeName,
+        active: tab.routeName === activeRouteName
+      };
+    });
+  }),
+
+  actions: {
+    refresh: function () {
+      this.get('targetObject').send('reload');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/table-controls.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/table-controls.js 
b/tez-ui/src/main/webapp/app/components/table-controls.js
new file mode 100644
index 0000000..9923711
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/table-controls.js
@@ -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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  classNames: ['table-controls'],
+
+  actions: {
+    cogClicked: function () {
+      this.get('targetObject.targetObject').send('openColumnSelector');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/components/zip-download-modal.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/components/zip-download-modal.js 
b/tez-ui/src/main/webapp/app/components/zip-download-modal.js
new file mode 100644
index 0000000..c55b34e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/components/zip-download-modal.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.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  classNames: ['zip-download-modal'],
+  content: null,
+
+  _onSuccess: Ember.observer("content.downloader.succeeded", function () {
+    if(this.get("content.downloader.succeeded")) {
+      Ember.run.later(this, "close");
+    }
+  }),
+
+  close: function () {
+    Ember.$(".simple-modal").modal("hide");
+  },
+
+  actions: {
+    cancel: function () {
+      var downloader = this.get("content.downloader");
+      if(downloader) {
+        downloader.cancel();
+      }
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/abstract.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/abstract.js 
b/tez-ui/src/main/webapp/app/controllers/abstract.js
new file mode 100644
index 0000000..863a63f
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/abstract.js
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import NameMixin from '../mixins/name';
+
+export default Ember.Controller.extend(NameMixin, {
+  // Must be set by inheriting classes
+  breadcrumbs: null,
+
+  // Must be set from abstract route
+  loadTime: null,
+  isLoading: false,
+
+  init: function () {
+    this._super();
+    Ember.run.later(this, "setBreadcrumbs");
+  },
+
+  loaded: Ember.computed("model", "isLoading", function () {
+    return this.get("model") && !this.get("isLoading");
+  }),
+
+  crumbObserver: Ember.observer("breadcrumbs", function () {
+    Ember.run.later(this, "setBreadcrumbs");
+  }),
+
+  setBreadcrumbs: function () {
+    var crumbs = {},
+        name = this.get("name");
+    crumbs[name] = this.get("breadcrumbs");
+    this.send("setBreadcrumbs", crumbs);
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/app.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/app.js 
b/tez-ui/src/main/webapp/app/controllers/app.js
new file mode 100644
index 0000000..f379e80
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/app.js
@@ -0,0 +1,44 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import ParentController from './parent';
+
+export default ParentController.extend({
+  breadcrumbs: Ember.computed("model.appID", "model.app.name", function () {
+    var name = this.get("model.app.name") || this.get("model.appID");
+
+    return [{
+      text: `Application [ ${name} ]`,
+      routeName: "app.index",
+      model: this.get("model.entityID")
+    }];
+  }),
+
+  tabs: [{
+    text: "Application Details",
+    routeName: "app.index"
+  }, {
+    text: "DAGs",
+    routeName: "app.dags"
+  }, {
+    text: "Configurations",
+    routeName: "app.configs"
+  }]
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/app/configs.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/app/configs.js 
b/tez-ui/src/main/webapp/app/controllers/app/configs.js
new file mode 100644
index 0000000..838abc1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/app/configs.js
@@ -0,0 +1,60 @@
+/*global more*/
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import TableController from '../table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+var MoreObject = more.Object;
+
+export default TableController.extend({
+  searchText: "tez",
+
+  breadcrumbs: [{
+    text: "Configurations",
+    routeName: "app.configs",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'configName',
+    headerTitle: 'Configuration Name',
+    contentPath: 'configName',
+  }, {
+    id: 'configValue',
+    headerTitle: 'Configuration Value',
+    contentPath: 'configValue',
+  }]),
+
+  configs: Ember.computed("model.configs", function () {
+    var configs = this.get("model.configs"),
+        configRows = [];
+
+    if(configs) {
+      MoreObject.forEach(configs, function (key, value) {
+        configRows.push(Ember.Object.create({
+          configName: key,
+          configValue: value
+        }));
+      });
+    }
+
+    return Ember.A(configRows);
+  })
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/app/dags.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/app/dags.js 
b/tez-ui/src/main/webapp/app/controllers/app/dags.js
new file mode 100644
index 0000000..e0c5e68
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/app/dags.js
@@ -0,0 +1,98 @@
+/**
+ * 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.
+ */
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+  breadcrumbs: [{
+    text: "DAGs",
+    routeName: "app.dags",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'name',
+    headerTitle: 'Dag Name',
+    contentPath: 'name',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "dag",
+        model: row.get("entityID"),
+        text: row.get("name")
+      };
+    }
+  },{
+    id: 'entityID',
+    headerTitle: 'Id',
+    contentPath: 'entityID'
+  },{
+    id: 'submitter',
+    headerTitle: 'Submitter',
+    contentPath: 'submitter'
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'status',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'queue',
+    headerTitle: 'Queue',
+    contentPath: 'queue'
+  },{
+    id: 'callerID',
+    headerTitle: 'Context ID',
+    contentPath: 'callerID'
+  },{
+    id: 'logs',
+    headerTitle: 'Logs',
+    contentPath: 'containerLogs',
+    cellComponentName: "em-table-linked-cell",
+    cellDefinition: {
+      target: "_blank"
+    }
+  }]),
+
+  getCounterColumns: function () {
+    return 
this._super().concat(this.get('env.app.tables.defaultColumns.dagCounters'));
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/app/index.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/app/index.js 
b/tez-ui/src/main/webapp/app/controllers/app/index.js
new file mode 100644
index 0000000..7f7bc78
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/app/index.js
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+import PageController from '../page';
+
+export default PageController.extend({
+
+  trackingURL: Ember.computed("model.appID", function () {
+    return [
+      this.get("hosts.rm"),
+      this.get("env.app.namespaces.web.rm"),
+      "app",
+      this.get("model.appID")
+    ].join("/");
+  })
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/application.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/application.js 
b/tez-ui/src/main/webapp/app/controllers/application.js
new file mode 100644
index 0000000..4911a16
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/application.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.
+ */
+
+import Ember from 'ember';
+
+const BREADCRUMB_PREFIX = [{
+  text: "All DAGs",
+  routeName: 'application'
+}];
+
+export default Ember.Controller.extend({
+  breadcrumbs: null,
+  appError: null,
+
+  prefixedBreadcrumbs: Ember.computed("breadcrumbs", function () {
+    var prefix = BREADCRUMB_PREFIX,
+    breadcrumbs = this.get('breadcrumbs');
+
+    if(Array.isArray(breadcrumbs)) {
+      prefix = prefix.concat(breadcrumbs);
+    }
+
+    return prefix;
+  })
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/attempt.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/attempt.js 
b/tez-ui/src/main/webapp/app/controllers/attempt.js
new file mode 100644
index 0000000..b10fc7f
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/attempt.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.
+ */
+
+import Ember from 'ember';
+
+import ParentController from './parent';
+
+export default ParentController.extend({
+  breadcrumbs: Ember.computed("model.dag", function () {
+    var dagName = this.get("model.dag.name"),
+        vertexName = this.get("model.vertexName"),
+        taskIndex = this.get("model.taskIndex"),
+        attemptNo = this.get("model.index");
+
+    return [{
+      text: `DAG [ ${dagName} ]`,
+      routeName: "dag.index",
+      model: this.get("model.dagID")
+    },{
+      text: `Vertex [ ${vertexName} ]`,
+      routeName: "vertex.index",
+      model: this.get("model.vertexID")
+    },{
+      text: `Task [ ${taskIndex} ]`,
+      routeName: "task.index",
+      model: this.get("model.taskID")
+    },{
+      text: `Attempt [ ${attemptNo} ]`,
+      routeName: "attempt.index",
+      model: this.get("model.entityID")
+    }];
+  }),
+
+  tabs: [{
+    text: "Attempt Details",
+    routeName: "attempt.index"
+  },{
+    text: "Attempt Counters",
+    routeName: "attempt.counters"
+  }]
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/attempt/counters.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/attempt/counters.js 
b/tez-ui/src/main/webapp/app/controllers/attempt/counters.js
new file mode 100644
index 0000000..1a93c80
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/attempt/counters.js
@@ -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.
+ */
+
+import CountersTableController from '../counters-table';
+
+export default CountersTableController.extend({
+  breadcrumbs: [{
+    text: "Attempt Counters",
+    routeName: "attempt.counters",
+  }],
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/attempt/index.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/attempt/index.js 
b/tez-ui/src/main/webapp/app/controllers/attempt/index.js
new file mode 100644
index 0000000..9745328
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/attempt/index.js
@@ -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.
+ */
+
+import PageController from '../page';
+
+export default PageController.extend({
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/counters-table.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/counters-table.js 
b/tez-ui/src/main/webapp/app/controllers/counters-table.js
new file mode 100644
index 0000000..42361b4
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/counters-table.js
@@ -0,0 +1,74 @@
+/*global more*/
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import TableController from './table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+var MoreObject = more.Object;
+
+export default TableController.extend({
+  counters: Ember.A(),
+  countersCount: 0, // Because Ember.Array doesn't handle length well
+
+  columns: ColumnDefinition.make([{
+    id: 'groupName',
+    headerTitle: 'Group Name',
+    contentPath: 'groupName',
+  }, {
+    id: 'counterName',
+    headerTitle: 'Counter Name',
+    contentPath: 'counterName',
+  }, {
+    id: 'counterValue',
+    headerTitle: 'Counter Value',
+    contentPath: 'counterValue',
+    observePath: true
+  }]),
+
+  _countersObserver: Ember.observer("model.counterGroupsHash", function () {
+    var counterGroupsHash = this.get("model.counterGroupsHash"),
+        counters = this.get("counters"),
+        counterIndex = 0;
+
+    if(counterGroupsHash) {
+      MoreObject.forEach(counterGroupsHash, function (groupName, countersHash) 
{
+        if(countersHash) {
+          MoreObject.forEach(countersHash, function (counterName, 
counterValue) {
+            let counterRow = counters.get(counterIndex);
+            if(!counterRow) {
+              counterRow = Ember.Object.create();
+              counters.push(counterRow);
+            }
+
+            counterRow.setProperties({
+              groupName: groupName,
+              counterName: counterName,
+              counterValue: counterValue
+            });
+            counterIndex++;
+          });
+        }
+      });
+    }
+
+    this.set("countersCount", counterIndex);
+  })
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag.js 
b/tez-ui/src/main/webapp/app/controllers/dag.js
new file mode 100644
index 0000000..46200b8
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag.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.
+ */
+
+import Ember from 'ember';
+
+import ParentController from './parent';
+
+export default ParentController.extend({
+  breadcrumbs: Ember.computed("model", function () {
+    var name = this.get("model.name");
+
+    return [{
+      text: `DAG [ ${name} ]`,
+      routeName: "dag.index.index",
+      model: this.get("model.entityID")
+    }];
+  }),
+
+  tabs: [{
+    text: "DAG Details",
+    routeName: "dag.index.index"
+  }, {
+    text: "DAG Counters",
+    routeName: "dag.counters"
+  }, {
+    text: "Graphical View",
+    routeName: "dag.graphical"
+  }, {
+    text: "All Vertices",
+    routeName: "dag.vertices"
+  }, {
+    text: "All Tasks",
+    routeName: "dag.tasks"
+  }, {
+    text: "All Task Attempts",
+    routeName: "dag.attempts"
+  }, {
+    text: "Vertex Swimlane",
+    routeName: "dag.swimlane"
+  }]
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/attempts.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/attempts.js 
b/tez-ui/src/main/webapp/app/controllers/dag/attempts.js
new file mode 100644
index 0000000..9f9ba15
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/attempts.js
@@ -0,0 +1,120 @@
+/**
+ * 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.
+ */
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+  breadcrumbs: [{
+    text: "All Task Attempts",
+    routeName: "dag.attempts",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'index',
+    headerTitle: 'Attempt No',
+    contentPath: 'index',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "attempt",
+        model: row.get("entityID"),
+        text: row.get("index")
+      };
+    }
+  },{
+    id: 'taskIndex',
+    headerTitle: 'Task Index',
+    contentPath: 'taskIndex',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "task",
+        model: row.get("taskID"),
+        text: row.get("taskIndex")
+      };
+    }
+  },{
+    id: 'vertexName',
+    headerTitle: 'Vertex Index',
+    contentPath: 'vertexName',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "vertex",
+        model: row.get("vertexID"),
+        text: row.get("vertexName")
+      };
+    }
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'status',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'containerID',
+    headerTitle: 'Container',
+    contentPath: 'containerID'
+  },{
+    id: 'nodeID',
+    headerTitle: 'Node',
+    contentPath: 'nodeID'
+  }, {
+    id: 'log',
+    headerTitle: 'Log',
+    contentPath: 'logURL',
+    cellComponentName: 'em-table-linked-cell',
+    cellDefinition: {
+      target: "_blank"
+    },
+    getCellContent: function (row) {
+      return [{
+        href: row.get("logURL"),
+        text: "View"
+      }, {
+        href: row.get("logURL"),
+        text: "Download",
+        download: true
+      }];
+    }
+  }])
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/counters.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/counters.js 
b/tez-ui/src/main/webapp/app/controllers/dag/counters.js
new file mode 100644
index 0000000..c54fd7e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/counters.js
@@ -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.
+ */
+
+import CountersTableController from '../counters-table';
+
+export default CountersTableController.extend({
+  breadcrumbs: [{
+    text: "DAG Counters",
+    routeName: "dag.counters",
+  }],
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/graphical.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/graphical.js 
b/tez-ui/src/main/webapp/app/controllers/dag/graphical.js
new file mode 100644
index 0000000..535f32b
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/graphical.js
@@ -0,0 +1,174 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+
+  columnSelectorTitle: 'Customize vertex tooltip',
+
+  breadcrumbs: [{
+    text: "Graphical View",
+    routeName: "dag.graphical",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'name',
+    headerTitle: 'Vertex Name',
+    contentPath: 'name',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "vertex",
+        model: row.get("entityID"),
+        text: row.get("name")
+      };
+    }
+  },{
+    id: 'entityID',
+    headerTitle: 'Vertex Id',
+    contentPath: 'entityID'
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'finalStatus',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'firstTaskStartTime',
+    headerTitle: 'First Task Start Time',
+    contentPath: 'firstTaskStartTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'totalTasks',
+    headerTitle: 'Tasks',
+    contentPath: 'totalTasks',
+  },{
+    id: 'succeededTasks',
+    headerTitle: 'Succeeded Tasks',
+    contentPath: 'succeededTasks',
+    observePath: true
+  },{
+    id: 'runningTasks',
+    headerTitle: 'Running Tasks',
+    contentPath: 'runningTasks',
+    observePath: true
+  },{
+    id: 'pendingTasks',
+    headerTitle: 'Pending Tasks',
+    contentPath: 'pendingTasks',
+    observePath: true
+  },{
+    id: 'processorClassName',
+    headerTitle: 'Processor Class',
+    contentPath: 'processorClassName',
+  }]),
+
+  redirect: function (details) {
+    switch(details.type) {
+      case 'vertex':
+        this.transitionToRoute('vertex.index', details.d.get('data.entityID'));
+      break;
+      case 'task':
+        this.transitionToRoute('vertex.tasks', details.d.get('data.entityID'));
+      break;
+      case 'io':
+      break;
+      case 'input':
+      break;
+      case 'output':
+      break;
+    }
+  },
+
+  actions: {
+    entityClicked: function (details) {
+
+      /**
+       * In IE 11 under Windows 7, mouse events are not delivered to the page
+       * anymore at all after a SVG use element that was under the mouse is
+       * removed from the DOM in the event listener in response to a mouse 
click.
+       * See https://connect.microsoft.com/IE/feedback/details/796745
+       *
+       * This condition and related actions must be removed once the bug is 
fixed
+       * in all supported IE versions
+       */
+      if(this.get("env.ENV.isIE")) {
+        var pageType = details.type === "io" ? "additionals" : details.type,
+            message = `You will be redirected to ${pageType} page`;
+
+        alert(message);
+      }
+      this.redirect(details);
+    }
+  },
+
+  viewData: Ember.computed("model", function () {
+    var model = this.get("model"),
+        dag, vertices, entities;
+
+    if(!model) {
+      return {};
+    }
+
+    dag = this.get('model.firstObject.dag');
+    vertices = this.get('model.firstObject.dag.vertices') || [];
+    entities = {};
+
+    model.forEach(function (vertexData) {
+      entities[vertexData.get('name')] = vertexData;
+    });
+
+    vertices.forEach(function (vertex) {
+      vertex.data = entities[vertex.vertexName];
+    });
+
+    return {
+      vertices: vertices,
+      edges: dag.get('edges'),
+      vertexGroups: dag.get('vertexGroups')
+    };
+  })
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/index.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/index.js 
b/tez-ui/src/main/webapp/app/controllers/dag/index.js
new file mode 100644
index 0000000..9745328
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/index.js
@@ -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.
+ */
+
+import PageController from '../page';
+
+export default PageController.extend({
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/index/index.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/index/index.js 
b/tez-ui/src/main/webapp/app/controllers/dag/index/index.js
new file mode 100644
index 0000000..2937a2b
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/index/index.js
@@ -0,0 +1,129 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import MultiTableController from '../../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+  columns: ColumnDefinition.make([{
+    id: 'name',
+    headerTitle: 'Vertex Name',
+    contentPath: 'name',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "vertex",
+        model: row.get("entityID"),
+        text: row.get("name")
+      };
+    }
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'status',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'totalTasks',
+    headerTitle: 'Total Tasks',
+    contentPath: 'totalTasks',
+    observePath: true
+  },{
+    id: 'succeededTasks',
+    headerTitle: 'Succeeded Tasks',
+    contentPath: 'succeededTasks',
+    observePath: true
+  },{
+    id: 'runningTasks',
+    headerTitle: 'Running Tasks',
+    contentPath: 'runningTasks',
+    observePath: true
+  },{
+    id: 'pendingTasks',
+    headerTitle: 'Pending Tasks',
+    contentPath: 'pendingTasks',
+    observePath: true
+  },{
+    id: 'failedTaskAttempts',
+    headerTitle: 'Failed Task Attempts',
+    contentPath: 'failedTaskAttempts',
+    observePath: true
+  },{
+    id: 'killedTaskAttempts',
+    headerTitle: 'Killed Task Attempts',
+    contentPath: 'killedTaskAttempts',
+    observePath: true
+  }]),
+
+  stats: Ember.computed("[email protected]", function () {
+    var vertices = this.get("model");
+
+    if(vertices) {
+      let succeededVertices = 0,
+          succeededTasks = 0,
+          totalTasks =0,
+
+          failedTasks = 0,
+          killedTasks = 0,
+          failedTaskAttempts = 0,
+          killedTaskAttempts = 0;
+
+      vertices.forEach(function (vertex) {
+        if(vertex.get("status") === "SUCCEEDED") {
+          succeededVertices++;
+        }
+
+        succeededTasks += vertex.get("succeededTasks");
+        totalTasks += vertex.get("totalTasks");
+
+        failedTasks += vertex.get("failedTasks");
+        killedTasks += vertex.get("killedTasks");
+
+        failedTaskAttempts += vertex.get("failedTaskAttempts");
+        killedTaskAttempts += vertex.get("killedTaskAttempts");
+      });
+
+      return {
+        succeededVertices: succeededVertices,
+        totalVertices: vertices.get("length"),
+
+        succeededTasks: succeededTasks,
+        totalTasks: totalTasks,
+
+        failedTasks: failedTasks,
+        killedTasks: killedTasks,
+
+        failedTaskAttempts: failedTaskAttempts,
+        killedTaskAttempts: killedTaskAttempts
+      };
+    }
+  }),
+
+  beforeSort: function () {
+    return true;
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js 
b/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js
new file mode 100644
index 0000000..259d72f
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/swimlane.js
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+import VertexProcess from '../../utils/vertex-process';
+
+import fullscreen from 'em-tgraph/utils/fullscreen';
+
+export default MultiTableController.extend({
+
+  zoom: 100,
+
+  columnSelectorTitle: 'Customize vertex tooltip',
+
+  breadcrumbs: [{
+    text: "Vertex Swimlane",
+    routeName: "dag.swimlane",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'entityID',
+    headerTitle: 'Vertex Id',
+    contentPath: 'entityID'
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'finalStatus',
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellDefinition: {
+      type: 'number',
+      format: '0%'
+    }
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellDefinition: {
+      type: 'date'
+    }
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellDefinition: {
+      type: 'date'
+    }
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'firstTaskStartTime',
+    headerTitle: 'First Task Start Time',
+    contentPath: 'firstTaskStartTime',
+    cellDefinition: {
+      type: 'date'
+    }
+  },{
+    id: 'totalTasks',
+    headerTitle: 'Tasks',
+    contentPath: 'totalTasks',
+  },{
+    id: 'succeededTasks',
+    headerTitle: 'Succeeded Tasks',
+    contentPath: 'succeededTasks',
+  },{
+    id: 'runningTasks',
+    headerTitle: 'Running Tasks',
+    contentPath: 'runningTasks',
+  },{
+    id: 'pendingTasks',
+    headerTitle: 'Pending Tasks',
+    contentPath: 'pendingTasks',
+  },{
+    id: 'processorClassName',
+    headerTitle: 'Processor Class',
+    contentPath: 'processorClassName',
+  }]),
+
+  processes: Ember.computed("model", function () {
+    var processes = [],
+        processHash = {},
+
+        dagPlanEdges = this.get("model.firstObject.dag.edges"),
+
+        that = this,
+        getVisibleProps = function () {
+          return that.get("visibleColumns");
+        };
+
+    // Create process instances for each vertices
+    this.get("model").forEach(function (vertex) {
+      var process = VertexProcess.create({
+        vertex: vertex,
+        getVisibleProps: getVisibleProps,
+        blockers: Ember.A()
+      });
+      processHash[vertex.get("name")] = process;
+      processes.push(process);
+    });
+
+    // Add process(vertex) dependencies based on dagPlan
+    if(dagPlanEdges) {
+      dagPlanEdges.forEach(function (edge) {
+        var process = processHash[edge.outputVertexName];
+        if(process && processHash[edge.inputVertexName]) {
+          process.blockers.push(processHash[edge.inputVertexName]);
+          process.edgeHash.set(edge.inputVertexName, edge);
+        }
+      });
+    }
+
+    return Ember.A(processes);
+  }),
+
+  actions: {
+    toggleFullscreen: function () {
+      var swimlaneElement = Ember.$(".swimlane-page").get(0);
+      if(swimlaneElement){
+        fullscreen.toggle(swimlaneElement);
+      }
+    },
+    click: function (type, process) {
+      this.transitionToRoute('vertex.index', process.get('vertex.entityID'));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/tasks.js 
b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
new file mode 100644
index 0000000..113d37e
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/tasks.js
@@ -0,0 +1,82 @@
+/**
+ * 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.
+ */
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+  breadcrumbs: [{
+    text: "All Tasks",
+    routeName: "dag.tasks",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'index',
+    headerTitle: 'Task Index',
+    contentPath: 'index',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "task",
+        model: row.get("entityID"),
+        text: row.get("index")
+      };
+    }
+  },{
+    id: 'vertexName',
+    headerTitle: 'Vertex Name',
+    contentPath: 'vertexName',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "vertex",
+        model: row.get("vertexID"),
+        text: row.get("vertexName")
+      };
+    }
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'status',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  }])
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dag/vertices.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dag/vertices.js 
b/tez-ui/src/main/webapp/app/controllers/dag/vertices.js
new file mode 100644
index 0000000..283f2d1
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dag/vertices.js
@@ -0,0 +1,122 @@
+/**
+ * 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.
+ */
+
+import MultiTableController from '../multi-table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+
+export default MultiTableController.extend({
+  breadcrumbs: [{
+    text: "All Vertices",
+    routeName: "dag.vertices",
+  }],
+
+  columns: ColumnDefinition.make([{
+    id: 'name',
+    headerTitle: 'Vertex Name',
+    contentPath: 'name',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "vertex",
+        model: row.get("entityID"),
+        text: row.get("name")
+      };
+    }
+  },{
+    id: 'entityID',
+    headerTitle: 'Vertex Id',
+    contentPath: 'entityID'
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'finalStatus',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'firstTaskStartTime',
+    headerTitle: 'First Task Start Time',
+    contentPath: 'firstTaskStartTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'totalTasks',
+    headerTitle: 'Tasks',
+    contentPath: 'totalTasks',
+  },{
+    id: 'succeededTasks',
+    headerTitle: 'Succeeded Tasks',
+    contentPath: 'succeededTasks',
+    observePath: true
+  },{
+    id: 'runningTasks',
+    headerTitle: 'Running Tasks',
+    contentPath: 'runningTasks',
+    observePath: true
+  },{
+    id: 'pendingTasks',
+    headerTitle: 'Pending Tasks',
+    contentPath: 'pendingTasks',
+    observePath: true
+  },{
+    id: 'processorClassName',
+    headerTitle: 'Processor Class',
+    contentPath: 'processorClassName',
+  }]),
+
+  beforeSort: function (columnDefinition) {
+    if(this._super(columnDefinition)) {
+      if(this.get("polling.isReady")) {
+        let columnName = columnDefinition.get("headerTitle");
+        switch(columnDefinition.get("contentPath")) {
+          case "succeededTasks":
+          case "runningTasks":
+          case "pendingTasks":
+            this.send("openModal", {
+              title: "Cannot sort!",
+              content: `Sorting on ${columnName} is disabled for running DAGs!`
+            });
+            return false;
+        }
+      }
+    }
+    return true;
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/dags.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/dags.js 
b/tez-ui/src/main/webapp/app/controllers/dags.js
new file mode 100644
index 0000000..b35c693
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/dags.js
@@ -0,0 +1,166 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import TableController from './table';
+import ColumnDefinition from 'em-table/utils/column-definition';
+import TableDefinition from 'em-table/utils/table-definition';
+
+export default TableController.extend({
+
+  queryParams: ["dagName", "dagID", "submitter", "status", "appID", 
"callerID"],
+  dagName: "",
+  dagID: "",
+  submitter: "",
+  status: "",
+  appID: "",
+  callerID: "",
+
+  // Because pageNo is a query param added by table controller, and in the 
current design
+  // we don't want page to be a query param as only the first page will be 
loaded first.
+  pageNum: 1,
+
+  breadcrumbs: [],
+
+  moreAvailable: false,
+  loadingMore: false,
+
+  headerComponentNames: ['dags-page-search', 'table-controls', 
'dags-pagination-ui'],
+
+  _definition: TableDefinition.create(),
+  // Using computed, as observer won't fire if the property is not used
+  definition: Ember.computed("dagName", "dagID", "submitter", "status",
+      "appID", "callerID", "pageNum", "moreAvailable", "loadingMore", function 
() {
+
+    var definition = this.get("_definition");
+
+    definition.setProperties({
+      dagName: this.get("dagName"),
+      dagID: this.get("dagID"),
+      submitter: this.get("submitter"),
+      status: this.get("status"),
+      appID: this.get("appID"),
+      callerID: this.get("callerID"),
+
+      pageNum: this.get("pageNum"),
+
+      moreAvailable: this.get("moreAvailable"),
+      loadingMore: this.get("loadingMore")
+    });
+
+    return definition;
+  }),
+
+  columns: ColumnDefinition.make([{
+    id: 'name',
+    headerTitle: 'Dag Name',
+    contentPath: 'name',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "dag",
+        model: row.get("entityID"),
+        text: row.get("name")
+      };
+    }
+  },{
+    id: 'entityID',
+    headerTitle: 'Id',
+    contentPath: 'entityID'
+  },{
+    id: 'submitter',
+    headerTitle: 'Submitter',
+    contentPath: 'submitter'
+  },{
+    id: 'status',
+    headerTitle: 'Status',
+    contentPath: 'status',
+    cellComponentName: 'em-table-status-cell',
+    observePath: true
+  },{
+    id: 'progress',
+    headerTitle: 'Progress',
+    contentPath: 'progress',
+    cellComponentName: 'em-table-progress-cell',
+    observePath: true
+  },{
+    id: 'startTime',
+    headerTitle: 'Start Time',
+    contentPath: 'startTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'endTime',
+    headerTitle: 'End Time',
+    contentPath: 'endTime',
+    cellComponentName: 'date-formatter',
+  },{
+    id: 'duration',
+    headerTitle: 'Duration',
+    contentPath: 'duration',
+    cellDefinition: {
+      type: 'duration'
+    }
+  },{
+    id: 'appID',
+    headerTitle: 'Application Id',
+    contentPath: 'appID',
+    cellComponentName: 'em-table-linked-cell',
+    getCellContent: function (row) {
+      return {
+        routeName: "app",
+        model: row.get("appID"),
+        text: row.get("appID")
+      };
+    }
+  },{
+    id: 'queue',
+    headerTitle: 'Queue',
+    contentPath: 'queue'
+  },{
+    id: 'callerID',
+    headerTitle: 'Caller ID',
+    contentPath: 'callerID'
+  },{
+    id: 'callerType',
+    headerTitle: 'Caller Type',
+    contentPath: 'callerType'
+  },{
+    id: 'logs',
+    headerTitle: 'Logs',
+    contentPath: 'containerLogs',
+    cellComponentName: "em-table-linked-cell",
+    cellDefinition: {
+      target: "_blank"
+    }
+  }]),
+
+  getCounterColumns: function () {
+    return 
this._super().concat(this.get('env.app.tables.defaultColumns.dagCounters'));
+  },
+
+  actions: {
+    search: function (properties) {
+      this.setProperties(properties);
+    },
+    pageChanged: function (pageNum) {
+      this.set("pageNum", pageNum);
+    },
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/multi-table.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/multi-table.js 
b/tez-ui/src/main/webapp/app/controllers/multi-table.js
new file mode 100644
index 0000000..3a11830
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/multi-table.js
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import TableController from './table';
+import CounterColumnDefinition from '../utils/counter-column-definition';
+
+export default TableController.extend({
+
+  _visibleColumnsObserver: Ember.on("init", Ember.observer("visibleColumns", 
function () {
+    Ember.run.later(this, "sendCountersChanged");
+  })),
+
+  sendCountersChanged: function () {
+    var visibleCounters = this.get("visibleColumns").filter(function 
(definition) {
+      return definition instanceof CounterColumnDefinition;
+    });
+    this.send("countersToPollChanged", visibleCounters);
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/page.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/page.js 
b/tez-ui/src/main/webapp/app/controllers/page.js
new file mode 100644
index 0000000..14ad6b5
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/page.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.
+ */
+
+import AbstractController from './abstract';
+
+export default AbstractController.extend({
+  // Any page specific
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/parent.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/parent.js 
b/tez-ui/src/main/webapp/app/controllers/parent.js
new file mode 100644
index 0000000..089ed77
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/parent.js
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import AbstractController from './abstract';
+
+export default AbstractController.extend({
+  polling: Ember.inject.service("pollster"),
+  actions: {
+    autoRefreshChanged: function (state) {
+      this.get("polling").set("active", state);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/13132ec7/tez-ui/src/main/webapp/app/controllers/table.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/controllers/table.js 
b/tez-ui/src/main/webapp/app/controllers/table.js
new file mode 100644
index 0000000..f0bce1c
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/controllers/table.js
@@ -0,0 +1,166 @@
+/*global more*/
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+
+import AbstractController from './abstract';
+import TableDefinition from 'em-table/utils/table-definition';
+import isIOCounter from '../utils/misc';
+
+import CounterColumnDefinition from '../utils/counter-column-definition';
+
+var MoreObject = more.Object;
+
+export default AbstractController.extend({
+  queryParams: ["rowCount", "searchText", "sortColumnId", "sortOrder", 
"pageNo"],
+  rowCount: 10,
+  searchText: "",
+  sortColumnId: "",
+  sortOrder: "",
+  pageNo: 1,
+
+  columns: [],
+
+  headerComponentNames: ['em-table-search-ui', 'table-controls', 
'em-table-pagination-ui'],
+
+  visibleColumnIDs: {},
+  columnSelectorTitle: 'Column Selector',
+  columnSelectorMessage: "",
+
+  polling: Ember.inject.service("pollster"),
+
+  definition: Ember.computed("model", function () {
+    return TableDefinition.create({
+      rowCount: this.get("rowCount"),
+      searchText: this.get("searchText"),
+      sortColumnId: this.get("sortColumnId"),
+      sortOrder: this.get("sortOrder"),
+      pageNo: this.get("pageNo")
+    });
+  }),
+
+  storageID: Ember.computed("name", function () {
+    return this.get("name") + ":visibleColumnIDs";
+  }),
+
+  initVisibleColumns: Ember.on("init", Ember.observer("columns", function () { 
//To reset on entity change
+    var visibleColumnIDs = this.get("localStorage").get(this.get("storageID")) 
|| {};
+
+    this.get('columns').forEach(function (config) {
+      if(visibleColumnIDs[config.id] !== false) {
+        visibleColumnIDs[config.id] = true;
+      }
+    });
+
+    this.set('visibleColumnIDs', visibleColumnIDs);
+  })),
+
+  beforeSort: function (columnDefinition) {
+    if(this.get("polling.isReady")) {
+      let columnName = columnDefinition.get("headerTitle");
+      switch(columnDefinition.get("contentPath")) {
+        case "counterGroupsHash":
+          columnName = "Counters";
+          /* falls through */
+        case "status":
+        case "progress":
+          this.send("openModal", {
+            title: "Cannot sort!",
+            content: `Sorting on ${columnName} is disabled for running DAGs!`
+          });
+          return false;
+      }
+    }
+    return true;
+  },
+
+  allColumns: Ember.computed("columns", function () {
+    var columns = this.get("columns"),
+        counters = this.getCounterColumns(),
+        beforeSort = this.get("beforeSort").bind(this);
+
+    columns = columns.concat(CounterColumnDefinition.make(counters));
+
+    columns.forEach(function (column) {
+      column.set("beforeSort", beforeSort);
+    });
+
+    return columns;
+  }),
+
+  visibleColumns: Ember.computed('visibleColumnIDs', 'allColumns', function() {
+    var visibleColumnIDs = this.visibleColumnIDs;
+    return this.get('allColumns').filter(function (column) {
+      return visibleColumnIDs[column.get("id")];
+    });
+  }),
+
+  getCounterColumns: function () {
+    return this.get('env.app.tables.defaultColumns.counters');
+  },
+
+  actions: {
+    searchChanged: function (searchText) {
+      this.set("searchText", searchText);
+    },
+    sortChanged: function (sortColumnId, sortOrder) {
+      this.setProperties({
+        sortColumnId,
+        sortOrder
+      });
+    },
+    rowCountChanged: function (rowCount) {
+      this.set("rowCount", rowCount);
+    },
+    pageChanged: function (pageNum) {
+      this.set("pageNo", pageNum);
+    },
+
+    rowsChanged: function (rows) {
+      this.send("setPollingRecords", rows);
+    },
+
+    // Column selection actions
+    openColumnSelector: function () {
+      this.send("openModal", "column-selector", {
+        title: this.get('columnSelectorTitle'),
+        targetObject: this,
+        content: {
+          message: this.get('columnSelectorMessage'),
+          columns: this.get('allColumns'),
+          visibleColumnIDs: this.get('visibleColumnIDs')
+        }
+      });
+    },
+    columnsSelected: function (visibleColumnIDs) {
+      var columnIDs = {};
+
+      MoreObject.forEach(visibleColumnIDs, function (key, value) {
+        if(!isIOCounter(key)) {
+          columnIDs[key] = value;
+        }
+      });
+
+      if(!MoreObject.equals(columnIDs, this.get("visibleColumnIDs"))) {
+        this.get("localStorage").set(this.get("storageID"), columnIDs);
+        this.set('visibleColumnIDs', columnIDs);
+      }
+    }
+  }
+});

Reply via email to