azkaban-uncached

Initial restyle of project page.

11/18/2013 2:04:52 PM

Details

diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel2.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel2.vm
new file mode 100644
index 0000000..f338e2d
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel2.vm
@@ -0,0 +1,185 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+<script type="text/javascript" src="${context}/js/azkaban.layout.js"></script>
+<script type="text/javascript" src="${context}/js/svgNavigate.js"></script>
+<script type="text/javascript" src="${context}/js/azkaban.context.menu.js"></script>
+<script type="text/javascript" src="${context}/js/azkaban.common.utils.js"></script>
+<script type="text/javascript" src="${context}/js/azkaban.svg.graph.view.js"></script>
+<script type="text/javascript" src="${context}/js/azkaban.flow.execute.view.js"></script>
+
+<div id="modalBackground" class="modalBackground2">
+<div id="execute-flow-panel" class="modal modalContainer2">
+	<h3 id="execute-flow-panel-title"></h3>
+	<a title="Close" class="modal-close closeExecPanel">x</a>
+	<div id="execute-message" class="message">
+	</div>
+	
+	<div class="panel">
+		<div id="executionGraphOptions">
+			<div id="graphOptions" class="sideMenu">
+				<h3 id="flowOption" viewpanel="svgDivCustom">Flow View</h3>
+				<div>
+					<p>Right click on the jobs to disable and enable jobs in the flow.</p>
+				</div>
+				<h3 viewpanel="notificationPanel">Notification</h3>
+				<div>
+					<p>Change the addresses where success and failure emails will be sent.</p>
+				</div>
+				<h3 viewpanel="failureOptions">Failure Options</h3>
+				<div>
+					<p>Select flow behavior when a failure is detected.</p>
+				</div>
+				<h3 viewpanel="concurrentPanel">Concurrent</h3>
+				<div>
+					<p>Change the behavior of the flow if it is already running.</p>
+				</div>
+				<h3 viewpanel="flowParametersPanel">Flow Parameters</h3>
+				<div>
+					<p>Add temporary flow parameters that are used to override global properties for each job.</p>
+				</div>
+			</div>
+		</div>
+		<div id="executionGraphOptionsPanel" class="rightPanel">
+			<div id="svgDivCustom" class="svgDiv sidePanel" >
+				<svg class="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
+				</svg>
+			</div>
+			<div id="notificationPanel" class="sidePanel">
+				<div>
+					<h4>Notify on Failure</h4>
+					<p>On a job failure, notify on either the first failure, and/or when the failed flow finishes.</p>
+					<input id="notifyFailureFirst" class="checkbox" type="checkbox" name="notify" value="first" checked /> <label for="notify">First Failure</label>
+					<input id="notifyFailureLast" class="checkbox" type="checkbox" name="notify" value="last"></input> <label for="notify">Flow Finished</label>
+					
+
+					<h4>Failure Emails</h4>
+					<div>
+						<input id="overrideFailureEmails" type="checkbox" name="overrideFailureEmails" value="overrideFailureEmails" />
+						<label for="overrideFailureEmails">Override flow email settings</label>
+					</div>
+					<p>Notify these addresses on failure. Comma, space or semi-colon delimited list.</p>
+					<textarea id="failureEmails"></textarea>
+				</div>
+			
+				<div>
+					<h4>Success Emails</h4>
+					<div>
+						<input id="overrideSuccessEmails" type="checkbox" name="overrideSuccessEmails" value="overrideSuccessEmails" />
+						<label for="overrideSuccessEmails">Override flow email settings</label>
+					</div>
+					<p>Notify when the flow finishes successfully. Comma, space or semi-colon delimited list.</p>
+					<textarea id="successEmails"></textarea>
+				</div>
+			</div> 
+			<div id="failureOptions" class="failureOptions sidePanel">
+				<h4>Failure Options</h4>
+				<p>When a failure first occurs in the flow, select the execution behavior.</p>
+				<ul>
+					<li><span class="bold">Finish Current Running</span> finishes only the currently running jobs. It will not start any new jobs.</p></li>
+					<li><span class="bold">Cancel All</span> immediately kills all jobs and fails the flow.</p></li>
+					<li><span class="bold">Finish All Possible</span> will keep executing jobs as long as its dependencies are met.</p></li>
+				</ul>
+
+				<select id="failureAction" name="failureAction">
+					<option value="finishCurrent">Finish Current Running</option>
+					<option value="cancelImmediately">Cancel All</option>
+					<option value="finishPossible">Finish All Possible</option>
+				</select>
+			</div>
+			<div id="concurrentPanel" class="sidePanel">
+				<h4>Concurrent Execution Options</h4>
+				<p>If the flow is currently running, these are the options that can be set.</p>
+
+				<input id="skip" class="radio" type="radio" name="concurrent" value="skip" checked /><label for="skip">Skip Execution</label>
+				<p>Do not run flow if it is already running.</p>
+				
+				<input id="ignore" class="radio" type="radio" name="concurrent" value="ignore" checked /><label for="ignore">Run Concurrently</label>
+				<p>Run the flow anyways. Previous execution is unaffected.</p>
+
+				<input id="pipeline" class="radio" type="radio" name="concurrent" value="pipeline" /><label for="pipeline">Pipeline</label>
+				<select id="pipelineLevel" name="pipelineLevel">
+					<option value="1">Level 1</option>
+					<option value="2">Level 2</option>
+				</select>
+				<p>Pipeline the flow, so the current execution will not be overrun.</p>
+				<ul>
+					<li>Level 1: block job A until the previous flow job A has completed.</li>
+					<li>Level 2: block job A until the previous flow job A's children have completed.</li>
+				</ul>
+				<!--
+				<input id="queue" class="radio" type="radio" name="concurrent" value="queue" /><label for="queue">Queue Job</label>
+				<select id="queueLevel" name="queueLevel">
+					<option value="1">1</option>
+					<option value="2">2</option>
+				</select>
+				<p>Queue up to 2. Wait until the previous execution has completed before running.</p>
+				-->
+			</div>
+			<div id="flowParametersPanel" class="sidePanel">
+				<h4>Flow Property Override</h4>
+				<div id="editTable" class="tableDiv">
+					<table>
+						<thead>
+							<tr>
+								<th>Name</th>
+								<th>Value</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr id="addRow" class="addRow"><td id="addRow-col" colspan="2"><span class="addIcon"></span><a>Add Row</a></td></tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+	</div>
+	
+	<div class="actions">
+		#if(!$show_schedule || $show_schedule == 'true') 
+		<a class="btn2" id="schedule-btn">Schedule</a>
+		#end
+		
+#*
+		#if( $triggerPlugins.size() > 0 ) 
+			#foreach( $triggerPlugin in $triggerPlugins )
+			<a class="btn2" id=set-$triggerPlugin.pluginName>$triggerPlugin.pluginName</a>
+			#end
+		#end
+*#
+		<a class="yes btn1" id="execute-btn">Execute</a>
+		<a class="no simplemodal-close btn3 closeExecPanel">Cancel</a>
+	</div>
+</div>
+</div>
+
+#if(!$show_schedule || $show_schedule == 'true') 
+#parse( "azkaban/webapp/servlet/velocity/schedulepanel.vm" )
+#end
+
+#*
+#if( $triggerPlugins.size() > 0 ) 
+	#foreach( $triggerPlugin in $triggerPlugins )
+		#set ($prefix = $triggerPlugin.pluginName )
+		#set ($webpath = $triggerPlugin.pluginPath )
+		#parse( $triggerPlugin.inputPanelVM )
+	#end
+#end
+*#
+
+<div id="contextMenu">
+	
+</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/index.vm b/src/java/azkaban/webapp/servlet/velocity/index.vm
index 86427cc..2887e91 100644
--- a/src/java/azkaban/webapp/servlet/velocity/index.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/index.vm
@@ -174,7 +174,7 @@
 
 ## Modal dialog to be displayed when the user sesion is invalid. -->
 			
