azkaban-developers
Changes
src/java/azkaban/utils/Mailman.java 40(+20 -20)
src/web/css/azkaban.css 138(+130 -8)
src/web/js/azkaban.flow.view.js 82(+61 -21)
src/web/js/azkaban.staging.flow.view.js 1034(+1034 -0)
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;
src/java/azkaban/utils/Mailman.java 40(+20 -20)
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>
src/web/css/azkaban.css 138(+130 -8)
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");
src/web/js/azkaban.flow.view.js 82(+61 -21)
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({
src/web/js/azkaban.staging.flow.view.js 1034(+1034 -0)
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();
+ }
+ });
+ });
+});