azkaban-developers

Confirmation page for executions.

9/28/2012 5:48:05 AM

Details

diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index bdc9ede..b6fd420 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -159,6 +159,16 @@ public class ExecutorManager {
 		return flows;
 	}
 	
+	public boolean isFlowRunning(String projectId, String flowId) {
+		for (ExecutableFlow flow: getRunningFlows()) {
+			if (flow.getProjectId().equals(projectId) && flow.getFlowId().equals(flowId)) {
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
 	public List<ExecutableFlow> getRunningFlows() {
 		ArrayList<ExecutableFlow> execFlows = new ArrayList<ExecutableFlow>(runningFlows.values());
 		return execFlows;
diff --git a/src/java/azkaban/utils/Mailman.java b/src/java/azkaban/utils/Mailman.java
index 38e812b..7fc828a 100644
--- a/src/java/azkaban/utils/Mailman.java
+++ b/src/java/azkaban/utils/Mailman.java
@@ -50,26 +50,26 @@ public class Mailman {
 
 	public void sendEmail(String fromAddress, List<String> toAddress,
 			String subject, String body) throws MessagingException {
-		SimpleEmail email = new SimpleEmail();
-
-		try {
-			email.setHostName(_mailHost);
-
-			for (String addr : toAddress) {
-				email.addTo(addr);
-			}
-
-			email.setFrom(fromAddress);
-			email.setSubject(subject);
-
-			email.setMsg(body);
-			email.setDebug(true);
-			email.send();
-		} catch (EmailException e) {
-			logger.error(e);
-		} catch (Exception e) {
-			logger.error(e);
-		}
+//		SimpleEmail email = new SimpleEmail();
+//
+//		try {
+//			email.setHostName(_mailHost);
+//
+//			for (String addr : toAddress) {
+//				email.addTo(addr);
+//			}
+//
+//			email.setFrom(fromAddress);
+//			email.setSubject(subject);
+//
+//			email.setMsg(body);
+//			email.setDebug(true);
+//			email.send();
+//		} catch (EmailException e) {
+//			logger.error(e);
+//		} catch (Exception e) {
+//			logger.error(e);
+//		}
 
 	}
 
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index 6ae0e66..0aaf143 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -152,7 +152,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		
 		return null;
 	}
-
+	
 	protected Project getProjectAjaxByPermission(Map<String, Object> ret, String projectId, User user, Permission.Type type) {
 		Project project = projectManager.getProject(projectId);
 		
@@ -222,6 +222,11 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 				}
 			}
 		}
+		else if (ajaxName.equals("isRunning")) {
+			String projectName = getParam(req, "project");
+			String flowName = getParam(req, "flow");
+			ajaxIsFlowRunning(req, resp, ret, session.getUser(), projectName, flowName);
+		}
 		else {
 			String projectName = getParam(req, "project");
 			
@@ -324,12 +329,20 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		}
 	}
 
+	private void ajaxIsFlowRunning(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, String projectId, String flowId) throws ServletException{
+		Project project = getProjectAjaxByPermission(ret, projectId, user, Type.EXECUTE);
+		if (project == null) {
+			return;
+		}
+		
+		ret.put("running", executorManager.isFlowRunning(projectId, flowId));
+	}
+	
 	private void ajaxRestartFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
 		Project project = getProjectAjaxByPermission(ret, exFlow.getProjectId(), user, Type.EXECUTE);
 		if (project == null) {
 			return;
 		}
-
 	}
 
 	private void ajaxPauseFlow(HttpServletRequest req, HttpServletResponse resp, HashMap<String, Object> ret, User user, ExecutableFlow exFlow) throws ServletException{
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 29bc6d0..720f8f5 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -91,6 +91,9 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			else if (hasParam(req, "permissions")) {
 				handlePermissionPage(req, resp, session);
 			}
+			else if (hasParam(req, "staging")) {
+				handleFlowStagingPage(req, resp, session);
+			}
 			else if (hasParam(req, "job")) {
 				handleJobPage(req, resp, session);
 			}
@@ -698,6 +701,48 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		page.render();
 	}
 