-			<div class="modal fade">
+			<div class="modal fade" id="invalid-session-modal">
 				<div class="modal-dialog">
 					<div class="modal-content">
 						<div class="modal-header">
diff --git a/src/java/azkaban/webapp/servlet/velocity/javascript.vm b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
index 2b2c316..40b35fd 100644
--- a/src/java/azkaban/webapp/servlet/velocity/javascript.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/javascript.vm
@@ -16,6 +16,7 @@
 
 		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
 		<script type="text/javascript" src="${context}/js/bootstrap.min.js"></script>    
+		<script type="text/javascript" src="${context}/js/bootstrap-fileinput.js"></script>    
 		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
 		<script type="text/javascript" src="${context}/js/namespace.js"></script>
 		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
diff --git a/src/java/azkaban/webapp/servlet/velocity/messagedialog2.vm b/src/java/azkaban/webapp/servlet/velocity/messagedialog2.vm
new file mode 100644
index 0000000..f50118a
--- /dev/null
+++ b/src/java/azkaban/webapp/servlet/velocity/messagedialog2.vm
@@ -0,0 +1,34 @@
+#*
+ * Copyright 2012 LinkedIn Corp.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+*#
+
+			<script type="text/javascript" src="${context}/js/azkaban.message.dialog2.view.js"></script>
+
+			<div class="modal fade" id="azkaban-message-dialog">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title" id="azkaban-message-dialog-title"></h4>
+						</div>
+						<div class="modal-body">
+							<p id="azkaban-message-dialog-text"></p>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-primary" id="continue-btn">Continue</button>
+						</div>
+					</div>
+				</div>
+			</div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
index ef3d281..7d11650 100644
--- a/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/projectpage.vm
@@ -15,22 +15,15 @@
 *#
 
 <!DOCTYPE html> 
