azkaban-aplcache

Details

diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index e89c6f9..ebc33f5 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -11,8 +11,11 @@
 		<script type="text/javascript" src="${context}/js/jquery.simplemodal.js"></script>
 		<script type="text/javascript" src="${context}/js/jquery.contextMenu.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.job.status.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.graph.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.exflow.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
 		<script type="text/javascript">
@@ -69,13 +72,13 @@
 					</div>
 					<div id="graphView">
 						<div class="relative">
-							<div id="jobList">
-								<div id="filterList">
-									<input id="filter" placeholder="  Job Filter" />
+							<div id="jobList" class="jobList">
+								<div id="filterList" class="filterList">
+									<input id="filter" class="filter" placeholder="  Job Filter" />
 								</div>
-								<div id="list">
+								<div id="list" class="list">
 								</div>
-								<div id="resetPanZoomBtn" class="btn5" >Reset Pan Zoom</div>
+								<div id="resetPanZoomBtn" class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
 							</div>
 							<div id="svgDiv" >
 								<svg id="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 96aaedf..f8096c1 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -14,7 +14,10 @@
 		<script type="text/javascript" src="${context}/js/azkaban.ajax.utils.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.nav.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.job.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.flow.graph.view.js"></script>
 		<script type="text/javascript" src="${context}/js/azkaban.flow.view.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.exflow.options.view.js"></script>
 		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
 		<script type="text/javascript">
 			var contextURL = "${context}";
@@ -54,7 +57,7 @@
 							<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
 						</div>
 						
-						<div id="executebtn" class="btn1">Execute...</div>
+						<div id="executebtn" class="btn1">Execute</div>
 						<div id="scheduleflowbtn" class="btn2 scheduleflow">Schedule Flow</div>
 					</div>
 					
@@ -67,16 +70,16 @@
 					</div>
 					<div id="graphView">
 						<div class="relative">
-							<div id="jobList">
-								<div id="filterList">
-									<input id="filter" placeholder="  Job Filter" />
+							<div id="jobList" class="jobList">
+								<div id="filterList" class="filterList">
+									<input id="filter" class="filter" placeholder="  Job Filter" />
 								</div>
-								<div id="list">
+								<div id="list" class="list">
 								</div>
-								<div id="resetPanZoomBtn" class="btn5" >Reset Pan Zoom</div>
+								<div id="resetPanZoomBtn" class="btn5 resetPanZoomBtn" >Reset Pan Zoom</div>
 							</div>
-							<div id="svgDiv" >
-								<svg id="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+							<div id="svgDiv" class="svgDiv">
+								<svg id="svgGraph" class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
 								</svg>
 							</div>
 						</div>
@@ -189,17 +192,59 @@
                 <div id="modalBackground" class="modalBackground2">
 	                <div id="executing-options" class="modal modalContainer2">
 	                	<a href='#' title='Close' class='modal-close'>x</a>
-	                		<h3>Executing Flow</h3>
+	                		<h3>Executing Flow Options</h3>
 	                		<div>
 	                			<ul class="optionsPicker">
-	                				<li>General Options</li>
-	                				<li>Flow Options</li>
+	                				<li id="generalOptions">General Options</li>
+	                				<li id="flowOptions">Flow Options</li>
 	                			</ul>
 	                		</div>
 	                		<div class="optionsPane">
-	                			<div class="generalPanel panel">
+	                			<div id="generalPanel" class="generalPanel panel">
+	                				<div>
+	                					<h4>Completion Actions</h4>
+		                				<dl>
+		                					<dt>Failure Action</dt>
+		                					<dd>
+		                						<select name="failureAction">
+		                							<option value="finishCurrent">Finish Current</option>
+		                							<option value="cancelImmediately">Cancel All</option>
+		                							<option value="finishPossible">Finish All Possible</option>
+		                						</select>
+											</dd>
+		                					<dt>Failure Email</dt>
+		                					<dd>
+		                						<textarea></textarea>
+		                					</dd>
+		                					<dt>Notify on Failure</dt>
+		                					<dd>
+		                						<input class="checkbox" type="checkbox" name="notify" value="first" checked >First Failure</input>
+		                						<input class="checkbox" type="checkbox" name="notify" value="last">Flow Stop</input>
+		                					</dd>
+		                					<dt>Success Email</dt>
+		                					<dd>
+		                						<textarea></textarea>
+		                					</dd>
+		                				</dl>
+	                				</div>
+	                				<div id="flowPropertyOverride">
+	                					<h4>Flow Property Override</h4>
+	                					<div class="tableDiv">
+		                					<table>
+		                						<thead>
+		                							<tr>
+			                							<th>Name</th>
+			                							<th>Value</th>
+			                						</tr>
+		                						</thead>
+		                						<tbody>
+		                							<tr id="addRow"><td id="addRow-col" colspan="2"><span class="addIcon"></span><a href="#">Add Row</a></td></tr>
+		                						</tbody>
+		                					</table>
+	                					</div>
+	                				</div>
 	                			</div>
-								<div class="graphPanel panel">
+								<div id="graphPanel" class="graphPanel panel">
 									<div class="jobList">
 										<div id="filterList2">
 											<input id="filter2" placeholder="  Job Filter" />

src/web/css/azkaban.css 141(+121 -20)

diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 45425e5..2d3fdb1 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -1344,6 +1344,15 @@ tr:hover td {
 	color: #CCC;
 }
 
+#executing-options ul.optionsPicker li.selected {
+	text-decoration: underline;
+	color: #000;
+}
+
+#executing-options ul.optionsPicker li.selected:hover {
+	color: #000;
+}
+
 #executing-options ul.optionsPicker li:hover {
 	color: #888;
 }
@@ -1351,7 +1360,7 @@ tr:hover td {
 #executing-options .optionsPane {
 	position: absolute;
 	top: 85px;
-	background-color: #000;
+	background-color: #FFF;
 	left: 0px;
 	right: 0px;
 	bottom: 0px;
@@ -1361,11 +1370,103 @@ tr:hover td {
 	position: absolute;
 	width: 100%;
 	top: 0px;
-	bottom: 60px;
+	bottom: 65px;
 }
 
 #executing-options .generalPanel.panel {
-	background-color: #ACC;
+	background-color: #F4F4F4;
+	padding-top: 15px;
+}
+
+#executing-options h3 {
+	margin-left: 20px;
+	font-size: 14pt;
+	border-bottom: 1px solid #CCC;
+}
+
+#executing-options h4 {
+	margin-left: 20px;
+	font-size: 12pt;
+	border-bottom: 1px solid #CCC;
+}
+
+#generalPanel {
+	overflow: auto;
+}
+
+#generalPanel dt {
+	width: 150px;
+	font-size: 10pt;
+	font-weight: bold;
+	margin-top: 5px;
+}
+
+#generalPanel textarea {
+	width: 500px;
+}
+
+#generalPanel table #addRow {
+	cursor: pointer;
+}
+
+#generalPanel table tr {
+	height: 24px;
+}
+
+#generalPanel table .editable {
+
+}
+
+#generalPanel table .editable input {
+	border: 1px solid #009FC9;
+	height: 16px;
+}
+
+#generalPanel table .name {
+	width: 40%;
+}
+
+#generalPanel span.addIcon {
+	display: block;
+	width: 16px;
+	height: 16px;
+	background-image: url("./images/addIcon.png");
+}
+
+#generalPanel span.removeIcon {
+	display: block;
+	visibility:hidden;
+	disabled: true;
+	width: 16px;
+	height: 16px;
+	background-image: url("./images/removeIcon.png");
+	cursor: pointer;
+}
+
+#generalPanel .editable:hover span.removeIcon {
+	visibility:visible;
+}
+
+#generalPanel {
+}
+
+#generalPanel span {
+	float: left;
+	margin-left: 5px;
+}
+
+#generalPanel dd {
+	font-size: 10pt;
+}
+
+#flowPropertyOverride {
+	clear: both;
+	padding-top: 30px;
+}
+
+#flowPropertyOverride .tableDiv {
+	padding-right: 20px;
+	padding-left: 20px;
 }
 
 #jobList {