+	private void handleFlowStagingPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException {
+		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/flowstagingpage.vm");
+		String projectName = getParam(req, "project");
+		String flowName = getParam(req, "flow");
+		
+		String retryFlow = null;
+		if (hasParam(req, "retry")) {
+			retryFlow = getParam(req, "retry");
+		}
+		
+		User user = session.getUser();
+		Project project = null;
+		Flow flow = null;
+		try {
+			project = projectManager.getProject(projectName);
+			
+			if (project == null) {
+				page.add("errorMsg", "Project " + projectName + " not found.");
+			}
+			else {
+				if (!project.hasPermission(user, Type.EXECUTE)) {
+					throw new AccessControlException( "No permission Project " + projectName + " to Execute.");
+				}
+				
+				page.add("project", project);
+				
+				flow = project.getFlow(flowName);
+				if (flow == null) {
+					page.add("errorMsg", "Flow " + flowName + " not found.");
+				}
+				
+				page.add("flowid", flow.getId());
+				page.add("retry", retryFlow);
+			}
+		}
+		catch (AccessControlException e) {
+			page.add("errorMsg", e.getMessage());
+		}
+		
+		page.render();
+	}
+	
 	private void handleProjectPage(HttpServletRequest req, HttpServletResponse resp, Session session) throws ServletException {
 		Page page = newPage(req, resp, session, "azkaban/webapp/servlet/velocity/projectpage.vm");
 		String projectName = getParam(req, "project");
diff --git a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
index 20d2bc8..e89c6f9 100644
--- a/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/executingflowpage.vm
@@ -64,7 +64,7 @@
 							<li><div id="pausebtn" class="btn2">Pause</div></li>
 							<li><div id="resumebtn" class="btn2">Resume</div></li>
 							<li><div id="cancelbtn" class="btn6">Cancel</div></li>
-							<li><div id="restartbtn" class="btn1">Restart</div></li>
+							<li><div id="restartbtn" class="btn1">Retry...</div></li>
 						</ul>
 					</div>
 					<div id="graphView">
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 57d0b4c..21a4e8b 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -54,7 +54,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>
 					
@@ -77,7 +77,7 @@
 							</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" >
-								</svg>pload-project
+								</svg>
 							</div>
 						</div>
 					</div>
@@ -115,7 +115,7 @@
 				</div>
 		<!-- modal content -->
                 <div id="schedule-flow" class="modal">
-                        <h3>Schedulemm A Flow</h3>
+                        <h3>Schedule Flow</h3>
                         <div id="errorMsg" class="box-error-message">$errorMsg</div>
 
                         <!--div class="message">
@@ -174,16 +174,48 @@
                                 <a class="yes btn3" id="schedule-btn" href="#">Schedule The Flow</a>
                                 <a class="no simplemodal-close btn4" href="#">Cancel</a>
                         </div>
-                        
-                        <div id="invalid-session" class="modal">
-                                <h3>Invalid Session</h3>
-                                <p>Session has expired. Please re-login.</p>
-                                <div class="actions">
-                                        <a class="yes btn3" id="login-btn" href="#">Re-login</a>
-                                </div>
+                </div>
+                <div id="invalid-session" class="modal">
+                        <h3>Invalid Session</h3>
+                        <p>Session has expired. Please re-login.</p>
+                        <div class="actions">
+                                <a class="yes btn3" id="login-btn" href="#">Re-login</a>
                         </div>
                 </div>
-
+                
+                <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>
+	                		<div>
+	                			<ul class="optionsPicker">
+	                				<li>General Options</li>
+	                				<li>Flow Options</li>
+	                			</ul>
+	                		</div>
+	                		<div class="optionsPane">
+	                			<div class="generalPanel panel">
+	                			</div>
+								<div class="graphPanel panel">
+									<div class="jobList">
+										<div id="filterList2">
+											<input id="filter2" placeholder="  Job Filter" />
+										</div>
+										<div id="list2">
+										</div>
+										<div id="resetPanZoomBtn2" class="btn5" >Reset Pan Zoom</div>
+									</div>
+								    <div class="svgDiv" >
+									</div>
+		                        </div>
+	                        </div>
+                    		<div class="actions">
+	                                <a class="yes btn1" id="execute-btn" href="#">Execute Now</a>
+	                                <a class="no simplemodal-close btn3" id="cancel-btn" href="#">Cancel</a>
+	                        </div>
+	                </div>
+                </div>
+                
 #end
 		<ul id="jobMenu" class="contextMenu flowSubmenu">  
 			<li class="open"><a href="#open">Open...</a></li>
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowstagingpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowstagingpage.vm
new file mode 100644
index 0000000..4baed0f
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/flowstagingpage.vm
@@ -0,0 +1,107 @@
+<!DOCTYPE html> 
+<html>
+	<head>
+#parse( "azkaban/webapp/servlet/velocity/style.vm" )
+		<script type="text/javascript" src="${context}/js/jquery/jquery.js"></script>    
+		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui.custom.min.js"></script>
+		<script type="text/javascript" src="${context}/js/jqueryui/jquery.ui.datepicker.min.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.date.utils.js"></script>  
+		<script type="text/javascript" src="${context}/js/namespace.js"></script>
+		<script type="text/javascript" src="${context}/js/underscore-1.2.1-min.js"></script>
+		<script type="text/javascript" src="${context}/js/backbone-0.5.3-min.js"></script>
+		<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.nav.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+		<script type="text/javascript" src="${context}/js/azkaban.staging.flow.view.js"></script>
+		<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
+		<script type="text/javascript">
+			var contextURL = "${context}";
+			var currentTime = ${currentTime};
+			var timezone = "${timezone}";
+			var errorMessage = "${error_message}";
+			var successMessage = "${success_message}";
+			
+			var projectName = "${project.name}";
+			var flowName = "${flowid}";
+			var retryExecution = "${retry}";
+		</script>
+		<script>
+			$(function() {
+				$( "#datepicker" ).datepicker();
+			});
+		</script>
+		<link rel="stylesheet" type="text/css" href="${context}/css/jquery.contextMenu.custom.css" /> 
+	</head>
+	<body>
+#set($current_page="all")
+#parse( "azkaban/webapp/servlet/velocity/nav.vm" )
+		<div class="messaging"><p id="messageClose">X</p><p id="message"></p></div>  
+		<div class="content">
+#if($errorMsg)
+				<div class="box-error-message">$errorMsg</div>
+#else
+#if($error_message != "null")
+				<div class="box-error-message">$error_message</div>
+#elseif($success_message != "null")
+				<div class="box-success-message">$success_message</div>
+#end
+
+				<div id="all-jobs-content">
+					<div class="section-hd flow-header">
+						<h2><a href="${context}/manager?project=${project.name}&flow=${flowid}">Preparing Execution Flow <span>$flowid</span></a></h2>
+						<div class="section-sub-hd">
+							<h4><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h4>
+						</div>
+						
+						<div id="executebtn" class="btn1">Execute Now</div>
+					</div>
+					
+					<div id="headertabs" class="headertabs">
+						<ul>
+							<li><a id="graphViewLink" href="#graph">Graph</a></li>
+						</ul>
+					</div>
+					<div id="graphView">
+						<div class="relative">
+							<div id="jobList">
+								<div id="filterList">
+									<input id="filter" placeholder="  Job Filter" />
+								</div>
+								<div id="list">
+								</div>
+								<div id="resetPanZoomBtn" class="btn5" >Reset Pan Zoom</div>
+							</div>
+							<div id="svgDiv" class="svgDivStaging" >
+								<svg id="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+								</svg>
+							</div>
+						</div>
+					</div>
+				</div>
+#end
+		<ul id="jobMenu" class="contextMenu flowSubmenu">  
+			<li class="open"><a href="#open">Open...</a></li>
+			<li class="openwindow"><a href="#openwindow">Open in New Window...</a></li>
+			<li id="disable" class="disable separator"><a href="#disable">Disable</a><div id="disableArrow" class="context-sub-icon"></div></li>
+			<ul id="disableSub" class="subMenu">
+				<li class="all"><a href="#disableAll">All</a></li>
+				<li class="parents"><a href="#disableParents">Parents</a></li>
+				<li class="ancestors"><a href="#disableAncestors">All Ancestors</a></li>
+				<li class="children"><a href="#disableChildren">Children</a></li>
+				<li class="decendents"><a href="#disableDescendents">All Descendents</a></li>
+			</ul>
+			<li id="enable" class="enable"><a href="#enable">Enable</a> <div id="enableArrow" class="context-sub-icon"></div></li>
+			<ul id="enableSub" class="subMenu">
+				<li class="all"><a href="#enableAll">All</a></li>
+				<li class="parents"><a href="#enableParents">Parents</a></li>
+				<li class="ancestors"><a href="#enableAncestors">All Ancestors</a></li>
+				<li class="children"><a href="#enableChildren">Children</a></li>
+				<li class="decendents"><a href="#enableDescendents">All Descendents</a></li>
+			</ul>
+		</ul>
+
+		</div>
+	</body>
+</html>
diff --git a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
index 5404daf..8dd6bbb 100644
--- a/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/jobpage.vm
@@ -59,7 +59,7 @@
 					<tr><td class="first">Dependents:</td><td>
 #if ($dependents) 
 #foreach($dependent in $dependents)
-						<a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependent">$dependent</a><span>,</span>
+						<span class="nowrap"><a href="${context}/manager?project=${project.name}&flow=${flowid}&job=$dependent">$dependent</a></span>
 #end
 #else
 						<span>No Dependencies</span>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 11c7103..45425e5 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -487,10 +487,20 @@ tr:hover td {
 }
 
 /* confirm modal */
-.modal {
+
+.modalContainer2 {
   display: none;
-  -moz-box-shadow: 5px 5px 5px #888;
-  -webkit-box-shadow: 5px 5px 5px #888;
+  position: absolute;
+}
+
+.modalBackground2 {
+	position: absolute;
+	display: none;
+	left: 0px;
+	right: 0px;
+	top: 0px;
+	bottom: 0px;
+	background-color: rgba(0, 0, 0, 0.4);
 }
 
 .search-input {
@@ -589,14 +599,18 @@ tr:hover td {
 }
 
 .modal {
+  display: none;
   background-color: #fff;
-  border: 1px solid #9c9c9c;
-  border-radius: 10px;
-  -moz-border-radius: 10px;
-
+  border: 1px solid #DDD;
+  -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
+  -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
   overflow: hidden;
 }
 
+.modal p {
+	margin-left: 30px;
+}
+
 .modal h3 {
   border-bottom: 1px solid #d0d0d0;
   font-weight: bold;
@@ -622,6 +636,18 @@ tr:hover td {
   padding: 16px 16px 20px 0;
 }
 
+.modalContainer2.modal .actions {
+  position: absolute;
+  background-color: #f0f0f0;
+  margin-top: 10px;
+  overflow: hidden;
+  padding: 16px 16px 20px 0;
+  bottom: 0px;
+  right: 0px;
+  left: 0px;
+}
+
+.modal .btn1,
 .modal .btn2,
 .modal .btn3,
 .modal .btn6 {
@@ -978,12 +1004,14 @@ tr:hover td {
 #job-summary {
 	margin-left: 30px;
 	margin-bottom: 10px;
-	width: 60%;
+	width:90%;
 }
 
 #job-summary table tr td a {
 	text-decoration: none;
 	color: #555;
+	margin-left: 0px;
+	margin-right: 10px;
 }
 
 #job-summary table tr td span {
@@ -995,6 +1023,10 @@ tr:hover td {
 	color: #009FC9;
 }
 
+#job-summary .first {
+	width: 100px;
+}
+
 .summary-table {
 	font-size: 12px;
 	background: none;
@@ -1010,6 +1042,10 @@ tr:hover td {
 	border-style: none;
 }
 
+.summary-table td.left{
+	text-align: left;
+}
+
 .user-table {
 	font-size: 12px;
 	background: none;
@@ -1235,21 +1271,103 @@ tr:hover td {
 	height: 100%;
 }
 
+#editIcon {
+	text-align: center;
+}
+
+#toolsBar .btn3{
+	float: left;
+	margin-left: 3px;
+}
+
 #svgGraph {
 	width: 100%;
 	height: 100%;
 	background: #fff;
 }
 
+
 #svgDiv {
 	position: absolute;
 	top: 0px;
 	right: 0px;
 	left: 260px;
 	bottom: 0px;
+}
+
+#svgDiv.svgDivStaging {
+	top: 30px;
+}
+
+#svgDivModal {
+	position: absolute;
+	top: 0px;
+	right: 0px;
+	left: 260px;
 	height: 100%;
 }
 