-<html>
+<html lang="en">
 	<head>
-#parse( "azkaban/webapp/servlet/velocity/style.vm" )
-		<script type="text/javascript" src="${context}/js/jquery/jquery-1.9.1.js"></script>    
-		<script type="text/javascript" src="${context}/js/jqueryui/jquery-ui-1.10.1.custom.js"></script>
-		
-		<script type="text/javascript" src="${context}/js/underscore-1.4.4-min.js"></script>
-		<script type="text/javascript" src="${context}/js/namespace.js"></script>
-		<script type="text/javascript" src="${context}/js/backbone-0.9.10-min.js"></script>
-		<script type="text/javascript" src="${context}/js/jquery.simplemodal-1.4.4.js"></script>
+
+#parse ("azkaban/webapp/servlet/velocity/style2.vm")
+#parse ("azkaban/webapp/servlet/velocity/javascript.vm")
+
 		<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.project.view.js"></script>
-
-		<link rel="stylesheet" type="text/css" href="${context}/css/jquery-ui-1.10.1.custom.css" />
-
 		<script type="text/javascript">
 			var contextURL = "${context}";
 			var currentTime = ${currentTime};
@@ -39,149 +32,208 @@
 			var successMessage = null;
 
 			var projectId = ${project.id};
-
 			var execAccess = ${exec};
 			var projectName = "$project.name";
 		</script>
 	</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"><pre>$errorMsg</pre></div>
-#else
-#if($error_message != "null")
-				<div class="box-error-message"><pre>$error_message</pre></div>
-#elseif($success_message != "null")
-				<div class="box-success-message"><pre>$success_message</pre></div>
-#end
+#set ($current_page="all")
+#parse ("azkaban/webapp/servlet/velocity/nav2.vm")
 
-				<div id="all-jobs-content">
-					<div class="section-hd">
-						<h2><a href="${context}/manager?project=${project.name}">Project <span>$project.name</span></a></h2>
-						<a id="project-upload-btn" class="btn1 projectupload">Upload</a>
-						<a id="project-permission-btn" class="btn5 projectpermission" href="${context}/manager?project=${project.name}&permissions">Permissions</a>
-						#if($admin)
-						<a id="project-logs-btn" class="btn2" href="${context}/manager?project=${project.name}&logs">Project Logs</a>
-						<a id="project-delete-btn" class="btn6">Delete Project</a>
-						#end
-					</div><!-- end .section-hd -->
-				</div>
+		<div class="container">
 
