azkaban-aplcache
Changes
azkaban-web-server/src/web/js/azkaban/view/flow.js 435(+232 -203)
Details
diff --git a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowpage.vm b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowpage.vm
index bcbb9ab..abfbaa1 100644
--- a/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/azkaban-web-server/src/main/resources/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -25,6 +25,7 @@
<script type="text/javascript" src="${context}/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="${context}/js/raphael.min.js"></script>
<script type="text/javascript" src="${context}/js/morris.min.js"></script>
+ <script type="text/javascript" src="${context}/js/jquery.twbsPagination.min.js"></script>
<script type="text/javascript" src="${context}/js/dust-full-2.2.3.min.js"></script>
<script type="text/javascript" src="${context}/js/flowsummary.js?version=20161003"></script>
@@ -49,6 +50,28 @@
var flowId = "${flowid}";
var execId = null;
var pageSize = "${size}";
+
+ var flowPageSettings = {
+ projectId: ${project.id},
+ projectName: "${project.name}",
+ flowId: "${flowid}",
+ pageSize: ${size},
+
+ executionUrl: contextURL + "/executor",
+ triggerInstanceUrl: contextURL + "/executor",
+ fetchFlowExecutionsUrl: contextURL + "/manager",
+ fetchTriggerInstancesUrl: contextURL + "/flowtriggerinstance",
+ fetchFlowDetailsUrl: contextURL + "/manager",
+ fetchScheduleUrl: contextURL + "/schedule",
+ slaInfoUrl: contextURL + "/schedule",
+ fetchLastSuccessfulFlowExecutionUrl: contextURL + "/manager",
+ fetchTriggerUrl: contextURL + "/flowtrigger",
+ fetchFlowGraphUrl: contextURL + "/manager",
+ };
+
+ $(function () {
+ initFlowPage(flowPageSettings);
+ });
</script>
<link rel="stylesheet" type="text/css" href="${context}/css/morris.css"/>
<link rel="stylesheet" type="text/css" href="${context}/css/bootstrap-datetimepicker.css"/>
@@ -74,8 +97,8 @@
</div>
<div class="header-control">
<div class="pull-right header-form">
- <button type="button" class="btn btn-sm btn-success" id="executebtn">Schedule / Execute
- Flow
+ <button type="button" class="btn btn-sm btn-success" id="executebtn">
+ Schedule / Execute Flow
</button>
</div>
</div>
@@ -135,15 +158,8 @@
<tbody id="execTableBody">
</tbody>
</table>
- <ul id="pageSelection" class="pagination">
- <li id="previous" class="first"><a><span class="arrow">←</span>Previous</a></li>
- <li id="page1"><a href="#page1">1</a></li>
- <li id="page2"><a href="#page2">2</a></li>
- <li id="page3"><a href="#page3">3</a></li>
- <li id="page4"><a href="#page4">4</a></li>
- <li id="page5"><a href="#page5">5</a></li>
- <li id="next"><a>Next<span class="arrow">→</span></a></li>
- </ul>
+
+ <ul id="executionsPagination" class="pagination"></ul>
</div>
</div>
</div><!-- /.container-fill -->
@@ -154,7 +170,7 @@
<div class="col-xs-12">
<table class="table table-striped table-bordered table-condensed table-hover"
- id="execTable">
+ id="flowtriggerTable">
<thead>
<tr>
<th>Flow Trigger Instance Id</th>
@@ -166,18 +182,11 @@
<th class="action">Action</th>
</tr>
</thead>
- <tbody id="triggerTableBody">
+ <tbody id="flowtriggerTableBody">
</tbody>
</table>
- <ul id="pageSelection" class="pagination">
- <li id="previous" class="first"><a><span class="arrow">←</span>Previous</a></li>
- <li id="page1"><a href="#page1">1</a></li>
- <li id="page2"><a href="#page2">2</a></li>
- <li id="page3"><a href="#page3">3</a></li>
- <li id="page4"><a href="#page4">4</a></li>
- <li id="page5"><a href="#page5">5</a></li>
- <li id="next"><a>Next<span class="arrow">→</span></a></li>
- </ul>
+
+ <ul id="flowtriggerPagination" class="pagination"></ul>
</div>
</div>
</div><!-- /.container-fill -->
@@ -192,8 +201,8 @@
<div class="col-xs-12">
<div class="callout callout-info">
<h4>Analyze last run</h4>
- <p>Analyze the last run for aggregate performance statistics. Note: this may take a few
- minutes, especially if your flow is large.</p>
+ <p>Analyze the last run for aggregate performance statistics.
+ Note: this may take a few minutes, especially if your flow is large.</p>
<p>
<button type="button" id="analyze-btn" class="btn btn-primary">Analyze</button>
</p>
@@ -216,4 +225,3 @@
</div><!-- /.container -->
#end
</body>
-</body>
azkaban-web-server/src/web/js/azkaban/view/flow.js 435(+232 -203)
diff --git a/azkaban-web-server/src/web/js/azkaban/view/flow.js b/azkaban-web-server/src/web/js/azkaban/view/flow.js
index 7d97353..a4ebe61 100644
--- a/azkaban-web-server/src/web/js/azkaban/view/flow.js
+++ b/azkaban-web-server/src/web/js/azkaban/view/flow.js
@@ -26,7 +26,24 @@ var handleJobMenuClick = function (action, el, pos) {
else if (action == "openwindow") {
window.open(requestURL);
}
-}
+};
+
+var initPagination = function (elem, model) {
+ var totalPages = model.get("total");
+ if (!totalPages) {
+ return;
+ }
+
+ $(elem).twbsPagination({
+ totalPages: Math.ceil(totalPages / model.get("pageSize")),
+ startPage: model.get("page"),
+ initiateStartPageClick: false,
+ visiblePages: model.get("visiblePages"),
+ onPageClick: function (event, page) {
+ model.set({"page": page});
+ }
+ });
+};
var flowTabView;
azkaban.FlowTabView = Backbone.View.extend({
@@ -73,7 +90,7 @@ azkaban.FlowTabView = Backbone.View.extend({
$("#flowtriggerView").hide();
$("#executionsView").show();
$('#summaryView').hide();
- executionModel.trigger("change:view");
+ executionModel.trigger("initView");
},
handleFlowTriggerLinkClick: function () {
@@ -86,7 +103,7 @@ azkaban.FlowTabView = Backbone.View.extend({
$("#flowtriggerView").show();
$("#executionsView").hide();
$('#summaryView').hide();
- flowTriggerModel.trigger("change:view");
+ flowTriggerModel.trigger("initView");
},
handleSummaryLinkClick: function () {
@@ -104,31 +121,23 @@ azkaban.FlowTabView = Backbone.View.extend({
var jobListView;
var svgGraphView;
-var executionsView;
+var executionModel;
+azkaban.ExecutionModel = Backbone.Model.extend({});
+
+var executionsView;
azkaban.ExecutionsView = Backbone.View.extend({
- events: {
- "click #pageSelection li": "handleChangePageSelection"
- },
initialize: function (settings) {
- this.model.bind('change:view', this.handleChangeView, this);
+ // Interested on first tab activation only to load init data
+ this.model.once('initView', this.handleInitView, this);
this.model.bind('render', this.render, this);
- console.log("during initialization, pageSize: " + pageSize);
- this.model.set({page: 1, pageSize: pageSize});
this.model.bind('change:page', this.handlePageChange, this);
},
- render: function (evt) {
- console.log("render");
+ render: function () {
// Render page selections
- var content = this.model.get("content");
- if (content == "flow") {
- var tbody = $("#execTableBody");
- }
- else {
- var tbody = $("#triggerTableBody");
- }
+ var tbody = $("#execTableBody");
tbody.empty();
var executions = this.model.get("executions");
@@ -137,16 +146,10 @@ azkaban.ExecutionsView = Backbone.View.extend({
var tdId = document.createElement("td");
var execA = document.createElement("a");
- if (content == "flow") {
- $(execA).attr("href", contextURL + "/executor?execid="
- + executions[i].execId);
- $(execA).text(executions[i].execId);
- }
- else {
- $(execA).attr("href", contextURL + "/executor?triggerinstanceid="
- + executions[i].instanceId);
- $(execA).text(executions[i].instanceId);
- }
+ $(execA).attr("href", this.model.get("executionUrl") + "?execid="
+ + executions[i].execId);
+ $(execA).text(executions[i].execId);
+
tdId.appendChild(execA);
row.appendChild(tdId);
@@ -186,12 +189,7 @@ azkaban.ExecutionsView = Backbone.View.extend({
var status = document.createElement("div");
$(status).addClass("status");
$(status).addClass(executions[i].status);
- if (content == "flow") {
- $(status).text(statusStringMap[executions[i].status]);
- }
- else {
- $(status).text(executions[i].status);
- }
+ $(status).text(statusStringMap[executions[i].status]);
tdStatus.appendChild(status);
row.appendChild(tdStatus);
@@ -200,140 +198,170 @@ azkaban.ExecutionsView = Backbone.View.extend({
tbody.append(row);
}
+ },
- this.renderPagination(evt);
+ handleInitView: function () {
+ var model = this.model;
+ this.loadAndRenderData(function () {
+ initPagination('#executionsPagination', model);
+ });
},
- 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);
+ handlePageChange: function () {
+ this.loadAndRenderData();
+ },
- this.model.set({"numPages": numPages});
- var page = this.model.get("page");
+ loadAndRenderData: function (onDataLoadedCb) {
+ var model = this.model;
+ var currentPage = model.get("page");
+ var pageSize = model.get("pageSize");
+ var requestURL = model.get("fetchFlowExecutionsUrl");
+ var requestData = {
+ "project": model.get("projectName"),
+ "flow": model.get("flowId"),
+ "ajax": "fetchFlowExecutions",
+ "start": (currentPage - 1) * pageSize,
+ "length": pageSize
+ };
- //Start it off
- $("#pageSelection .active").removeClass("active");
+ $.get(requestURL, requestData)
+ .done(function (data) {
+ model.set({
+ "executions": data.executions,
+ "total": data.total
+ });
+ model.trigger("render");
+ if (onDataLoadedCb) {
+ onDataLoadedCb();
+ }
+ })
+ .fail(function () {
+ // TODO
+ });
+ }
+});
- // 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");
- }
+var flowTriggerModel;
+azkaban.FlowTriggerModel = Backbone.Model.extend({});
- // 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");
- }
+var flowTriggersView;
+azkaban.FlowTriggersView = Backbone.View.extend({
- 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");
- }
+ initialize: function (settings) {
+ // Interested on first tab activation only to load init data
+ this.model.once('initView', this.handleInitView, this);
+ this.model.bind('render', this.render, this);
+ this.model.bind('change:page', this.handlePageChange, this);
+ },
- // Selection is always in middle unless at barrier.
- var startPage = 0;
- var selectionPosition = 0;
- if (page < 3) {
- selectionPosition = page;
- startPage = 1;
- }
- else if (page == numPages) {
- selectionPosition = 5;
- startPage = numPages - 4;
- }
- else if (page == numPages - 1) {
- selectionPosition = 4;
- startPage = numPages - 4;
- }
- else {
- selectionPosition = 3;
- startPage = page - 2;
- }
+ render: function () {
+ // Render page selections
+ var tbody = $("#flowtriggerTableBody");
+ tbody.empty();
- $("#page" + selectionPosition).addClass("active");
- $("#page" + selectionPosition)[0].page = page;
- var selecta = $("#page" + selectionPosition + " a");
- selecta.text(page);
- selecta.attr("href", "#page" + page);
+ var executions = this.model.get("executions");
+ for (var i = 0; i < executions.length; ++i) {
+ var row = document.createElement("tr");
- for (var j = 0; j < 5; ++j) {
- var realPage = startPage + j;
- var elementId = "#page" + (j + 1);
+ var tdId = document.createElement("td");
+ var execA = document.createElement("a");
+ $(execA).attr("href", this.model.get("triggerInstanceUrl") +
+ "?triggerinstanceid=" + executions[i].instanceId);
+ $(execA).text(executions[i].instanceId);
+ tdId.appendChild(execA);
+ row.appendChild(tdId);
- $(elementId)[0].page = realPage;
- var a = $(elementId + " a");
- a.text(realPage);
- a.attr("href", "#page" + realPage);
- }
- },
+ var tdUser = document.createElement("td");
+ $(tdUser).text(executions[i].submitUser);
+ row.appendChild(tdUser);
- handleChangePageSelection: function (evt) {
- if ($(evt.currentTarget).hasClass("disabled")) {
- return;
+ 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 && executions[i].endTime != 0) {
+ 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(executions[i].status);
+ tdStatus.appendChild(status);
+ row.appendChild(tdStatus);
+
+ var tdAction = document.createElement("td");
+ row.appendChild(tdAction);
+
+ tbody.append(row);
}
- var page = evt.currentTarget.innerText;
- this.model.set({"page": page});
},
- handleChangeView: function (evt) {
- if (this.init) {
- return;
- }
- console.log("init");
- this.handlePageChange(evt);
- this.init = true;
+ handleInitView: function () {
+ var model = this.model;
+ this.loadAndRenderData(function () {
+ initPagination('#flowtriggerPagination', model);
+ });
},
- handlePageChange: function (evt) {
- var page = this.model.get("page") - 1;
- var pageSize = this.model.get("pageSize");
- console.log("pageSize = " + pageSize)
- var content = this.model.get("content");
- if (content == 'flow') {
- requestURL = contextURL + "/manager";
- }
- else {
- requestURL = contextURL + "/flowtriggerinstance";
- }
+ handlePageChange: function () {
+ this.loadAndRenderData();
+ },
+ loadAndRenderData: function (onDataLoadedCb) {
var model = this.model;
+ var currentPage = model.get("page");
+ var pageSize = model.get("pageSize");
+ var requestURL = model.get("fetchTriggerInstancesUrl");
var requestData = {
- "project": projectName,
- "flow": flowId,
- "ajax": content == 'flow' ? "fetchFlowExecutions"
- : "fetchTriggerInstances",
- "start": page * pageSize,
+ "project": model.get("projectName"),
+ "flow": model.get("flowId"),
+ "ajax": "fetchTriggerInstances",
+ "start": (currentPage - 1) * pageSize,
"length": pageSize
};
- var successHandler = function (data) {
+ $.get(requestURL, requestData)
+ .done(function (data) {
model.set({
- "content": content,
"executions": data.executions,
"total": data.total
});
model.trigger("render");
- };
- $.get(requestURL, requestData, successHandler, "json");
+ if (onDataLoadedCb) {
+ onDataLoadedCb();
+ }
+ })
+ .fail(function () {
+ // TODO
+ });
}
});
+var summaryModel;
+azkaban.SummaryModel = Backbone.Model.extend({});
+
var summaryView;
azkaban.SummaryView = Backbone.View.extend({
events: {
@@ -351,20 +379,19 @@ azkaban.SummaryView = Backbone.View.extend({
},
fetchDetails: function () {
- var requestURL = contextURL + "/manager";
+ var model = this.model;
+ var requestURL = model.get("fetchFlowDetailsUrl");
var requestData = {
'ajax': 'fetchflowdetails',
- 'project': projectName,
- 'flow': flowId
+ 'project': model.get("projectName"),
+ 'flow': model.get("flowId")
};
- var model = this.model;
-
var successHandler = function (data) {
console.log(data);
model.set({
- 'jobTypes': data.jobTypes,
- 'condition': data.condition
+ jobTypes: data.jobTypes,
+ condition: data.condition
});
model.trigger('render');
};
@@ -372,13 +399,14 @@ azkaban.SummaryView = Backbone.View.extend({
},
fetchSchedule: function () {
- var requestURL = contextURL + "/schedule"
+ var model = this.model;
+ var requestURL = model.get("fetchScheduleUrl");
var requestData = {
'ajax': 'fetchSchedule',
- 'projectId': projectId,
- 'flowId': flowId
+ 'projectId': model.get("projectId"),
+ 'flowId': model.get("flowId")
};
- var model = this.model;
+
var view = this;
var successHandler = function (data) {
model.set({'schedule': data.schedule});
@@ -394,12 +422,13 @@ azkaban.SummaryView = Backbone.View.extend({
return;
}
- var requestURL = contextURL + "/schedule"
+ var model = this.model;
+ var requestURL = model.get("slaInfoUrl");
var requestData = {
"scheduleId": schedule.scheduleId,
"ajax": "slaInfo"
};
- var model = this.model;
+
var successHandler = function (data) {
if (data == null || data.settings == null || data.settings.length == 0) {
return;
@@ -412,11 +441,12 @@ azkaban.SummaryView = Backbone.View.extend({
},
fetchLastRun: function () {
- var requestURL = contextURL + "/manager";
+ var model = this.model;
+ var requestURL = model.get("fetchLastSuccessfulFlowExecutionUrl");
var requestData = {
'ajax': 'fetchLastSuccessfulFlowExecution',
- 'project': projectName,
- 'flow': flowId
+ 'project': model.get("projectName"),
+ 'flow': model.get("flowId")
};
var view = this;
var successHandler = function (data) {
@@ -432,14 +462,14 @@ azkaban.SummaryView = Backbone.View.extend({
},
fetchFlowTrigger: function () {
- var requestURL = contextURL + "/flowtrigger"
+ var model = this.model;
+ var requestURL = model.get("fetchTriggerUrl");
var requestData = {
'ajax': 'fetchTrigger',
- 'projectId': projectId,
- 'flowId': flowId
+ 'projectId': model.get("projectId"),
+ 'flowId': model.get("flowId")
};
- var model = this.model;
- var view = this;
+
var successHandler = function (data) {
model.set({'flowtrigger': data.flowTrigger});
model.trigger('render');
@@ -451,13 +481,14 @@ azkaban.SummaryView = Backbone.View.extend({
},
render: function (evt) {
+ var model = this.model;
var data = {
- projectName: projectName,
- flowName: flowId,
- jobTypes: this.model.get('jobTypes'),
- condition: this.model.get('condition'),
- schedule: this.model.get('schedule'),
- flowtrigger: this.model.get('flowtrigger'),
+ projectName: model.get('projectName'),
+ flowName: model.get('flowId'),
+ jobTypes: model.get('jobTypes'),
+ condition: model.get('condition'),
+ schedule: model.get('schedule'),
+ flowtrigger: model.get('flowtrigger')
};
dust.render("flowsummary", data, function (err, out) {
$('#summary-view-content').html(out);
@@ -467,40 +498,53 @@ azkaban.SummaryView = Backbone.View.extend({
var graphModel;
var mainSvgGraphView;
-
-var executionModel;
-azkaban.ExecutionModel = Backbone.Model.extend({});
-
-var flowTriggerModel;
-azkaban.FlowTriggerModel = Backbone.Model.extend({});
-
-var summaryModel;
-azkaban.SummaryModel = Backbone.Model.extend({});
-
var flowStatsView;
var flowStatsModel;
-
var executionsTimeGraphView;
var slaView;
-$(function () {
+var initFlowPage = function (settings) {
var selected;
+
// Execution model has to be created before the window switches the tabs.
- executionModel = new azkaban.ExecutionModel();
- executionModel.set("content", "flow");
+ executionModel = new azkaban.ExecutionModel({
+ page: 1,
+ pageSize: settings.pageSize,
+ visiblePages: 5,
+ projectName: settings.projectName,
+ flowId: settings.flowId,
+ executionUrl: settings.executionUrl,
+ fetchFlowExecutionsUrl: settings.fetchFlowExecutionsUrl
+ });
executionsView = new azkaban.ExecutionsView({
el: $('#executionsView'),
model: executionModel
});
- flowTriggerModel = new azkaban.ExecutionModel();
- flowTriggerModel.set("content", "trigger");
- flowTriggerView = new azkaban.ExecutionsView({
+ flowTriggerModel = new azkaban.FlowTriggerModel({
+ page: 1,
+ pageSize: settings.pageSize,
+ visiblePages: 5,
+ projectName: settings.projectName,
+ flowId: settings.flowId,
+ triggerInstanceUrl: settings.triggerInstanceUrl,
+ fetchTriggerInstancesUrl: settings.fetchTriggerInstancesUrl
+ });
+ flowTriggersView = new azkaban.FlowTriggersView({
el: $('#flowtriggerView'),
model: flowTriggerModel
});
- summaryModel = new azkaban.SummaryModel();
+ summaryModel = new azkaban.SummaryModel({
+ projectId: settings.projectId,
+ projectName: settings.projectName,
+ flowId: settings.flowId,
+ fetchFlowDetailsUrl: settings.fetchFlowDetailsUrl,
+ fetchScheduleUrl: settings.fetchScheduleUrl,
+ slaInfoUrl: settings.slaInfoUrl,
+ fetchLastSuccessfulFlowExecutionUrl: settings.fetchLastSuccessfulFlowExecutionUrl,
+ fetchTriggerUrl: settings.fetchTriggerUrl
+ });
summaryView = new azkaban.SummaryView({
el: $('#summaryView'),
model: summaryModel
@@ -542,24 +586,21 @@ $(function () {
slaView = new azkaban.ChangeSlaView({el: $('#sla-options')});
- var requestURL = contextURL + "/manager";
// Set up the Flow options view. Create a new one every time :p
$('#executebtn').click(function () {
- var data = graphModel.get("data");
- var nodes = data.nodes;
var executingData = {
- project: projectName,
+ project: settings.projectName,
ajax: "executeFlow",
- flow: flowId
+ flow: settings.flowId
};
flowExecuteDialogView.show(executingData);
});
var requestData = {
- "project": projectName,
+ "project": settings.projectName,
"ajax": "fetchflowgraph",
- "flow": flowId
+ "flow": settings.flowId
};
var successHandler = function (data) {
console.log("data fetched");
@@ -578,23 +619,11 @@ $(function () {
if (hash == "#flowtriggers") {
flowTabView.handleFlowTriggerLinkClick();
}
-
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";
- }
- }
}
};
- $.get(requestURL, requestData, successHandler, "json");
-});
+ $.get(settings.fetchFlowGraphUrl, requestData, successHandler, "json");
+};
diff --git a/azkaban-web-server/src/web/js/jquery.twbsPagination.min.js b/azkaban-web-server/src/web/js/jquery.twbsPagination.min.js
new file mode 100755
index 0000000..315f27b
--- /dev/null
+++ b/azkaban-web-server/src/web/js/jquery.twbsPagination.min.js
@@ -0,0 +1,10 @@
+/*
+ * jQuery Bootstrap Pagination v1.4.2
+ * https://github.com/josecebe/twbs-pagination
+ *
+ * Copyright 2014-2018, Eugene Simakin <john-24@list.ru>
+ * Released under Apache-2.0 license
+ * http://apache.org/licenses/LICENSE-2.0.html
+ */
+
+!function(o,e,t,s){"use strict";var i=o.fn.twbsPagination,r=function(t,s){if(this.$element=o(t),this.options=o.extend({},o.fn.twbsPagination.defaults,s),this.options.startPage<1||this.options.startPage>this.options.totalPages)throw new Error("Start page option is incorrect");if(this.options.totalPages=parseInt(this.options.totalPages),isNaN(this.options.totalPages))throw new Error("Total pages option is not correct!");if(this.options.visiblePages=parseInt(this.options.visiblePages),isNaN(this.options.visiblePages))throw new Error("Visible pages option is not correct!");if(this.options.beforePageClick instanceof Function&&this.$element.first().on("beforePage",this.options.beforePageClick),this.options.onPageClick instanceof Function&&this.$element.first().on("page",this.options.onPageClick),this.options.hideOnlyOnePage&&1==this.options.totalPages)return this.options.initiateStartPageClick&&this.$element.trigger("page",1),this;if(this.options.href&&(this.options.startPage=this.getPageFromQueryString(),this.options.startPage||(this.options.startPage=1)),"UL"===("function"==typeof this.$element.prop?this.$element.prop("tagName"):this.$element.attr("tagName")))this.$listContainer=this.$element;else{var e=this.$element,i=o([]);e.each(function(t){var s=o("<ul></ul>");o(this).append(s),i.push(s[0])}),this.$listContainer=i,this.$element=i}return this.$listContainer.addClass(this.options.paginationClass),this.options.initiateStartPageClick?this.show(this.options.startPage):(this.currentPage=this.options.startPage,this.render(this.getPages(this.options.startPage)),this.setupEvents()),this};r.prototype={constructor:r,destroy:function(){return this.$element.empty(),this.$element.removeData("twbs-pagination"),this.$element.off("page"),this},show:function(t){if(t<1||t>this.options.totalPages)throw new Error("Page is incorrect.");this.currentPage=t,this.$element.trigger("beforePage",t);var s=this.getPages(t);return this.render(s),this.setupEvents(),this.$element.trigger("page",t),s},enable:function(){this.show(this.currentPage)},disable:function(){var t=this;this.$listContainer.off("click").on("click","li",function(t){t.preventDefault()}),this.$listContainer.children().each(function(){o(this).hasClass(t.options.activeClass)||o(this).addClass(t.options.disabledClass)})},buildListItems:function(t){var s=[];if(this.options.first&&s.push(this.buildItem("first",1)),this.options.prev){var e=1<t.currentPage?t.currentPage-1:this.options.loop?this.options.totalPages:1;s.push(this.buildItem("prev",e))}for(var i=0;i<t.numeric.length;i++)s.push(this.buildItem("page",t.numeric[i]));if(this.options.next){var a=t.currentPage<this.options.totalPages?t.currentPage+1:this.options.loop?1:this.options.totalPages;s.push(this.buildItem("next",a))}return this.options.last&&s.push(this.buildItem("last",this.options.totalPages)),s},buildItem:function(t,s){var e=o("<li></li>"),i=o("<a></a>"),a=this.options[t]?this.makeText(this.options[t],s):s;return e.addClass(this.options[t+"Class"]),e.data("page",s),e.data("page-type",t),e.append(i.attr("href",this.makeHref(s)).addClass(this.options.anchorClass).html(a)),e},getPages:function(t){var s=[],e=Math.floor(this.options.visiblePages/2),i=t-e+1-this.options.visiblePages%2,a=t+e,n=this.options.visiblePages;n>this.options.totalPages&&(n=this.options.totalPages),i<=0&&(i=1,a=n),a>this.options.totalPages&&(i=this.options.totalPages-n+1,a=this.options.totalPages);for(var o=i;o<=a;)s.push(o),o++;return{currentPage:t,numeric:s}},render:function(s){var e=this;this.$listContainer.children().remove();var t=this.buildListItems(s);o.each(t,function(t,s){e.$listContainer.append(s)}),this.$listContainer.children().each(function(){var t=o(this);switch(t.data("page-type")){case"page":t.data("page")===s.currentPage&&t.addClass(e.options.activeClass);break;case"first":t.toggleClass(e.options.disabledClass,1===s.currentPage);break;case"last":t.toggleClass(e.options.disabledClass,s.currentPage===e.options.totalPages);break;case"prev":t.toggleClass(e.options.disabledClass,!e.options.loop&&1===s.currentPage);break;case"next":t.toggleClass(e.options.disabledClass,!e.options.loop&&s.currentPage===e.options.totalPages)}})},setupEvents:function(){var e=this;this.$listContainer.off("click").on("click","li",function(t){var s=o(this);if(s.hasClass(e.options.disabledClass)||s.hasClass(e.options.activeClass))return!1;!e.options.href&&t.preventDefault(),e.show(parseInt(s.data("page")))})},changeTotalPages:function(t,s){return this.options.totalPages=t,this.show(s)},makeHref:function(t){return this.options.href?this.generateQueryString(t):"#"},makeText:function(t,s){return t.replace(this.options.pageVariable,s).replace(this.options.totalPagesVariable,this.options.totalPages)},getPageFromQueryString:function(t){var s=this.getSearchString(t),e=new RegExp(this.options.pageVariable+"(=([^&#]*)|&|#|$)").exec(s);return e&&e[2]?(e=decodeURIComponent(e[2]),e=parseInt(e),isNaN(e)?null:e):null},generateQueryString:function(t,s){var e=this.getSearchString(s),i=new RegExp(this.options.pageVariable+"=*[^&#]*");return e?"?"+e.replace(i,this.options.pageVariable+"="+t):""},getSearchString:function(t){var s=t||e.location.search;return""===s?null:(0===s.indexOf("?")&&(s=s.substr(1)),s)},getCurrentPage:function(){return this.currentPage},getTotalPages:function(){return this.options.totalPages}},o.fn.twbsPagination=function(t){var s,e=Array.prototype.slice.call(arguments,1),i=o(this),a=i.data("twbs-pagination"),n="object"==typeof t?t:{};return a||i.data("twbs-pagination",a=new r(this,n)),"string"==typeof t&&(s=a[t].apply(a,e)),void 0===s?i:s},o.fn.twbsPagination.defaults={totalPages:1,startPage:1,visiblePages:5,initiateStartPageClick:!0,hideOnlyOnePage:!1,href:!1,pageVariable:"{{page}}",totalPagesVariable:"{{total_pages}}",page:null,first:"First",prev:"Previous",next:"Next",last:"Last",loop:!1,beforePageClick:null,onPageClick:null,paginationClass:"pagination",nextClass:"page-item next",prevClass:"page-item prev",lastClass:"page-item last",firstClass:"page-item first",pageClass:"page-item",activeClass:"active",disabledClass:"disabled",anchorClass:"page-link"},o.fn.twbsPagination.Constructor=r,o.fn.twbsPagination.noConflict=function(){return o.fn.twbsPagination=i,this},o.fn.twbsPagination.version="1.4.2"}(window.jQuery,window,document);
\ No newline at end of file