+#executing-options {
+	left: 100px;
+	right: 100px;
+	top: 50px;
+	bottom: 40px;
+}
+
+#executing-options .svgDiv {
+	position: absolute;
+	background-color: #CCC;
+	left: 260px;
+	right: 0px;
+	top: 0px;
+	bottom: 0px;
+}
+
+#executing-options .jobList {
+	position: absolute;
+	width: 260px;
+	top: 0px;
+	bottom: 0px;
+	background-color: #F00;
+}
+
+#executing-options ul.optionsPicker {
+	margin-left: 30px;
+}
+
+#executing-options ul.optionsPicker li {
+	float: left;
+	font-size: 12pt;
+	font-weight: bold;
+	margin-right: 15px;
+	cursor: pointer;
+	color: #CCC;
+}
+
+#executing-options ul.optionsPicker li:hover {
+	color: #888;
+}
+
+#executing-options .optionsPane {
+	position: absolute;
+	top: 85px;
+	background-color: #000;
+	left: 0px;
+	right: 0px;
+	bottom: 0px;
+}
+
+#executing-options .panel {
+	position: absolute;
+	width: 100%;
+	top: 0px;
+	bottom: 60px;
+}
+
+#executing-options .generalPanel.panel {
+	background-color: #ACC;
+}
+
 #jobList {
 	position: absolute;
 	top: 0px;