-				<div id="project-users">
-					<table class="user-table">
-						<tr><td class="first">Project Admins:</td><td>$admins</td></tr>
-						<tr><td class="first">Your Permissions:</td><td>$userpermission.toString()</td></tr>
-					</table>
-				</div>
+## Page error or success message.
 
-				<div id="project-summary">
-					<table class="summary-table">
-						<tr><td class="first">Name:</td><td>$project.name</td></tr>
-						<tr><td class="first">Created Date:</td><td>$utils.formatDate($project.createTimestamp)</td></tr>
-						<tr><td class="first">Modified Date:</td><td>$utils.formatDate($project.lastModifiedTimestamp)</td></tr>
-						<tr><td class="first">Modified by:</td><td>$project.lastModifiedUser</td></tr>
-						<tr><td class="first">Description:</td><td id="pdescription">$project.description</td>
-							#if($admin)
-								<td><div id="edit" class="btn5">Edit Description</div></td>
-							#end
-						</tr>
-					</table>
+#if ($errorMsg)
+			<div class="panel panel-danger">
+				<div class="panel-heading">Error</div>
+				<div class="panel-body">
+					$errorMsg
 				</div>
-
-			<div id="flow-tabs">
-				<table id="all-jobs" class="all-jobs job-table">
-					<thead>
-						<tr>
-							<th class="tb-name">Flow Name</th>
-						</tr>
-					</thead>
-					<tbody>
-#if($flows)
-#foreach($flow in $flows)
-						<tr class="row flowrow">
-							<td class="tb-name" flow="${flow.id}" project="${project.name}">
-									<div class="jobfolder expand" id="${flow.id}">
-										<span class="state-icon"></span>
-										<a href="${context}/manager?project=${project.name}&flow=${flow.id}">${flow.id}</a>
-									</div>
-									#if (${exec})
-									<div class="job-hover-menu">
-										<div class="btn1 executeFlow" flowId="${flow.id}">Execute Flow</div>
-									</div>
-									#end
-							</td>
-						</tr>
-						<tr class="childrow" id="${flow.id}-child" style="display: none;">
-							<td class="expandedFlow">
-								<table class="innerTable">
-									<thead>
-										<tr><th class="tb-name">Jobs</th></tr>
-									</thead>
-									<tbody id="${flow.id}-tbody">
-									</tbody>
-								</table>
-							</td>
-						</tr>
-#end
-#else
-						<tr><td class="last">No flows uploaded to this project.</td></tr>
-#end
-					</tbody>
-				</table>
 			</div>
-#end
-		</div>
-	
-		<div id="upload-project" class="modal">
-			<h3>Upload Project Files</h3>
-			<div id="errorMsg" class="box-error-message">$errorMsg</div>
-			<div class="message">
-				<form id="upload-form" enctype="multipart/form-data" method="post" action="$!context/manager">
-					<fieldset>
-						<dl>
-							<dt>Job Archive</dt>
-							<dd><input id="file" name="file" class="file" type="file" /></dd>
-							<input type="hidden" name="project" value="$project.name" />
-							<input type="hidden" name="action" value="upload" />
-						</dl>
-					</fieldset>
-				</form>
+#else
+	#if ($error_message != "null")
+			<div class="alert alert-danger">$error_message</div>
+	#elseif ($success_message != "null")
+			<div class="alert alert-success">$success_message</div>
+	#end
+
+## Alert message
+
+			<div class="alert alert-dismissable" id="messaging">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+				<p id="messaging-message"></p>
 			</div>