@@ -1389,7 +1490,7 @@ tr:hover td {
 	bottom: 120px;
 }
 
-#resetPanZoomBtn {
+.resetPanZoomBtn {
 	position: absolute;
 	bottom: 90px;
 }
@@ -1407,74 +1508,74 @@ tr:hover td {
 	top: 100px;
 }
 
-#filter {
+.filter {
 	width: 100%;
 }
 
-#list ul {
+.list ul {
 	white-space: nowrap
 }
 
-#list ul li {
+.list ul li {
 	margin: 4px 5px;
 	border-bottom: 1px solid #EEE;
 	cursor: pointer;
 	background-position: 16px 0px;
 }
 
-#list ul li:hover{
+.list ul li:hover{
 	background-color: #E1E3E2;
 	color: #009FC9;
 }
 
-#list ul li.selected {
+.list ul li.selected {
 	background-color: #009FC9;
 	color: #EEE;
 }
 
-#list ul li.nodedisabled {
+.list ul li.nodedisabled {
 	opacity: 0.3;
 }
 
-#list ul li.DISABLED {
+.list ul li.DISABLED {
 	opacity: 0.3;
 }
 
-#list ul li.DISABLED .icon {
+.list ul li.DISABLED .icon {
 	background-position: 16px 0px;
 }
 
-#list ul li.READY .icon {
+.list ul li.READY .icon {
 	background-position: 16px 0px;
 }
 
-#list ul li.RUNNING .icon {
+.list ul li.RUNNING .icon {
 	background-position: 32px 0px;
 }
 
-#list ul li.SUCCEEDED .icon {
+.list ul li.SUCCEEDED .icon {
 	background-position: 48px 0px;
 }
 
-#list ul li.FAILED .icon {
+.list ul li.FAILED .icon {
 	background-position: 0px 0px;
 }
 
-#list ul li.KILLED .icon {
+.list ul li.KILLED .icon {
 	background-position: 0px 0px;
 }
 
-#list ul li a {
+.list ul li a {
 	font-size: 10pt;
 	margin-left: 5px;
 }
 
-#list ul li a span {
+.list ul li a span {
 	background-color: #FF0;
 	color: black;
 }
 