@@ -1778,6 +1896,10 @@ ul.disableMenu {
 	margin-top: 10px;
 }
 
+span .nowrap {
+	white-space: nowrap;
+}
+
 /* old styles */
 .azkaban-charts .hitarea {
 	background-image: url("../../js/jqueryui/themes/custom-theme/images/ui-icons_cccccc_256x240.png");
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 9555055..17b9d27 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -921,10 +921,69 @@ azkaban.ScheduleFlowView = Backbone.View.extend({
 
   },
   render: function() {
+	  
   }
 });
 
-
+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;
@@ -939,7 +998,7 @@ $(function() {
 	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
 	contextMenu = new azkaban.ContextMenu({el:$('#jobMenu')});
 	scheduleFlowView = new azkaban.ScheduleFlowView({el:$('#schedule-flow')});
-	
+	executeFlowView = new azkaban.ExecuteFlowView({el:$('#executing-options')});
 	var requestURL = contextURL + "/manager";
 
 	$.get(
@@ -1001,25 +1060,6 @@ $(function() {
 	      "json"
 	    );
 	    
-	$("#executebtn").click( function() {
-		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"
-		);
-		
-	});
-
 	$('#scheduleflowbtn').click( function() {
 	  console.log("schedule button clicked");
 	  $('#schedule-flow').modal({
diff --git a/src/web/js/azkaban.staging.flow.view.js b/src/web/js/azkaban.staging.flow.view.js
new file mode 100644
index 0000000..4f7a049
--- /dev/null
+++ b/src/web/js/azkaban.staging.flow.view.js
@@ -0,0 +1,1034 @@
+$.namespace('azkaban');
+
+var statusStringMap = {
+	"FAILED": "Failed",
+	"SUCCEEDED": "Success",
+	"FAILED_FINISHING": "Running w/Failure",
+	"RUNNING": "Running",
+	"WAITING": "Waiting",
+	"KILLED": "Killed",
+	"DISABLED": "Disabled",
+	"READY": "Ready",
+	"UNKNOWN": "Unknown"
+};
+
+var handleJobMenuClick = function(action, el, pos) {
+	var jobid = el[0].jobid;
+	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
+	var nodes = graphModel.get("nodes");
+	var disabled = graphModel.get("disabled");
+	
+	if (action == "open") {
+		window.location.href = requestURL;
+	}
+	else if(action == "openwindow") {
+		window.open(requestURL);
+	}
+	else if(action == "disable") {
+		disabled[jobid] = true;
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "disableAll") {
+		for (var key in nodes) {
+			disabled[key] = true;
+		}
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "disableParents") {
+		var inNodes = nodes[jobid].inNodes;
+
+		if (inNodes) {
+			for (var key in inNodes) {
+			  disabled[key] = true;
+			}
+		}
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "disableChildren") {
+		var outNodes = nodes[jobid].outNodes;
+
+		if (outNodes) {
+			for (var key in outNodes) {
+			  disabled[key] = true;
+			}
+		}
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "disableAncestors") {
+		recurseAllAncestors(nodes, disabled, jobid, true);
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "disableDescendents") {
+		recurseAllDescendents(nodes, disabled, jobid, true);
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if(action == "enable") {
+		delete disabled[jobid];
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if(action == "enableAll") {
+		graphModel.set({disabled: {}});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "enableParents") {
+		var inNodes = nodes[jobid].inNodes;
+
+		if (inNodes) {
+			for (var key in inNodes) {
+			  delete disabled[key];
+			}
+		}
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "enableChildren") {
+		var outNodes = nodes[jobid].outNodes;
+
+		if (outNodes) {
+			for (var key in outNodes) {
+			  delete disabled[key];
+			}
+		}
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "enableAncestors") {
+		recurseAllAncestors(nodes, disabled, jobid, false);
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+	else if (action == "enableDescendents") {
+		recurseAllDescendents(nodes, disabled, jobid, false);
+		
+		graphModel.set({disabled: disabled});
+		graphModel.trigger("change:disabled");
+	}
+}
+
+function recurseAllAncestors(nodes, disabledMap, id, disable) {
+	var node = nodes[id];
+	
+	if (node.inNodes) {
+		for (var key in node.inNodes) {
+			if (false) {
+				delete disabledMap[key];
+			}
+			else {
+				disabledMap[key] = true;
+			}
+			recurseAllAncestors(nodes, disabledMap, key, disable);
+		}
+	}
+}
+
+function recurseAllDescendents(nodes, disabledMap, id, disable) {
+	var node = nodes[id];
+	
+	if (node.outNodes) {
+		for (var key in node.outNodes) {
+			if (false) {
+				delete disabledMap[key];
+			}
+			else {
+				disabledMap[key] = true;
+			}
+			recurseAllDescendents(nodes, disabledMap, key, disable);
+		}
+	}
+}
+
+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 flowTabView;
+azkaban.FlowTabView= Backbone.View.extend({
+  events : {
+  	"click #graphViewLink" : "handleGraphLinkClick",
+  	"click #executionsViewLink" : "handleExecutionLinkClick"
+  },
+  initialize : function(settings) {
+  	var selectedView = settings.selectedView;
+  	if (selectedView == "executions") {
+  		this.handleExecutionLinkClick();
+  	}
+  	else {
+  		this.handleGraphLinkClick();
+  	}
+
+  },
+  render: function() {
+  	console.log("render graph");
+  },
+  handleGraphLinkClick: function(){
+  	$("#executionsViewLink").removeClass("selected");
+  	$("#graphViewLink").addClass("selected");
+  	
+  	$("#executionsView").hide();
+  	$("#graphView").show();
+  },
+  handleExecutionLinkClick: function() {
+  	$("#graphViewLink").removeClass("selected");
+  	$("#executionsViewLink").addClass("selected");
+  	
+  	 $("#graphView").hide();
+  	 $("#executionsView").show();
+  	 executionModel.trigger("change:view");
+  }
+});
+
+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 key in this.listNodes) {
+			if (disabledMap[key]) {
+				$(this.listNodes[key]).addClass("nodedisabled");
+			}
+			else {
+				$(this.listNodes[key]).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"
+	},
+	initialize: function(settings) {
+		this.model.bind('change:selected', this.changeSelected, this);
+		this.model.bind('change:disabled', this.handleDisabledChange, this);
+		this.model.bind('change:graph', this.render, this);
+		this.model.bind('resetPanZoom', this.resetPanZoom, 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.graphBounds = bounds;
+		this.resetPanZoom();
+	},
+	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});
+		}
+	},
+	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);
+		}
+	},
+	handleDisabledChange: function(evt) {
+		var disabledMap = this.model.get("disabled");
+		for (var id in this.nodes) {
+			var g = document.getElementById(id);
+			if (disabledMap[id]) {
+				this.nodes[id].disabled = true;
+				addClass(g, "disabled");
+			}
+			else {
+				this.nodes[id].disabled = false;
+				removeClass(g, "disabled");
+			}
+		}
+	},
+	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 executionsView;
+azkaban.ExecutionsView = Backbone.View.extend({
+	events: {
+		"click #pageSelection li": "handleChangePageSelection"
+	},
+	initialize: function(settings) {
+		this.model.bind('change:view', this.handleChangeView, this);
+		this.model.bind('render', this.render, this);
+		this.model.set({page: 1, pageSize: 16});
+		this.model.bind('change:page', this.handlePageChange, this);
+	},
+	render: function(evt) {
+		console.log("render");
+		// Render page selections
+		var tbody = $("#execTableBody");
+		tbody.empty();
+		
+		var executions = this.model.get("executions");
+		for (var i = 0; i < executions.length; ++i) {
+			var row = document.createElement("tr");
+			
+			var tdId = document.createElement("td");
+			var execA = document.createElement("a");
+			$(execA).attr("href", contextURL + "/executor?execid=" + executions[i].execId);
+			$(execA).text(executions[i].execId);
+			tdId.appendChild(execA);
+			row.appendChild(tdId);
+			
+			var tdUser = document.createElement("td");
+			$(tdUser).text(executions[i].submitUser);
+			row.appendChild(tdUser);
+			
+			var startTime = "-";
+			if (executions[i].startTime != -1) {
+				var startDateTime = new Date(executions[i].startTime);
+				startTime = getDateFormat(startDateTime);
+			}
+
+			var tdStartTime = document.createElement("td");
+			$(tdStartTime).text(startTime);
+			row.appendChild(tdStartTime);
+			
+			var endTime = "-";
+			var lastTime = executions[i].endTime;
+			if (executions[i].endTime != -1) {
+				var endDateTime = new Date(executions[i].endTime);
+				endTime = getDateFormat(endDateTime);
+			}
+			else {
+				lastTime = (new Date()).getTime();
+			}
+
+			var tdEndTime = document.createElement("td");
+			$(tdEndTime).text(endTime);
+			row.appendChild(tdEndTime);
+			
+			var tdElapsed = document.createElement("td");
+			$(tdElapsed).text( getDuration(executions[i].startTime, lastTime));
+			row.appendChild(tdElapsed);
+			
+			var tdStatus = document.createElement("td");
+			var status = document.createElement("div");
+			$(status).addClass("status");
+			$(status).addClass(executions[i].status);
+			$(status).text(statusStringMap[executions[i].status]);
+			tdStatus.appendChild(status);
+			row.appendChild(tdStatus);
+
+			var tdAction = document.createElement("td");
+			row.appendChild(tdAction);
+
+			tbody.append(row);
+		}
+		
+		this.renderPagination(evt);
+	},
+	renderPagination: function(evt) {
+		var total = this.model.get("total");
+		total = total? total : 1;
+		var pageSize = this.model.get("pageSize");
+		var numPages = Math.ceil(total/pageSize);
+		
+		this.model.set({"numPages": numPages});
+		var page = this.model.get("page");
+		
+		//Start it off
+		$("#pageSelection .selected").removeClass("selected");
+		
+		// Disable if less than 5
+		console.log("Num pages " + numPages)
+		var i = 1;
+		for (; i <= numPages && i <= 5; ++i) {
+			$("#page" + i).removeClass("disabled");
+		}
+		for (; i <= 5; ++i) {
+			$("#page" + i).addClass("disabled");
+		}
+		
+		// Disable prev/next if necessary.
+		if (page > 1) {
+			$("#previous").removeClass("disabled");
+			$("#previous")[0].page = page - 1;
+			$("#previous a").attr("href", "#page" + (page - 1));
+		}
+		else {
+			$("#previous").addClass("disabled");
+		}
+		
+		if (page < numPages) {
+			$("#next")[0].page = page + 1;
+			$("#next").removeClass("disabled");
+			$("#next a").attr("href", "#page" + (page + 1));
+		}
+		else {
+			$("#next")[0].page = page + 1;
+			$("#next").addClass("disabled");
+		}
+		
+		// Selection is always in middle unless at barrier.
+		if (page < 3) {
+			selectionPosition = page;
+		}
+		else if (page > numPages - 2) {
+			selectionPosition = 5 - (numPages - page) - 1;
+		}
+		else {
+			selectionPosition = 3;
+		}
+
+		$("#page"+selectionPosition).addClass("selected");
+		$("#page"+selectionPosition)[0].page = page;
+		var selecta = $("#page" + selectionPosition + " a");
+		selecta.text(page);
+		selecta.attr("href", "#page" + page);
+
+		for (var j = 1, tpage = page - selectionPosition + 1; j < selectionPosition; ++j, ++tpage) {
+			$("#page" + j)[0].page = tpage;
+			var a = $("#page" + i + " a");
+			a.text(tpage);
+			a.attr("href", "#page" + tpage);
+		}
+
+		for (var i = selectionPosition + 1, tpage = page + 1; i <= numPages; ++i, ++tpage) {
+			$("#page" + i)[0].page = tpage;
+			var a = $("#page" + i + " a");
+			a.text(tpage);
+			a.attr("href", "#page" + tpage);
+		}
+	},
+	handleChangePageSelection: function(evt) {
+		if ($(evt.currentTarget).hasClass("disabled")) {
+			return;
+		}
+		var page = evt.currentTarget.page;
+		
+		this.model.set({"page": page});
+	},
+	handleChangeView: function(evt) {
+		if (this.init) {
+			return;
+		}
+		
+		console.log("init");
+		this.handlePageChange(evt);
+		this.init = true;
+	},
+	handlePageChange: function(evt) {
+		var page = this.model.get("page") - 1;
+		var pageSize = this.model.get("pageSize");
+		var requestURL = contextURL + "/manager";
+		
+		var model = this.model;
+		$.get(
+			requestURL,
+			{"project": projectName, "flow":flowName, "ajax": "fetchFlowExecutions", "start":page * pageSize, "length": pageSize},
+			function(data) {
+				model.set({"executions": data.executions, "total": data.total});
+				model.trigger("render");
+			},
+			"json"
+		);
+		
+	}
+});
+
+var contextMenu;
+azkaban.ContextMenu = Backbone.View.extend({
+	events : {
+		"click #disableArrow" : "handleDisabledClick",
+		"click #enableArrow" : "handleEnabledClick"
+	},
+	initialize: function(settings) {
+		$('#disableSub').hide();
+		$('#enableSub').hide();
+	},
+	handleEnabledClick: function(evt) {
+		if(evt.stopPropagation) {
+			evt.stopPropagation();
+		}
+		evt.cancelBubble=true;
+		
+		if (evt.currentTarget.expanded) {
+			evt.currentTarget.expanded=false;
+			$('#enableArrow').removeClass('collapse');
+			$('#enableSub').hide();
+		}
+		else {
+			evt.currentTarget.expanded=true;
+			$('#enableArrow').addClass('collapse');
+			$('#enableSub').show();
+		}
+	},
+	handleDisabledClick: function(evt) {
+		if(evt.stopPropagation) {
+			evt.stopPropagation();
+		}
+		evt.cancelBubble=true;
+		
+		if (evt.currentTarget.expanded) {
+			evt.currentTarget.expanded=false;
+			$('#disableArrow').removeClass('collapse');
+			$('#disableSub').hide();
+		}
+		else {
+			evt.currentTarget.expanded=true;
+			$('#disableArrow').addClass('collapse');
+			$('#disableSub').show();
+		}
+	}
+});
+
+var graphModel;
+azkaban.GraphModel = Backbone.Model.extend({});
+
+var executionModel;
+azkaban.ExecutionModel = Backbone.Model.extend({});
+
+var scheduleFlowView;
+azkaban.ScheduleFlowView = Backbone.View.extend({
+  events : {
+    "click #schedule-btn": "handleScheduleFlow"
+  },
+  initialize : function(settings) {
+    $("#errorMsg").hide();
+  },
+  handleScheduleFlow : function(evt) {
+         // First make sure we can upload
+//     var projectName = $('#path').val();
+     var description = $('#description').val();
+
+     var hourVal = $('#hour').val();
+     var minutesVal = $('#minutes').val();
+     var ampmVal = $('#am_pm').val();
+     var dateVal = $('#datepicker').val();
+     var is_recurringVal = $('#is_recurring').val();
+     var periodVal = $('#period').val();
+     var periodUnits = $('#period_units').val();
+
+     console.log("Creating schedule for "+projectName+"."+flowName);
+     $.ajax({
+        async: "false",
+        url: "schedule",
+        dataType: "json",
+        type: "POST",
+        data: {
+		action:"scheduleFlow", 
+
+		projectId:projectName, 
+		flowId:flowName,
+		hour:hourVal,
+		minutes:minutesVal,
+		am_pm:ampmVal,
+		date:dateVal,
+		userExec:"dummy",
+		is_recurring:is_recurringVal,
+		period:periodVal,
+		period_units:periodUnits
+		},
+        success: function(data) {
+                if (data.status == "success") {
+			console.log("Successfully scheduled for "+projectName+"."+flowName);
+                        if (data.action == "redirect") {
+                                window.loaction = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ;
+                        }
+			else{
+				$("#success_message").text("Flow " + projectName + "." + flowName + " scheduled!" );			
+ 				window.location = contextURL + "/manager?project=" + projectName + "&flow=" + flowName ; 
+			}
+                }
+                else {
+                        if (data.action == "login") {
+                                        window.location = "";
+                        }
+                        else {
+                                $("#errorMsg").text("ERROR: " + data.message);
+                                $("#errorMsg").slideDown("fast");
+                        }
+                }
+        }
+     });
+
+  },
+  render: function() {
+  }
+});
+
+
+
+$(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();
+	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
+	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
+	contextMenu = new azkaban.ContextMenu({el:$('#jobMenu')});
+	scheduleFlowView = new azkaban.ScheduleFlowView({el:$('#schedule-flow')});
+	
+	var requestURL = contextURL + "/manager";
+
+	$.get(
+	      requestURL,
+	      {"project": projectName, "ajax":"fetchflowgraph", "flow":flowName},
+	      function(data) {
+	      	  // Create the nodes
+	      	  var nodes = {};
+	      	  for (var i=0; i < data.nodes.length; ++i) {
+	      	  	var node = data.nodes[i];
+	      	  	nodes[node.id] = node;
+	      	  }
+	      	  for (var i=0; i < data.edges.length; ++i) {
+	      	  	var edge = data.edges[i];
+	      	  	var fromNode = nodes[edge.from];
+	      	  	var toNode = nodes[edge.target];
+	      	  	
+	      	  	if (!fromNode.outNodes) {
+	      	  		fromNode.outNodes = {};
+	      	  	}
+	      	  	fromNode.outNodes[toNode.id] = toNode;
+	      	  	
+	      	  	if (!toNode.inNodes) {
+	      	  		toNode.inNodes = {};
+	      	  	}
+	      	  	toNode.inNodes[fromNode.id] = fromNode;
+	      	  }
+	      
+	          console.log("data fetched");
+	          graphModel.set({data: data});
+	          graphModel.set({nodes: nodes});
+	          graphModel.set({disabled: {}});
+	          graphModel.trigger("change:graph");
+	          
+	          // Handle the hash changes here so the graph finishes rendering first.
+	          if (window.location.hash) {
+				var hash = window.location.hash;
+				
+				if (hash == "#executions") {
+					flowTabView.handleExecutionLinkClick();
+				}
+				else if (hash == "#graph") {
+					// Redundant, but we may want to change the default. 
+					selected = "graph";
+				}
+				else {
+					if ("#page" == hash.substring(0, "#page".length)) {
+						var page = hash.substring("#page".length, hash.length);
+						console.log("page " + page);
+						flowTabView.handleExecutionLinkClick();
+						executionModel.set({"page": parseInt(page)});
+					}
+					else {
+						selected = "graph";
+					}
+				}
+			}
+	      },
+	      "json"
+	    );
+	    
+	$("#executebtn").click( function() {
+		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"
+		);
+		
+	});
+
+	$('#scheduleflowbtn').click( function() {
+	  console.log("schedule button clicked");
+	  $('#schedule-flow').modal({
+          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+          position: ["20%",],
+          containerId: 'confirm-container',
+          containerCss: {
+            'height': '220px',
+            'width': '565px'
+          },
+          onShow: function (dialog) {
+            var modal = this;
+            $("#errorMsg").hide();
+          }
+        });
+	});
+});