-			<div class="actions">
-				<a class="yes btn2" id="upload-btn" href="#">Upload</a>
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
+
+			<div class="page-header">
+				<h1><a href="${context}/manager?project=${project.name}">Project <small>$project.name</small></a></h1>
+				<p>$project.description</p>
 			</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 btn2" id="login-btn" href="#">Re-login</a>
+
+			<div class="row">
+				<div class="col-lg-8">
+					<div class="panel panel-default">
+						<div class="panel-heading">Flows</div>
+						<div class="panel-body">
+
+	#if ($flows)
+		#foreach ($flow in $flows)
+							<div class="panel panel-info" flow="${flow.id}" project="${project.name}">
+								<div class="panel-heading" id="${flow.id}">
+									<span>+</span>
+									<a href="${context}/manager?project=${project.name}&flow=${flow.id}">${flow.id}</a>
+								</div>
+								<div class="panel-body">
+									<div class="panel panel-default">
+										<div class="panel-heading">Jobs</div>
+										<div class="panel-body" id="${flow.id}-body">
+										</div>
+									</div>
+								</div>
+								<div class="panel-footer">
+			#if (${exec})
+									<button type="button" class="btn btn-primary" flowId="${flow.id}">Execute Flow</button>
+			#end
+								</div>
+							</div>
+		#end
+	#else
+							<p>No flows uploaded to this project</p>
+	#end
+						</div>
+					</div>
+				</div><!-- /col-lg-8 -->
+
+				<div class="col-lg-4">
+					<div class="well">
+						<table>
+							<tbody>
+								<tr><th>Name:</td><td>$project.name</td></tr>
+								<tr><th>Created Date:</td><td>$utils.formatDate($project.createTimestamp)</td></tr>
+								<tr><th>Modified Date:</td><td>$utils.formatDate($project.lastModifiedTimestamp)</td></tr>
+								<tr><th>Modified by:</td><td>$project.lastModifiedUser</td></tr>
+							</tbody>
+						</table>
+
+						<hr>
+
+						<table>
+							<tbody>
+								<tr><th>Project Admins:</td><td>$admins</td></tr>
+								<tr><th>Your Permissions:</td><td>$userpermission.toString()</td></tr>
+							</tbody>
+						</table>
+					</div>
+
+					<div class="well">
+						<button id="project-upload-btn" class="btn btn-primary">Upload</button>
+						<button id="project-delete-btn" class="btn btn-danger">Delete Project</button>
+					</div>
+
+					<div class="well well-sm">
+						<ul class="nav nav-pills nav-stacked">
+							<li class="active"><a href="${context}/manager?project=${project.name}">Project</a></li>
+						<li><a id="project-permission-btn" href="${context}/manager?project=${project.name}&permissions">Permissions</a></li>
+	#if ($admin)
+							<li><a id="project-logs-btn" href="${context}/manager?project=${project.name}&logs">Project Logs</a></li>
+	#end
+						</ul>
+					</div>
+				</div><!-- /col-lg-4 -->
+			</div><!-- /row -->
+
+## Upload project modal
+
+			<div class="modal fade" id="upload-project-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Upload Project Files</h4>
+						</div>
+						<div class="modal-body">
+							<div class="alert alert-danger" id="modal-error-msg">$error_msg</div>
+							<form id="upload-form" enctype="multipart/form-data" method="post" action="$!context/manager">
+								<fieldset class="form-horizontal">
+									<div class="form-group">
+										<label for="path" class="col-sm-2 control-label">Job Archive</label>
+										<div class="col-sm-10">
+											<div class="fileinput fileinput-new" data-provides="datainput">
+												<div class="form-control uneditable-input span3" data-trigger="fileinput">
+													<i class="glyphicon glyphicon-file fileinput-exists"></i>
+													<span class="fileinput-filename"></span>
+												</div>
+												<span class="input-group-addon btn btn-default btn-file">
+													<span class="fileinput-new">Select file</span>
+													<span class="fileinput-exists">Change</span>
+													<input type="file" name="...">
+												</span>
+												<a href="#" class="input-group-addon btn btn-default fileinput-exists" data-dismiss="fileinput">Remove</a>
+											</div>
+										</div>
+									</div>
+								</fieldset>
+							</form>
+						</div>
+						<div class="modal-footer">
+							<input type="hidden" name="project" value="$project.name">
+							<input type="hidden" name="action" value="upload">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="upload-btn">Upload</button>
+						</div>
+					</div>
 				</div>
 			</div>