-#list ul li .icon {
+.list ul li .icon {
 	float: left;
 	width: 16px;
 	height: 16px;
diff --git a/src/web/css/images/addIcon.png b/src/web/css/images/addIcon.png
new file mode 100644
index 0000000..f079305
Binary files /dev/null and b/src/web/css/images/addIcon.png differ
diff --git a/src/web/css/images/removeIcon.png b/src/web/css/images/removeIcon.png
new file mode 100644
index 0000000..32132da
Binary files /dev/null and b/src/web/css/images/removeIcon.png differ
diff --git a/src/web/js/azkaban.exflow.options.view.js b/src/web/js/azkaban.exflow.options.view.js
new file mode 100644
index 0000000..1c19104
--- /dev/null
+++ b/src/web/js/azkaban.exflow.options.view.js
@@ -0,0 +1,138 @@
+var executeFlowView;
+azkaban.ExecuteFlowView = Backbone.View.extend({
+  	  events : {
+  	  	"click" : "closeEditingTarget",
+	    "click #execute-btn": "handleExecuteFlow",
+	    "click #execute-custom-btn": "handleCustomFlow",
+	    "click #cancel-btn": "handleCancelExecution",
+	    "click .modal-close": "handleCancelExecution",
+	    "click #generalOptions": "handleGeneralOptionsSelect",
+	    "click #flowOptions": "handleFlowOptionsSelect",
+	    "click #addRow": "handleAddRow",
+	    "click table .editable": "handleEditColumn",
+	    "click table .removeIcon": "handleRemoveColumn"
+	  },
+	  initialize: function(evt) {
+	  	 $('#executebtn').click( function() {
+	  	 	$('#modalBackground').show();
+	  	 	$('#executing-options').show();
+	  	 });
+	  	
+	  	 this.handleGeneralOptionsSelect();
+	  },
+	  handleCancelExecution: function(evt) {
+	  	var executeURL = contextURL + "/executor";
+		$('#modalBackground').hide();
+	  	$('#executing-options').hide();
+	  },
+	  handleGeneralOptionsSelect: function(evt) {
+	  	$('#flowOptions').removeClass('selected');
+	  	$('#generalOptions').addClass('selected');
+
+	  	$('#generalPanel').show();	  	
+	  	$('#graphPanel').hide();
+	  },
+	  handleFlowOptionsSelect: function(evt) {
+	  	$('#generalOptions').removeClass('selected');
+	  	$('#flowOptions').addClass('selected');
+
+	  	$('#graphPanel').show();	  	
+	  	$('#generalPanel').hide();
+	  },
+	  handleExecuteFlow: function(evt) {
+	  	var executeURL = contextURL + "/executor";
+		$.get(
+			executeURL,
+			{"project": projectName, "ajax":"executeFlow", "flow":flowName, "disabled":graphModel.get("disabled")},
+			function(data) {
+				if (data.error) {
+					alert(data.error);
+				}
+				else {
+					var redirectURL = contextURL + "/executor?execid=" + data.execid;
+					window.location.href = redirectURL;
+				}
+			},
+			"json"
+		);
+	  },
+	  handleCustomFlow: function(evt) {
+	  	
+	  },
+	  handleAddRow: function(evt) {
+	  	var tr = document.createElement("tr");
+	  	var tdName = document.createElement("td");
+	    var tdValue = document.createElement("td");
+	    
+	    var icon = document.createElement("span");
+	    $(icon).addClass("removeIcon");
+	    var nameData = document.createElement("span");
+	    $(nameData).addClass("spanValue");
+	    var valueData = document.createElement("span");
+	    $(valueData).addClass("spanValue");
+	    	    
+		$(tdName).append(icon);
+		$(tdName).append(nameData);
+		$(tdName).addClass("name");
+		$(tdName).addClass("editable");
+		
+		$(tdValue).append(valueData);
+	    $(tdValue).addClass("editable");
+		
+	  	$(tr).append(tdName);
+	  	$(tr).append(tdValue);
+	   
+	  	$(tr).insertBefore("#addRow");
+	  },
+	  handleEditColumn : function(evt) {
+	  	var curTarget = evt.currentTarget;
+	
+	  	if (this.editingTarget != curTarget) {
+			this.closeEditingTarget();
+			
+			var text = $(curTarget).children(".spanValue").text();
+			$(curTarget).empty();
+						
+			var input = document.createElement("input");
+			$(input).attr("type", "text");
+			$(input).css("width", "100%");
+			$(input).val(text);
+			$(curTarget).addClass("editing");
+			$(curTarget).append(input);
+			$(input).focus();
+			this.editingTarget = curTarget;
+	  	}
+	  },
+	  handleRemoveColumn : function(evt) {
+	  	var curTarget = evt.currentTarget;
+	  	// Should be the table
+	  	var row = curTarget.parentElement.parentElement;
+		$(row).remove();
+	  },
+	  handleResetData : function(evt) {
+	  },
+	  closeEditingTarget: function(evt) {
+	  	if (this.editingTarget != null && this.editingTarget != evt.target && this.editingTarget != evt.target.parentElement ) {
+	  		var input = $(this.editingTarget).children("input")[0];
+	  		var text = $(input).val();
+	  		$(input).remove();
+
+		    var valueData = document.createElement("span");
+		    $(valueData).addClass("spanValue");
+		    $(valueData).text(text);
+
+	  		if ($(this.editingTarget).hasClass("name")) {
+		  		var icon = document.createElement("span");
+		    	$(icon).addClass("removeIcon");
+		    	$(this.editingTarget).append(icon);
+		    }
+		    
+		    $(this.editingTarget).removeClass("editing");
+		    $(this.editingTarget).append(valueData);
+		    this.editingTarget = null;
+	  	}
+	  },
+	  addRowData : function(evt) {
+
+	  }
+});
\ No newline at end of file
diff --git a/src/web/js/azkaban.exflow.view.js b/src/web/js/azkaban.exflow.view.js
index dbbaeb1..ff1cb39 100644
--- a/src/web/js/azkaban.exflow.view.js
+++ b/src/web/js/azkaban.exflow.view.js
@@ -1,19 +1,5 @@
 $.namespace('azkaban');
 
-var statusList = ["FAILED", "FAILED_FINISHING", "SUCCEEDED", "RUNNING", "WAITING", "KILLED", "DISABLED", "READY", "UNKNOWN", "PAUSED"];
-var statusStringMap = {
-	"FAILED": "Failed",
-	"SUCCEEDED": "Success",
-	"FAILED_FINISHING": "Running w/Failure",
-	"RUNNING": "Running",
-	"WAITING": "Waiting",
-	"KILLED": "Killed",
-	"DISABLED": "Disabled",
-	"READY": "Ready",
-	"UNKNOWN": "Unknown",
-	"PAUSED": "Paused"
-};
-
 var handleJobMenuClick = function(action, el, pos) {
 	var jobid = el[0].jobid;
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
@@ -25,32 +11,6 @@ var handleJobMenuClick = function(action, el, pos) {
 	}
 }
 
-function hasClass(el, name) 
-{
-	var classes = el.getAttribute("class");
-	if (classes == null) {
-		return false;
-	}
-   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
-}
-
-function addClass(el, name)
-{
-   if (!hasClass(el, name)) { 
-   		var classes = el.getAttribute("class");
-   		classes += classes ? ' ' + name : '' +name;
-   		el.setAttribute("class", classes);
-   }
-}
-
-function removeClass(el, name)
-{
-   if (hasClass(el, name)) {
-      var classes = el.getAttribute("class");
-      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
-   }
-}
-
 var statusView;
 azkaban.StatusView= Backbone.View.extend({
 	initialize : function(settings) {
@@ -271,420 +231,7 @@ var showDialog = function(title, message) {
 }
 
 var jobListView;
-azkaban.JobListView = Backbone.View.extend({
-	events: {
-		"keyup input": "filterJobs",
-		"click li": "handleJobClick",
-		"click #resetPanZoomBtn" : "handleResetPanZoom"
-	},
-	initialize: function(settings) {
-		this.model.bind('change:selected', this.handleSelectionChange, this);
-		this.model.bind('change:graph', this.render, this);
-		this.model.bind('change:update', this.handleStatusUpdate, this);
-	},
-	filterJobs: function(self) {
-		var filter = $("#filter").val();
-		
-		if (filter && filter.trim() != "") {
-			filter = filter.trim();
-			
-			if (filter == "") {
-				if (this.filter) {
-					$("#jobs").children().each(
-						function(){
-							var a = $(this).find("a");
-        					$(a).html(this.jobid);
-        					$(this).show();
-						}
-					);
-				}
-				
-				this.filter = null;
-				return;
-			}
-		}
-		else {
-			if (this.filter) {
-				$("#jobs").children().each(
-					function(){
-						var a = $(this).find("a");
-    					$(a).html(this.jobid);
-    					$(this).show();
-					}
-				);
-			}
-				
-			this.filter = null;
-			return;
-		}
-		
-		$("#jobs").children().each(
-			function(){
-        		var jobid = this.jobid;
-        		var index = jobid.indexOf(filter);
-        		if (index != -1) {
-        			var a = $(this).find("a");
-        			
-        			var endIndex = index + filter.length;
-        			var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
-        			
-        			$(a).html(newHTML);
-        			$(this).show();
-        		}
-        		else {
-        			$(this).hide();
-        		}
-    	});
-    	
-    	this.filter = filter;
-	},
-	render: function(self) {
-		var data = this.model.get("data");
-		var nodes = data.nodes;
-		var edges = data.edges;
-		
-		this.listNodes = {}; 
-		if (nodes.length == 0) {
-			console.log("No results");
-			return;
-		};
-	
-		var nodeArray = nodes.slice(0);
-		nodeArray.sort(function(a,b){ 
-			var diff = a.y - b.y;
-			if (diff == 0) {
-				return a.x - b.x;
-			}
-			else {
-				return diff;
-			}
-		});
-		
-		var ul = document.createElement("ul");
-		$(ul).attr("id", "jobs");
-		for (var i = 0; i < nodeArray.length; ++i) {
-			var li = document.createElement("li");
-			
-			var iconDiv = document.createElement("div");
-			$(iconDiv).addClass("icon");
-			li.appendChild(iconDiv);
-			
-			var a = document.createElement("a");
-			$(a).text(nodeArray[i].id);
-			li.appendChild(a);
-			ul.appendChild(li);
-			li.jobid=nodeArray[i].id;
-			
-			$(li).contextMenu({
-					menu: 'jobMenu'
-				},
-				handleJobMenuClick
-			);
-			
-			this.listNodes[nodeArray[i].id] = li;
-		}
-		
-		$("#list").append(ul);
-		this.assignInitialStatus(self);
-	},
-	handleJobClick : function(evt) {
-		var jobid = evt.currentTarget.jobid;
-		if(!evt.currentTarget.jobid) {
-			return;
-		}
-		
-		if (this.model.has("selected")) {
-			var selected = this.model.get("selected");
-			if (selected == jobid) {
-				this.model.unset("selected");
-			}
-			else {
-				this.model.set({"selected": jobid});
-			}
-		}
-		else {
-			this.model.set({"selected": jobid});
-		}
-	},
-	handleStatusUpdate: function(evt) {
-		var updateData = this.model.get("update");
-		for (var i = 0; i < updateData.nodes.length; ++i) {
-			var updateNode = updateData.nodes[i];
-			$(this.listNodes[updateNode.id]).addClass(updateNode.status);
-		}
-	},
-	assignInitialStatus: function(evt) {
-		var data = this.model.get("data");
-		for (var i = 0; i < data.nodes.length; ++i) {
-			var updateNode = data.nodes[i];
-			
-			$(this.listNodes[updateNode.id]).addClass(updateNode.status);
-		}
-	},
-	handleSelectionChange: function(evt) {
-		if (!this.model.hasChanged("selected")) {
-			return;
-		}
-		
-		var previous = this.model.previous("selected");
-		var current = this.model.get("selected");
-		
-		if (previous) {
-			$(this.listNodes[previous]).removeClass("selected");
-		}
-		
-		if (current) {
-			$(this.listNodes[current]).addClass("selected");
-		}
-	},
-	handleResetPanZoom: function(evt) {
-		this.model.trigger("resetPanZoom");
-	}
-});
-
 var svgGraphView;
-azkaban.SvgGraphView = Backbone.View.extend({
-	events: {
-		"click g" : "clickGraph"
-	},
-	initialize: function(settings) {
-		this.model.bind('change:selected', this.changeSelected, this);
-		this.model.bind('change:graph', this.render, this);
-		this.model.bind('resetPanZoom', this.resetPanZoom, this);
-		this.model.bind('change:update', this.handleStatusUpdate, this);
-		
-		this.svgns = "http://www.w3.org/2000/svg";
-		this.xlinksn = "http://www.w3.org/1999/xlink";
-		
-		var graphDiv = this.el[0];
-		var svg = $('#svgGraph')[0];
-		this.svgGraph = svg;
-		
-		var gNode = document.createElementNS(this.svgns, 'g');
-		gNode.setAttribute("id", "group");
-		svg.appendChild(gNode);
-		this.mainG = gNode;
-
-		$(svg).svgNavigate();
-	},
-	initializeDefs: function(self) {
-		var def = document.createElementNS(svgns, 'defs');
-		def.setAttributeNS(null, "id", "buttonDefs");
-
-		// ArrowHead
-		var arrowHeadMarker = document.createElementNS(svgns, 'marker');
-		arrowHeadMarker.setAttribute("id", "triangle");
-		arrowHeadMarker.setAttribute("viewBox", "0 0 10 10");
-		arrowHeadMarker.setAttribute("refX", "5");
-		arrowHeadMarker.setAttribute("refY", "5");
-		arrowHeadMarker.setAttribute("markerUnits", "strokeWidth");
-		arrowHeadMarker.setAttribute("markerWidth", "4");
-		arrowHeadMarker.setAttribute("markerHeight", "3");
-		arrowHeadMarker.setAttribute("orient", "auto");
-		var path = document.createElementNS(svgns, 'polyline');
-		arrowHeadMarker.appendChild(path);
-		path.setAttribute("points", "0,0 10,5 0,10 1,5");
-
-		def.appendChild(arrowHeadMarker);
-		
-		this.svgGraph.appendChild(def);
-	},
-	render: function(self) {
-		console.log("graph render");
-
-		var data = this.model.get("data");
-		var nodes = data.nodes;
-		var edges = data.edges;
-		if (nodes.length == 0) {
-			console.log("No results");
-			return;
-		};
-	
-		// layout
-		layoutGraph(nodes, edges);
-		
-		var bounds = {};
-		this.nodes = {};
-		for (var i = 0; i < nodes.length; ++i) {
-			this.nodes[nodes[i].id] = nodes[i];
-		}
-		
-		for (var i = 0; i < edges.length; ++i) {
-			this.drawEdge(this, edges[i]);
-		}
-		
-		for (var i = 0; i < nodes.length; ++i) {
-			this.drawNode(this, nodes[i], bounds);
-		}
-		
-		bounds.minX = bounds.minX ? bounds.minX - 200 : -200;
-		bounds.minY = bounds.minY ? bounds.minY - 200 : -200;
-		bounds.maxX = bounds.maxX ? bounds.maxX + 200 : 200;
-		bounds.maxY = bounds.maxY ? bounds.maxY + 200 : 200;
-		
-		this.assignInitialStatus(self);
-		this.graphBounds = bounds;
-		this.resetPanZoom();
-	},
-	assignInitialStatus: function(evt) {
-		var data = this.model.get("data");
-		for (var i = 0; i < data.nodes.length; ++i) {
-			var updateNode = data.nodes[i];
-			var g = document.getElementById(updateNode.id);
-			addClass(g, updateNode.status);
-		}
-	},
-	changeSelected: function(self) {
-		console.log("change selected");
-		var selected = this.model.get("selected");
-		var previous = this.model.previous("selected");
-		
-		if (previous) {
-			// Unset previous
-			var g = document.getElementById(previous);
-			removeClass(g, "selected");
-		}
-		
-		if (selected) {
-			var g = document.getElementById(selected);
-			var node = this.nodes[selected];
-			
-			addClass(g, "selected");
-			
-			var offset = 200;
-			var widthHeight = offset*2;
-			var x = node.x - offset;
-			var y = node.y - offset;
-			
-			$("#svgGraph").svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
-		}
-	},
-	handleStatusUpdate: function(evt) {
-		var updateData = this.model.get("update");
-		for (var i = 0; i < updateData.nodes.length; ++i) {
-			var updateNode = updateData.nodes[i];
-			var g = document.getElementById(updateNode.id);
-			
-			for (var j = 0; j < statusList.length; ++j) {
-				var status = statusList[j];
-				removeClass(g, status);
-			}
-			
-			addClass(g, updateNode.status);
-		}
-	},
-	clickGraph: function(self) {
-		console.log("click");
-		if (self.currentTarget.jobid) {
-			this.model.set({"selected": self.currentTarget.jobid});
-		}
-	},
-	drawEdge: function(self, edge) {
-		var svg = self.svgGraph;
-		var svgns = self.svgns;
-		
-		var startNode = this.nodes[edge.from];
-		var endNode = this.nodes[edge.target];
-		
-		if (edge.guides) {
-			var pointString = "" + startNode.x + "," + startNode.y + " ";
-
-			for (var i = 0; i < edge.guides.length; ++i ) {
-				edgeGuidePoint = edge.guides[i];
-				pointString += edgeGuidePoint.x + "," + edgeGuidePoint.y + " ";
-			}
-			
-			pointString += endNode.x + "," + endNode.y;
-			var polyLine = document.createElementNS(svgns, "polyline");
-			polyLine.setAttributeNS(null, "class", "edge");
-			polyLine.setAttributeNS(null, "points", pointString);
-			polyLine.setAttributeNS(null, "style", "fill:none;");
-			self.mainG.appendChild(polyLine);
-		}
-		else { 
-			var line = document.createElementNS(svgns, 'line');
-			line.setAttributeNS(null, "class", "edge");
-			line.setAttributeNS(null, "x1", startNode.x);
-			line.setAttributeNS(null, "y1", startNode.y);
-			line.setAttributeNS(null, "x2", endNode.x);
-			line.setAttributeNS(null, "y2", endNode.y);
-			
-			self.mainG.appendChild(line);
-		}
-	},
-	drawNode: function(self, node, bounds) {
-		var svg = self.svgGraph;
-		var svgns = self.svgns;
-
-		var xOffset = 10;
-		var yOffset = 10;
-
-		var nodeG = document.createElementNS(svgns, "g");
-		nodeG.setAttributeNS(null, "class", "jobnode");
-		nodeG.setAttributeNS(null, "id", node.id);
-		nodeG.setAttributeNS(null, "font-family", "helvetica");
-		nodeG.setAttributeNS(null, "transform", "translate(" + node.x + "," + node.y + ")");
-		
-		var innerG = document.createElementNS(svgns, "g");
-		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
-		
-		var circle = document.createElementNS(svgns, 'circle');
-		circle.setAttributeNS(null, "cy", 10);
-		circle.setAttributeNS(null, "cx", 10);
-		circle.setAttributeNS(null, "r", 12);
-		circle.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
-		
-		
-		var text = document.createElementNS(svgns, 'text');
-		var textLabel = document.createTextNode(node.label);
-		text.appendChild(textLabel);
-		text.setAttributeNS(null, "x", 4);
-		text.setAttributeNS(null, "y", 15);
-		text.setAttributeNS(null, "height", 10); 
-				
-		this.addBounds(bounds, {minX:node.x - xOffset, minY: node.y - yOffset, maxX: node.x + xOffset, maxY: node.y + yOffset});
-		
-		var backRect = document.createElementNS(svgns, 'rect');
-		backRect.setAttributeNS(null, "x", 0);
-		backRect.setAttributeNS(null, "y", 2);
-		backRect.setAttributeNS(null, "class", "backboard");
-		backRect.setAttributeNS(null, "width", 10);
-		backRect.setAttributeNS(null, "height", 15);
-		
-		innerG.appendChild(circle);
-		innerG.appendChild(backRect);
-		innerG.appendChild(text);
-		innerG.jobid = node.id;
-
-		nodeG.appendChild(innerG);
-		self.mainG.appendChild(nodeG);
-
-		// Need to get text width after attaching to SVG.
-		var computeText = text.getComputedTextLength();
-		var halfWidth = computeText/2;
-		text.setAttributeNS(null, "x", -halfWidth + 10);
-		backRect.setAttributeNS(null, "x", -halfWidth);
-		backRect.setAttributeNS(null, "width", computeText + 20);
-
-		nodeG.setAttributeNS(null, "class", "node");
-		nodeG.jobid=node.id;
-		$(nodeG).contextMenu({
-				menu: 'jobMenu'
-			},
-			handleJobMenuClick
-		);
-	},
-	addBounds: function(toBounds, addBounds) {
-		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
-		toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
-		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
-		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
-	},
-	resetPanZoom : function(self) {
-		var bounds = this.graphBounds;
-		$("#svgGraph").svgNavigate("transformToBox", {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY) });
-	}
-});
 
 var executionListView;
 azkaban.ExecutionListView = Backbone.View.extend({
diff --git a/src/web/js/azkaban.flow.graph.view.js b/src/web/js/azkaban.flow.graph.view.js
new file mode 100644
index 0000000..75b3096
--- /dev/null
+++ b/src/web/js/azkaban.flow.graph.view.js
@@ -0,0 +1,266 @@
+var svgGraphView;
+azkaban.SvgGraphView = Backbone.View.extend({
+	events: {
+		"click g" : "clickGraph"
+	},
+	initialize: function(settings) {
+		this.model.bind('change:selected', this.changeSelected, this);
+		this.model.bind('change:graph', this.render, this);
+		this.model.bind('resetPanZoom', this.resetPanZoom, this);
+		this.model.bind('change:update', this.handleStatusUpdate, this);
+		this.model.bind('change:disabled', this.handleDisabledChange, this);
+				
+		this.svgns = "http://www.w3.org/2000/svg";
+		this.xlinksn = "http://www.w3.org/1999/xlink";
+		
+		var graphDiv = this.el[0];
+		var svg = $(this.el).find('svg')[0];
+		this.svgGraph = svg;
+		
+		var gNode = document.createElementNS(this.svgns, 'g');
+		svg.appendChild(gNode);
+		this.mainG = gNode;
+
+		$(svg).svgNavigate();
+	},
+	initializeDefs: function(self) {
+		var def = document.createElementNS(svgns, 'defs');
+		def.setAttributeNS(null, "id", "buttonDefs");
+
+		// ArrowHead
+		var arrowHeadMarker = document.createElementNS(svgns, 'marker');
+		arrowHeadMarker.setAttribute("id", "triangle");
+		arrowHeadMarker.setAttribute("viewBox", "0 0 10 10");
+		arrowHeadMarker.setAttribute("refX", "5");
+		arrowHeadMarker.setAttribute("refY", "5");
+		arrowHeadMarker.setAttribute("markerUnits", "strokeWidth");
+		arrowHeadMarker.setAttribute("markerWidth", "4");
+		arrowHeadMarker.setAttribute("markerHeight", "3");
+		arrowHeadMarker.setAttribute("orient", "auto");
+		var path = document.createElementNS(svgns, 'polyline');
+		arrowHeadMarker.appendChild(path);
+		path.setAttribute("points", "0,0 10,5 0,10 1,5");
+
+		def.appendChild(arrowHeadMarker);
+		
+		this.svgGraph.appendChild(def);
+	},
+	render: function(self) {
+		console.log("graph render");
+
+		var data = this.model.get("data");
+		var nodes = data.nodes;
+		var edges = data.edges;
+		if (nodes.length == 0) {
+			console.log("No results");
+			return;
+		};
+	
+		// layout
+		layoutGraph(nodes, edges);
+		
+		var bounds = {};
+		this.nodes = {};
+		for (var i = 0; i < nodes.length; ++i) {
+			this.nodes[nodes[i].id] = nodes[i];
+		}
+		
+		for (var i = 0; i < edges.length; ++i) {
+			this.drawEdge(this, edges[i]);
+		}
+		
+		this.gNodes = {};
+		for (var i = 0; i < nodes.length; ++i) {
+			this.drawNode(this, nodes[i], bounds);
+		}
+		
+		bounds.minX = bounds.minX ? bounds.minX - 200 : -200;
+		bounds.minY = bounds.minY ? bounds.minY - 200 : -200;
+		bounds.maxX = bounds.maxX ? bounds.maxX + 200 : 200;
+		bounds.maxY = bounds.maxY ? bounds.maxY + 200 : 200;
+		
+		this.assignInitialStatus(self);
+		this.graphBounds = bounds;
+		this.resetPanZoom(0);
+	},
+	handleDisabledChange: function(evt) {
+		var disabledMap = this.model.get("disabled");
+		for(var id in disabledMap) {
+		    if(disabledMap.hasOwnProperty(id)) {
+		    	var disabled = disabledMap[id];
+		    	this.nodes[id].disabled = disabled;
+		    	var g = this.gNodes[id];
+		    	
+		    	if (disabled) {
+		    		this.nodes[id].disabled = disabled;
+					addClass(g, "disabled");
+		    	}
+		    	else {
+		    		removeClass(g, "disabled");
+		    	}
+		    }
+		}
+	},
+	assignInitialStatus: function(evt) {
+		var data = this.model.get("data");
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var updateNode = data.nodes[i];
+			var g = this.gNodes[updateNode.id];
+			addClass(g, updateNode.status);
+		}
+	},
+	changeSelected: function(self) {
+		console.log("change selected");
+		var selected = this.model.get("selected");
+		var previous = this.model.previous("selected");
+		
+		if (previous) {
+			// Unset previous
+			var g = this.gNodes[previous];
+			removeClass(g, "selected");
+		}
+		
+		if (selected) {
+			var g = this.gNodes[selected];
+			var node = this.nodes[selected];
+			
+			addClass(g, "selected");
+			
+			var offset = 200;
+			var widthHeight = offset*2;
+			var x = node.x - offset;
+			var y = node.y - offset;
+			
+			$(this.svgGraph).svgNavigate("transformToBox", {x: x, y: y, width: widthHeight, height: widthHeight});
+		}
+	},
+	handleStatusUpdate: function(evt) {
+		var updateData = this.model.get("update");
+		for (var i = 0; i < updateData.nodes.length; ++i) {
+			var updateNode = updateData.nodes[i];
+			
+			var g = this.gNodes[updateNode.id];
+			
+			for (var j = 0; j < statusList.length; ++j) {
+				var status = statusList[j];
+				removeClass(g, status);
+			}
+			
+			addClass(g, updateNode.status);
+		}
+	},
+	clickGraph: function(self) {
+		console.log("click");
+		if (self.currentTarget.jobid) {
+			this.model.set({"selected": self.currentTarget.jobid});
+		}
+	},
+	drawEdge: function(self, edge) {
+		var svg = self.svgGraph;
+		var svgns = self.svgns;
+		
+		var startNode = this.nodes[edge.from];
+		var endNode = this.nodes[edge.target];
+		
+		if (edge.guides) {
+			var pointString = "" + startNode.x + "," + startNode.y + " ";
+
+			for (var i = 0; i < edge.guides.length; ++i ) {
+				edgeGuidePoint = edge.guides[i];
+				pointString += edgeGuidePoint.x + "," + edgeGuidePoint.y + " ";
+			}
+			
+			pointString += endNode.x + "," + endNode.y;
+			var polyLine = document.createElementNS(svgns, "polyline");
+			polyLine.setAttributeNS(null, "class", "edge");
+			polyLine.setAttributeNS(null, "points", pointString);
+			polyLine.setAttributeNS(null, "style", "fill:none;");
+			self.mainG.appendChild(polyLine);
+		}
+		else { 
+			var line = document.createElementNS(svgns, 'line');
+			line.setAttributeNS(null, "class", "edge");
+			line.setAttributeNS(null, "x1", startNode.x);
+			line.setAttributeNS(null, "y1", startNode.y);
+			line.setAttributeNS(null, "x2", endNode.x);
+			line.setAttributeNS(null, "y2", endNode.y);
+			
+			self.mainG.appendChild(line);
+		}
+	},
+	drawNode: function(self, node, bounds) {
+		var svg = self.svgGraph;
+		var svgns = self.svgns;
+
+		var xOffset = 10;
+		var yOffset = 10;
+
+		
+		var nodeG = document.createElementNS(svgns, "g");
+		nodeG.setAttributeNS(null, "class", "jobnode");
+		nodeG.setAttributeNS(null, "font-family", "helvetica");
+		nodeG.setAttributeNS(null, "transform", "translate(" + node.x + "," + node.y + ")");
+		this.gNodes[node.id] = nodeG;
+		
+		var innerG = document.createElementNS(svgns, "g");
+		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
+		
+		var circle = document.createElementNS(svgns, 'circle');
+		circle.setAttributeNS(null, "cy", 10);
+		circle.setAttributeNS(null, "cx", 10);
+		circle.setAttributeNS(null, "r", 12);
+		circle.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
+		
+		
+		var text = document.createElementNS(svgns, 'text');
+		var textLabel = document.createTextNode(node.label);
+		text.appendChild(textLabel);
+		text.setAttributeNS(null, "x", 4);
+		text.setAttributeNS(null, "y", 15);
+		text.setAttributeNS(null, "height", 10); 
+				
+		this.addBounds(bounds, {minX:node.x - xOffset, minY: node.y - yOffset, maxX: node.x + xOffset, maxY: node.y + yOffset});
+		
+		var backRect = document.createElementNS(svgns, 'rect');
+		backRect.setAttributeNS(null, "x", 0);
+		backRect.setAttributeNS(null, "y", 2);
+		backRect.setAttributeNS(null, "class", "backboard");
+		backRect.setAttributeNS(null, "width", 10);
+		backRect.setAttributeNS(null, "height", 15);
+		
+		innerG.appendChild(circle);
+		innerG.appendChild(backRect);
+		innerG.appendChild(text);
+		innerG.jobid = node.id;
+
+		nodeG.appendChild(innerG);
+		self.mainG.appendChild(nodeG);
+
+		// Need to get text width after attaching to SVG.
+		var computeText = text.getComputedTextLength();
+		var halfWidth = computeText/2;
+		text.setAttributeNS(null, "x", -halfWidth + 10);
+		backRect.setAttributeNS(null, "x", -halfWidth);
+		backRect.setAttributeNS(null, "width", computeText + 20);
+
+		nodeG.setAttributeNS(null, "class", "node");
+		nodeG.jobid=node.id;
+		$(nodeG).contextMenu({
+				menu: 'jobMenu'
+			},
+			handleJobMenuClick
+		);
+	},
+	addBounds: function(toBounds, addBounds) {
+		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
+		toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
+		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
+		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
+	},
+	resetPanZoom : function(duration) {
+		var bounds = this.graphBounds;
+		var param = {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY), duration: duration };
+
+		$(this.svgGraph).svgNavigate("transformToBox", param);
+	}
+});
\ No newline at end of file
diff --git a/src/web/js/azkaban.flow.job.view.js b/src/web/js/azkaban.flow.job.view.js
new file mode 100644
index 0000000..210b404
--- /dev/null
+++ b/src/web/js/azkaban.flow.job.view.js
@@ -0,0 +1,189 @@
+azkaban.JobListView = Backbone.View.extend({
+	events: {
+		"keyup input": "filterJobs",
+		"click li": "handleJobClick",
+		"click .resetPanZoomBtn" : "handleResetPanZoom"
+	},
+	initialize: function(settings) {
+		this.model.bind('change:selected', this.handleSelectionChange, this);
+		this.model.bind('change:disabled', this.handleDisabledChange, this);
+		this.model.bind('change:graph', this.render, this);
+		this.model.bind('change:update', this.handleStatusUpdate, this);
+		
+		this.filterInput = $(this.el).find(".filter");
+		this.list = $(this.el).find(".list");
+	},
+	filterJobs: function(self) {
+		var filter = this.filterInput.val();
+		
+		if (filter && filter.trim() != "") {
+			filter = filter.trim();
+			
+			if (filter == "") {
+				if (this.filter) {
+					this.jobs.children().each(
+						function(){
+							var a = $(this).find("a");
+        					$(a).html(this.jobid);
+        					$(this).show();
+						}
+					);
+				}
+				
+				this.filter = null;
+				return;
+			}
+		}
+		else {
+			if (this.filter) {
+				this.jobs.children().each(
+					function(){
+						var a = $(this).find("a");
+    					$(a).html(this.jobid);
+    					$(this).show();
+					}
+				);
+			}
+				
+			this.filter = null;
+			return;
+		}
+		
+		this.jobs.children().each(
+			function(){
+        		var jobid = this.jobid;
+        		var index = jobid.indexOf(filter);
+        		if (index != -1) {
+        			var a = $(this).find("a");
+        			
+        			var endIndex = index + filter.length;
+        			var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
+        			
+        			$(a).html(newHTML);
+        			$(this).show();
+        		}
+        		else {
+        			$(this).hide();
+        		}
+    	});
+    	
+    	this.filter = filter;
+	},
+	handleStatusUpdate: function(evt) {
+		var updateData = this.model.get("update");
+		for (var i = 0; i < updateData.nodes.length; ++i) {
+			var updateNode = updateData.nodes[i];
+			$(this.listNodes[updateNode.id]).addClass(updateNode.status);
+		}
+	},
+	assignInitialStatus: function(evt) {
+		var data = this.model.get("data");
+		for (var i = 0; i < data.nodes.length; ++i) {
+			var updateNode = data.nodes[i];
+			
+			$(this.listNodes[updateNode.id]).addClass(updateNode.status);
+		}
+	},
+	render: function(self) {
+		var data = this.model.get("data");
+		var nodes = data.nodes;
+		var edges = data.edges;
+		
+		this.listNodes = {}; 
+		if (nodes.length == 0) {
+			console.log("No results");
+			return;
+		};
+	
+		var nodeArray = nodes.slice(0);
+		nodeArray.sort(function(a,b){ 
+			var diff = a.y - b.y;
+			if (diff == 0) {
+				return a.x - b.x;
+			}
+			else {
+				return diff;
+			}
+		});
+		
+		var ul = document.createElement("ul");
+		$(ul).attr("class", "jobs");
+		this.jobs = $(ul);
+		
+		for (var i = 0; i < nodeArray.length; ++i) {
+			var li = document.createElement("li");
+			var iconDiv = document.createElement("div");
+			$(iconDiv).addClass("icon");
+			li.appendChild(iconDiv);
+			
+			var a = document.createElement("a");
+			$(a).text(nodeArray[i].id);
+			li.appendChild(a);
+			ul.appendChild(li);
+			li.jobid=nodeArray[i].id;
+			
+			$(li).contextMenu({
+					menu: 'jobMenu'
+				},
+				handleJobMenuClick
+			);
+			
+			this.listNodes[nodeArray[i].id] = li;
+		}
+		
+		this.list.append(ul);
+		this.assignInitialStatus(self);
+	},
+	handleJobClick : function(evt) {
+		var jobid = evt.currentTarget.jobid;
+		if(!evt.currentTarget.jobid) {
+			return;
+		}
+		
+		if (this.model.has("selected")) {
+			var selected = this.model.get("selected");
+			if (selected == jobid) {
+				this.model.unset("selected");
+			}
+			else {
+				this.model.set({"selected": jobid});
+			}
+		}
+		else {
+			this.model.set({"selected": jobid});
+		}
+	},
+	handleDisabledChange: function(evt) {
+		var disabledMap = this.model.get("disabled");
+		for(var id in disabledMap) {
+		    if(disabledMap.hasOwnProperty(id)) {
+		    	var disabled = (disabledMap[id]);
+		    	if (disabled) {
+		    		$(this.listNodes[id]).addClass("nodedisabled");
+		    	}
+		    	else {
+		    		$(this.listNodes[id]).removeClass("nodedisabled");
+		    	}
+		    }
+		}
+	},
+	handleSelectionChange: function(evt) {
+		if (!this.model.hasChanged("selected")) {
+			return;
+		}
+		
+		var previous = this.model.previous("selected");
+		var current = this.model.get("selected");
+		
+		if (previous) {
+			$(this.listNodes[previous]).removeClass("selected");
+		}
+		
+		if (current) {
+			$(this.listNodes[current]).addClass("selected");
+		}
+	},
+	handleResetPanZoom: function(evt) {
+		this.model.trigger("resetPanZoom");
+	}
+});
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index c8aef95..d65dc9d 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -214,171 +214,9 @@ azkaban.FlowTabView= Backbone.View.extend({
 });
 
 var jobListView;
-azkaban.JobListView = Backbone.View.extend({
-	events: {
-		"keyup input": "filterJobs",
-		"click li": "handleJobClick",
-		"click #resetPanZoomBtn" : "handleResetPanZoom"
-	},
-	initialize: function(settings) {
-		this.model.bind('change:selected', this.handleSelectionChange, this);
-		this.model.bind('change:disabled', this.handleDisabledChange, this);
-		this.model.bind('change:graph', this.render, this);
-	},
-	filterJobs: function(self) {
-		var filter = $("#filter").val();
-		
-		if (filter && filter.trim() != "") {
-			filter = filter.trim();
-			
-			if (filter == "") {
-				if (this.filter) {
-					$("#jobs").children().each(
-						function(){
-							var a = $(this).find("a");
-        					$(a).html(this.jobid);
-        					$(this).show();
-						}
-					);
-				}
-				
-				this.filter = null;
-				return;
-			}
-		}
-		else {
-			if (this.filter) {
-				$("#jobs").children().each(
-					function(){
-						var a = $(this).find("a");
-    					$(a).html(this.jobid);
-    					$(this).show();
-					}
-				);
-			}
-				
-			this.filter = null;
-			return;
-		}
-		
-		$("#jobs").children().each(
-			function(){
-        		var jobid = this.jobid;
-        		var index = jobid.indexOf(filter);
-        		if (index != -1) {
-        			var a = $(this).find("a");
-        			
-        			var endIndex = index + filter.length;
-        			var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
-        			
-        			$(a).html(newHTML);
-        			$(this).show();
-        		}
-        		else {
-        			$(this).hide();
-        		}
-    	});
-    	
-    	this.filter = filter;
-	},
-	render: function(self) {
-		var data = this.model.get("data");
-		var nodes = data.nodes;
-		var edges = data.edges;
-		
-		this.listNodes = {}; 
-		if (nodes.length == 0) {
-			console.log("No results");
-			return;
-		};
-	
-		var nodeArray = nodes.slice(0);
-		nodeArray.sort(function(a,b){ 
-			var diff = a.y - b.y;
-			if (diff == 0) {
-				return a.x - b.x;
-			}
-			else {
-				return diff;
-			}
-		});
-		
-		var ul = document.createElement("ul");
-		$(ul).attr("id", "jobs");
-		for (var i = 0; i < nodeArray.length; ++i) {
-			var li = document.createElement("li");
-			var a = document.createElement("a");
-			$(a).text(nodeArray[i].id);
-			li.appendChild(a);
-			ul.appendChild(li);
-			li.jobid=nodeArray[i].id;
-			
-			$(li).contextMenu({
-					menu: 'jobMenu'
-				},
-				handleJobMenuClick
-			);
-			
-			this.listNodes[nodeArray[i].id] = li;
-		}
-		
-		$("#list").append(ul);
-	},
-	handleJobClick : function(evt) {
-		var jobid = evt.currentTarget.jobid;
-		if(!evt.currentTarget.jobid) {
-			return;
-		}
-		
-		if (this.model.has("selected")) {
-			var selected = this.model.get("selected");
-			if (selected == jobid) {
-				this.model.unset("selected");
-			}
-			else {
-				this.model.set({"selected": jobid});
-			}
-		}
-		else {
-			this.model.set({"selected": jobid});
-		}
-	},
-	handleDisabledChange: function(evt) {
-		var disabledMap = this.model.get("disabled");
-		for(var id in disabledMap) {
-		    if(disabledMap.hasOwnProperty(id)) {
-		    	var disabled = (disabledMap[id]);
-		    	if (disabled) {
-		    		$(this.listNodes[id]).addClass("nodedisabled");
-		    	}
-		    	else {
-		    		$(this.listNodes[id]).removeClass("nodedisabled");
-		    	}
-		    }
-		}
-	},
-	handleSelectionChange: function(evt) {
-		if (!this.model.hasChanged("selected")) {
-			return;
-		}
-		
-		var previous = this.model.previous("selected");
-		var current = this.model.get("selected");
-		
-		if (previous) {
-			$(this.listNodes[previous]).removeClass("selected");
-		}
-		
-		if (current) {
-			$(this.listNodes[current]).addClass("selected");
-		}
-	},
-	handleResetPanZoom: function(evt) {
-		this.model.trigger("resetPanZoom");
-	}
-});
 
 var svgGraphView;
+/*
 azkaban.SvgGraphView = Backbone.View.extend({
 	events: {
 		"click g" : "clickGraph"
@@ -617,7 +455,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		$("#svgGraph").svgNavigate("transformToBox", {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY) });
 	}
 });
-
+*/
 var executionsView;
 azkaban.ExecutionsView = Backbone.View.extend({
 	events: {
@@ -927,72 +765,12 @@ azkaban.ScheduleFlowView = Backbone.View.extend({
   }
 });
 
-var executeFlowView;
-azkaban.ExecuteFlowView = Backbone.View.extend({
-  	  events : {
-	    "click #execute-btn": "handleExecuteFlow",
-	    "click #execute-custom-btn": "handleCustomFlow",
-	    "click #cancel-btn": "handleCancelExecution",
-	    "click .modal-close": "handleCancelExecution"
-	  },
-	  initialize: function(evt) {
-	  	 $('#executebtn').click( function() {
-	  	 	$('#modalBackground').show();
-	  	 	$('#executing-options').show();
-	  	 });
-	  
-	  /*
-	     $('#executebtn').click( function() {
-		  console.log("Executing button clicked");
-		  $('#executing-options').modal({
-	          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
-	          position: ["10%",],
-	          containerId: 'confirm-container',
-	          containerCss: {
-	          	
-	          },
-	          opacity:40,
-	          overlayCss: {backgroundColor:"#000", width: '100%'},
-	          onShow: function (dialog) {
-	            var modal = this;
-	            $("#errorMsg").hide();
-	          }
-	        });
-		});*/
-	  },
-	  handleCancelExecution: function(evt) {
-	  	var executeURL = contextURL + "/executor";
-		$('#modalBackground').hide();
-	  	$('#executing-options').hide();
-	  },
-	  handleExecuteFlow: function(evt) {
-	  	var executeURL = contextURL + "/executor";
-		$.get(
-			executeURL,
-			{"project": projectName, "ajax":"executeFlow", "flow":flowName, "disabled":graphModel.get("disabled")},
-			function(data) {
-				if (data.error) {
-					alert(data.error);
-				}
-				else {
-					var redirectURL = contextURL + "/executor?execid=" + data.execid;
-					window.location.href = redirectURL;
-				}
-			},
-			"json"
-		);
-	  },
-	  handleCustomFlow: function(evt) {
-	  	
-	  }
-});
 
 $(function() {
 	var selected;
 	// Execution model has to be created before the window switches the tabs.
 	executionModel = new azkaban.ExecutionModel();
-	executionsView = new azkaban.ExecutionsView({el: $('#executionsView'), model: executionModel});
-		
+	
 	flowTabView = new azkaban.FlowTabView({el:$( '#headertabs'), selectedView: selected });
 
 	graphModel = new azkaban.GraphModel();
diff --git a/src/web/js/azkaban.job.status.utils.js b/src/web/js/azkaban.job.status.utils.js
new file mode 100644
index 0000000..271af58
--- /dev/null
+++ b/src/web/js/azkaban.job.status.utils.js
@@ -0,0 +1,39 @@
+var statusList = ["FAILED", "FAILED_FINISHING", "SUCCEEDED", "RUNNING", "WAITING", "KILLED", "DISABLED", "READY", "UNKNOWN", "PAUSED"];
+var statusStringMap = {
+	"FAILED": "Failed",
+	"SUCCEEDED": "Success",
+	"FAILED_FINISHING": "Running w/Failure",
+	"RUNNING": "Running",
+	"WAITING": "Waiting",
+	"KILLED": "Killed",
+	"DISABLED": "Disabled",
+	"READY": "Ready",
+	"UNKNOWN": "Unknown",
+	"PAUSED": "Paused"
+};
+
+function hasClass(el, name) 
+{
+	var classes = el.getAttribute("class");
+	if (classes == null) {
+		return false;
+	}
+   return new RegExp('(\\s|^)'+name+'(\\s|$)').test(classes);
+}
+
+function addClass(el, name)
+{
+   if (!hasClass(el, name)) { 
+   		var classes = el.getAttribute("class");
+   		classes += classes ? ' ' + name : '' +name;
+   		el.setAttribute("class", classes);
+   }
+}
+
+function removeClass(el, name)
+{
+   if (hasClass(el, name)) {
+      var classes = el.getAttribute("class");
+      el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
+   }
+}
\ No newline at end of file
diff --git a/src/web/js/svgNavigate.js b/src/web/js/svgNavigate.js
index 0e547a1..076f700 100644
--- a/src/web/js/svgNavigate.js
+++ b/src/web/js/svgNavigate.js
@@ -298,6 +298,7 @@
 			var x = arguments.x;
 			var y = arguments.y;
 			var factor = 0.9;
+			var duration = arguments.duration;
 			
 			var width = arguments.width ? arguments.width : 1;
 			var height = arguments.height ? arguments.height : 1;
@@ -319,7 +320,12 @@
 			var sx = (divWidth - scaledWidth)/2 -scale*x;
 			var sy = (divHeight - scaledHeight)/2 -scale*y;
 			console.log("sx,sy:" + sx + "," + sy);
-			animateTransform(target, scale, sx, sy, 500);
+			
+			if (duration != 0 && !duration) {
+				duration = 500;
+			}
+			
+			animateTransform(target, scale, sx, sy, duration);
 		},
 		attachNavigateModel : function(arguments) {
 			var $this = $(this);