-		</div>
-		<div id="delete-project" class="modal">
-			<h3>Delete Project</h3>
-			<div class="warn">
-				<div class="warning-icon"></div>
-				<div class="warning-message"><p>Warning: This project will be deleted and may not be recoverable.</p></div>
+
+## Modal dialog to be displayed when the user sesion is invalid. -->
+			
+			<div class="modal fade" id="invalid-session-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Invalid Session</h4>
+						</div>
+						<div class="modal-body">
+							<p>Session has expired. Please re-login.</p>
+						</div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-primary" id="login-btn">Re-login</button>
+						</div>
+					</div>
+				</div>
 			</div>
-			<form id="delete-form">
-				<input type="hidden" name="project" value="$project.name" />
-				<input type="hidden" name="delete" value="true" />
-			</form>
+
+## Delete project modal.
 			
-			<div class="actions">
-				<a class="no simplemodal-close btn3" href="#">Cancel</a>
-				<a class="yes btn6" id="delete-btn" href="#">Yes</a>
+			<div class="modal fade" id="delete-project-modal">
+				<div class="modal-dialog">
+					<div class="modal-content">
+						<div class="modal-header">
+							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+							<h4 class="modal-title">Delete Project</h4>
+						</div>
+						<div class="modal-body">
+							<p>Warning: This project will be deleted and may not be recoverable.</p>
+						</div>
+						<div class="modal-footer">
+							<input type="hidden" name="project" value="$project.name">
+							<input type="hidden" name="delete" value="true">
+							<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+							<button type="button" class="btn btn-primary" id="delete-btn">Yes</button>
+						</div>
+					</div>
+				</div>
 			</div>
-		</div>
-		#parse( "azkaban/webapp/servlet/velocity/flowexecutionpanel.vm" )
-		#parse( "azkaban/webapp/servlet/velocity/messagedialog.vm" )
+
+	#parse ("azkaban/webapp/servlet/velocity/flowexecutionpanel2.vm")
+	#parse ("azkaban/webapp/servlet/velocity/messagedialog2.vm")
+#end
+
+#parse ("azkaban/webapp/servlet/velocity/footer.vm")
+
+		</div><!-- /container -->
 	</body>
-	
 </html>
 
diff --git a/src/java/azkaban/webapp/servlet/velocity/style2.vm b/src/java/azkaban/webapp/servlet/velocity/style2.vm
index 8411f84..226ea13 100644
--- a/src/java/azkaban/webapp/servlet/velocity/style2.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/style2.vm
@@ -20,6 +20,7 @@
 		<link rel="shortcut icon" href="${context}/favicon.ico" />
     <!-- Bootstrap core CSS -->
     <link href="/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/css/bootstrap-fileinput.css" rel="stylesheet">
 
     <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
     <!--[if lt IE 9]>
diff --git a/src/web/css/bootstrap-fileinput.css b/src/web/css/bootstrap-fileinput.css
new file mode 100644
index 0000000..1929506
--- /dev/null
+++ b/src/web/css/bootstrap-fileinput.css
@@ -0,0 +1,93 @@
+
+.fileinput {
+  display: inline-block;
+  margin-bottom: 9px;
+}
+
+.fileinput .uneditable-input {
+  display: inline-block;
+  margin-bottom: 0;
+  vertical-align: middle;
+  cursor: text;
+}
+
+.fileinput .thumbnail {
+  display: inline-block;
+  margin-bottom: 5px;
+  overflow: hidden;
+  text-align: center;
+  vertical-align: middle;
+}
+
+.fileinput .thumbnail > img {
+  max-height: 100%;
+}
+
+.fileinput .btn {
+  vertical-align: middle;
+}
+
+.fileinput-exists .fileinput-new,
+.fileinput-new .fileinput-exists {
+  display: none;
+}
+
+.fileinput-inline .fileinput-controls {
+  display: inline;
+}
+
+.fileinput .uneditable-input {
+  white-space: normal;
+}
+
+.fileinput-new .input-group .btn-file {
+  border-radius: 0 4px 4px 0;
+}
+
+.fileinput-new .input-group .btn-file.btn-xs,
+.fileinput-new .input-group .btn-file.btn-sm {
+  border-radius: 0 3px 3px 0;
+}
+
+.fileinput-new .input-group .btn-file.btn-lg {
+  border-radius: 0 6px 6px 0;
+}
+
+.form-group.has-warning .fileinput .uneditable-input {
+  color: #c09853;
+  border-color: #faebcc;
+}
+
+.form-group.has-warning .fileinput .fileinput-preview {
+  color: #c09853;
+}
+
+.form-group.has-warning .fileinput .thumbnail {
+  border-color: #faebcc;
+}
+
+.form-group.has-error .fileinput .uneditable-input {
+  color: #b94a48;
+  border-color: #ebccd1;
+}
+
+.form-group.has-error .fileinput .fileinput-preview {
+  color: #b94a48;
+}
+
+.form-group.has-error .fileinput .thumbnail {
+  border-color: #ebccd1;
+}
+
+.form-group.has-success .fileinput .uneditable-input {
+  color: #468847;
+  border-color: #d6e9c6;
+}
+
+.form-group.has-success .fileinput .fileinput-preview {
+  color: #468847;
+}
+
+.form-group.has-success .fileinput .thumbnail {
+  border-color: #d6e9c6;
+}
diff --git a/src/web/js/bootstrap.fileinput.js b/src/web/js/bootstrap.fileinput.js
new file mode 100644
index 0000000..88c26c2
--- /dev/null
+++ b/src/web/js/bootstrap.fileinput.js
@@ -0,0 +1,179 @@
+/* ===========================================================
+ * Bootstrap: fileinput.js v3.0.0-p7
+ * http://jasny.github.com/bootstrap/javascript.html#fileinput
+ * ===========================================================
+ * Copyright 2012 Jasny BV, Netherlands.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
++function ($) { "use strict";
+
+  var isIE = window.navigator.appName == 'Microsoft Internet Explorer'
+
+  // FILEUPLOAD PUBLIC CLASS DEFINITION
+  // =================================
+
+  var Fileupload = function (element, options) {
+    this.$element = $(element)
+      
+    this.$input = this.$element.find(':file')
+    if (this.$input.length === 0) return
+
+    this.name = this.$input.attr('name') || options.name
+
+    this.$hidden = this.$element.find('input[type=hidden][name="'+this.name+'"]')
+    if (this.$hidden.length === 0) {
+      this.$hidden = $('<input type="hidden" />')
+      this.$element.prepend(this.$hidden)
+    }
+
+    this.$preview = this.$element.find('.fileinput-preview')
+    var height = this.$preview.css('height')
+    if (this.$preview.css('display') != 'inline' && height != '0px' && height != 'none') this.$preview.css('line-height', height)
+
+    this.original = {
+      exists: this.$element.hasClass('fileinput-exists'),
+      preview: this.$preview.html(),
+      hiddenVal: this.$hidden.val()
+    }
+    
+    this.listen()
+  }
+  
+  Fileupload.prototype.listen = function() {
+    this.$input.on('change.bs.fileinput', $.proxy(this.change, this))
+    $(this.$input[0].form).on('reset.bs.fileinput', $.proxy(this.reset, this))
+    
+    this.$element.find('[data-trigger="fileinput"]').on('click.bs.fileinput', $.proxy(this.trigger, this))
+    this.$element.find('[data-dismiss="fileinput"]').on('click.bs.fileinput', $.proxy(this.clear, this))
+  },
+
+  Fileupload.prototype.change = function(e) {
+    if (e.target.files === undefined) e.target.files = e.target && e.target.value ? [ {name: e.target.value.replace(/^.+\\/, '')} ] : []
+    if (e.target.files.length === 0) return
+
+    this.$hidden.val('')
+    this.$hidden.attr('name', '')
+    this.$input.attr('name', this.name)
+
+    var file = e.target.files[0]
+
+    if (this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match('image.*') : file.name.match(/\.(gif|png|jpe?g)$/i)) && typeof FileReader !== "undefined") {
+      var reader = new FileReader()
+      var preview = this.$preview
+      var element = this.$element
+
+      reader.onload = function(re) {
+        var $img = $('<img>').attr('src', re.target.result)
+        e.target.files[0].result = re.target.result
+        
+        element.find('.fileinput-filename').text(file.name)
+        
+        // if parent has max-height, using `(max-)height: 100%` on child doesn't take padding and border into account
+        if (preview.css('max-height') != 'none') $img.css('max-height', parseInt(preview.css('max-height'), 10) - parseInt(preview.css('padding-top'), 10) - parseInt(preview.css('padding-bottom'), 10)  - parseInt(preview.css('border-top'), 10) - parseInt(preview.css('border-bottom'), 10))
+        
+        preview.html($img)
+        element.addClass('fileinput-exists').removeClass('fileinput-new')
+
+        element.trigger('change.bs.fileinput', e.target.files)
+      }
+
+      reader.readAsDataURL(file)
+    } else {
+      this.$element.find('.fileinput-filename').text(file.name)
+      this.$preview.text(file.name)
+      
+      this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
+      
+      this.$element.trigger('change.bs.fileinput')
+    }
+  },
+
+  Fileupload.prototype.clear = function(e) {
+    if (e) e.preventDefault()
+    
+    this.$hidden.val('')
+    this.$hidden.attr('name', this.name)
+    this.$input.attr('name', '')
+
+    //ie8+ doesn't support changing the value of input with type=file so clone instead
+    if (isIE) { 
+      var inputClone = this.$input.clone(true);
+      this.$input.after(inputClone);
+      this.$input.remove();
+      this.$input = inputClone;
+    } else {
+      this.$input.val('')
+    }
+
+    this.$preview.html('')
+    this.$element.find('.fileinput-filename').text('')
+    this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
+    
+    if (e !== false) {
+      this.$input.trigger('change')
+      this.$element.trigger('clear.bs.fileinput')
+    }
+  },
+
+  Fileupload.prototype.reset = function() {
+    this.clear(false)
+
+    this.$hidden.val(this.original.hiddenVal)
+    this.$preview.html(this.original.preview)
+    this.$element.find('.fileinput-filename').text('')
+
+    if (this.original.exists) this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
+     else this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
+    
+    this.$element.trigger('reset.bs.fileinput')
+  },
+
+  Fileupload.prototype.trigger = function(e) {
+    this.$input.trigger('click')
+    e.preventDefault()
+  }
+
+  
+  // FILEUPLOAD PLUGIN DEFINITION
+  // ===========================
+
+  $.fn.fileinput = function (options) {
+    return this.each(function () {
+      var $this = $(this)
+      , data = $this.data('fileinput')
+      if (!data) $this.data('fileinput', (data = new Fileupload(this, options)))
+      if (typeof options == 'string') data[options]()
+    })
+  }
+
+  $.fn.fileinput.Constructor = Fileupload
+
+
+  // FILEUPLOAD DATA-API
+  // ==================
+
+  $(document).on('click.fileinput.data-api', '[data-provides="fileinput"]', function (e) {
+    var $this = $(this)
+    if ($this.data('fileinput')) return
+    $this.fileinput($this.data())
+      
+    var $target = $(e.target).closest('[data-dismiss="fileinput"],[data-trigger="fileinput"]');
+    if ($target.length > 0) {
+      e.preventDefault()
+      $target.trigger('click.bs.fileinput')
+    }
+  })
+
+}(window.jQuery);