diff --git a/lib/python3.11/site-packages/luigi/static/visualiser/css/luigi.css b/lib/python3.11/site-packages/luigi/static/visualiser/css/luigi.css
new file mode 100644
index 0000000000..83ecf77816
--- /dev/null
+++ b/lib/python3.11/site-packages/luigi/static/visualiser/css/luigi.css
@@ -0,0 +1,254 @@
+.nodeCircle {
+ stroke: #fff;
+ stroke-width: 1.5px;
+}
+text {
+ font-size:8pt;
+}
+
+.link {
+ stroke: #999;
+ stroke-opacity: .6;
+}
+
+svg {
+ border:1px solid #DDDDDD;
+ overflow: inherit;
+}
+
+.taskRow {
+ word-break:break-all;
+}
+
+
+@-webkit-keyframes flash {
+ 0%, 50%, 100% {
+ opacity: 1;
+ }
+
+ 25%, 75% {
+ opacity: 0.2;
+ }
+}
+
+@keyframes flash {
+ 0%, 50%, 100% {
+ opacity: 1;
+ }
+
+ 25%, 75% {
+ opacity: 0.2;
+ }
+}
+
+.RUNNING {
+ -webkit-animation-duration: 5s;
+ animation-duration: 5s;
+ -webkit-animation-fill-mode: both;
+ animation-fill-mode: both;
+ -webkit-animation-iteration-count: 1;
+ animation-iteration-count: 1;
+}
+
+.live.map {
+ width: 100%;
+ height: 600px;
+ background: #333;
+}
+
+.live.map text {
+ font-weight: 300;
+ font-size: 14px;
+}
+
+.live.map .node rect {
+ stroke-width: 1.5px;
+ stroke: #bbb;
+ fill: #666;
+}
+
+.live.map .status {
+ height: 100%;
+ width: 15px;
+ display: block;
+ float: left;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ margin-right: 4px;
+}
+
+.live.map .DONE .status {
+ background-color: #7f7;
+}
+
+.live.map .RUNNING .status {
+ background-color: #7f7;
+}
+
+.live.map .PENDING .status {
+ background-color: #FFFF46;
+}
+
+.live.map .ERROR .status {
+ background-color: #f77;
+}
+
+.live.map .FAILED .status {
+ background-color: #dd4b39;
+}
+
+.live.map .RUNNING .queue {
+ color: #f77;
+}
+
+.live.map .DISABLED .status {
+ background-color: #aaaaaa;
+}
+
+.RUNNING {
+ -webkit-animation-name: flash;
+ animation-name: flash;
+}
+
+.live.map .consumers {
+ margin-right: 2px;
+}
+
+.live.map .consumers,
+.live.map .name {
+ margin-top: 4px;
+}
+
+.live.map .consumers:after {
+ content: "x";
+}
+
+.live.map .queue {
+ display: block;
+ float: left;
+ width: 130px;
+ height: 20px;
+ font-size: 12px;
+ margin-top: 2px;
+}
+
+.live.map .node g div {
+ width: 200px;
+ height: 40px;
+ color: #fff;
+}
+
+.live.map .node g div span.consumers {
+ display: inline-block;
+ width: 20px;
+}
+
+.live.map .edgeLabel text {
+ width: 50px;
+ fill: #fff;
+}
+
+.live.map .edgePath path {
+ stroke: #999;
+ stroke-width: 1.5px;
+ fill: #999;
+}
+
+td.details-control {
+ cursor: pointer;
+}
+
+span.status-icon {
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ border-bottom-left-radius: 2px;
+ display: inline-block;
+ height: 24px;
+ width: 24px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 24px;
+
+}
+
+#serverSide {
+ float: right;
+ margin: 4px;
+}
+
+.infoBar {
+ min-height: 80px;
+}
+
+#taskTable_filter {
+ margin-top: 9px;
+}
+
+#loadTaskForm input {
+ width: 20em;
+}
+
+#workerList .box-tools > div {
+ display: inline-block;
+}
+
+#workerList .btn-set-workers > span.caret {
+ margin-left: 4px;
+}
+
+#workerList .box-tools > span.label-unread-worker-messages {
+ margin-right: 6px;
+ vertical-align: middle;
+ font-style: italic;
+ color: red;
+}
+
+#resourceList i.resources-collapse {
+ padding-left: 10px;
+}
+
+#clear-task-filter {
+ margin-left: 20px;
+ cursor: pointer;
+}
+
+#clear-task-filter:hover {
+ opacity: 0.9;
+}
+
+#clear-task-filter:active {
+ box-shadow: inset -2px 3px 1px rgba(0,0,0,0.2);
+}
+
+.sidebar-menu li > a.sidebar-folder {
+ font-weight: bold;
+ background-color: #ddd !important;
+}
+
+.sidebar-menu li > a.sidebar-folder:hover {
+ opacity: 0.9;
+}
+
+.sidebar-menu li > a.sidebar-folder.expanded {
+ background-color: rgb(0, 166, 90) !important;
+ color: white !important;
+}
+
+.popover{
+ max-width: 100% !important;
+}
+
+.modal-header .close {
+ margin-left: 10px !important;
+}
+
+.modal-dialog.expanded {
+ width: 90% !important;
+ max-width: 90% !important;
+ height: 90% !important;
+}
+
+.modal-dialog.expanded .modal-content {
+ height: 100% !important;
+}
\ No newline at end of file
diff --git a/lib/python3.11/site-packages/luigi/static/visualiser/index.html b/lib/python3.11/site-packages/luigi/static/visualiser/index.html
new file mode 100644
index 0000000000..31a155acf8
--- /dev/null
+++ b/lib/python3.11/site-packages/luigi/static/visualiser/index.html
@@ -0,0 +1,593 @@
+
+
+
+ Luigi Task Visualiser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pending Tasks
+ ?
+
+
+
+
+
+
+
+
+ Running Tasks
+ ?
+
+
+
+
+
+
+
+
+ Batch Running Tasks
+ ?
+
+
+
+
+
+
+
+
+
+
+ Failed Tasks
+ ?
+
+
+
+
+
+
+
+
+ Upstream Failure
+ ?
+
+
+
+
+
+
+
+
+ Disabled Tasks
+ ?
+
+
+
+
+
+
+
+
+ Upstream Disabled
+ ?
+
+
+
+
+
+
+
+
+
+
+ |
+ Name |
+ Details |
+ Priority |
+ Time |
+ Actions |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/python3.11/site-packages/luigi/static/visualiser/js/visualiserApp.js b/lib/python3.11/site-packages/luigi/static/visualiser/js/visualiserApp.js
new file mode 100644
index 0000000000..cc1e72d7b4
--- /dev/null
+++ b/lib/python3.11/site-packages/luigi/static/visualiser/js/visualiserApp.js
@@ -0,0 +1,1584 @@
+function visualiserApp(luigi) {
+ var templates = {};
+ var typingTimer = 0;
+ var dt; // DataTable instantiated in $(document).ready()
+ var missingCategories = {};
+ var currentFilter = {
+ taskFamily: "",
+ taskCategory: [],
+ tableFilter: ""
+ };
+ var taskIcons = {
+ PENDING: 'pause',
+ RUNNING: 'play',
+ BATCH_RUNNING: 'play',
+ DONE: 'check',
+ FAILED: 'times',
+ UPSTREAM_FAILED: 'warning',
+ DISABLED: 'minus-circle',
+ UPSTREAM_DISABLED: 'warning'
+ };
+ var VISTYPE_DEFAULT = 'svg';
+
+ /*
+ * Updates view of the Visualization type.
+ */
+ function updateVisType(newVisType) {
+ $('#toggleVisButtons label').removeClass('active');
+ var visTypeInput = $('#toggleVisButtons input[value="' + newVisType + '"]');
+ visTypeInput.parent().addClass('active');
+ visTypeInput.prop('checked', true);
+ }
+
+ function loadTemplates() {
+ $("script[type='text/template']").each(function(i, element) {
+ var name = $(element).attr("name");
+ var content = $(element).text();
+ templates[name] = content;
+ });
+ }
+
+ function renderTemplate(templateName, dataObject) {
+ return $("").html(Mustache.render(templates[templateName], dataObject));
+ }
+
+
+ function formatTime(dateObject) {
+ return dateObject.getHours() + ":" + dateObject.getMinutes() + ":" + dateObject.getSeconds();
+ }
+
+ function taskToDisplayTask(task) {
+ var taskName = task.name;
+ var taskParams = JSON.stringify(task.params);
+ var displayTime = new Date(Math.floor(task.last_updated*1000)).toLocaleString();
+ var time_running = -1;
+ if (task.status == "RUNNING" && "time_running" in task) {
+ var current_time = new Date().getTime();
+ var minutes_running = Math.round((current_time - task.time_running * 1000) / 1000 / 60);
+ time_running = task.time_running;
+ displayTime += " | " + minutes_running + " minutes";
+ }
+ return {
+ taskId: task.taskId,
+ encodedTaskId: encodeURIComponent(task.taskId),
+ taskName: taskName,
+ taskParams: taskParams,
+ displayName: task.display_name,
+ priority: task.priority,
+ resources: JSON.stringify(task.resources_running || task.resources).replace(/,"/g, ', "'),
+ displayTime: displayTime,
+ displayTimestamp: task.last_updated,
+ timeRunning: time_running,
+ trackingUrl: task.tracking_url,
+ status: task.status,
+ graph: (task.status == "PENDING" || task.status == "RUNNING" || task.status == "DONE"),
+ error: task.status == "FAILED",
+ re_enable: task.status == "DISABLED" && task.re_enable_able,
+ mark_as_done: (task.status == "RUNNING" || task.status == "FAILED" || task.status == "DISABLED"),
+ statusMessage: task.status_message,
+ progressPercentage: task.progress_percentage,
+ acceptsMessages: task.accepts_messages,
+ workerIdRunning: task.worker_running,
+ };
+ }
+
+ function taskCategoryIcon(category) {
+ var iconClass;
+ var iconColor;
+ switch (category) {
+ case 'PENDING':
+ iconClass = 'fa-pause';
+ iconColor = 'yellow';
+ break;
+ case 'RUNNING':
+ iconClass = 'fa-play';
+ iconColor = 'aqua';
+ break;
+ case 'BATCH_RUNNING':
+ iconClass = 'fa-play';
+ iconColor = 'purple';
+ break;
+ case 'DONE':
+ iconClass = 'fa-check';
+ iconColor = 'green';
+ break;
+ case 'FAILED':
+ iconClass = 'fa-times';
+ iconColor = 'red';
+ break;
+ case 'DISABLED':
+ iconClass = 'fa-minus-circle';
+ iconColor = 'gray';
+ break;
+ case 'UPSTREAM_FAILED':
+ iconClass = 'fa-warning';
+ iconColor = 'maroon';
+ break;
+ case 'UPSTREAM_DISABLED':
+ iconClass = 'fa-warning';
+ iconColor = 'gray';
+ break;
+ default:
+ iconClass = 'fa-bug';
+ iconColor = 'orange';
+ break;
+ }
+ return '
';
+ }
+
+ /**
+ * Filter table by all activated info boxes.
+ */
+ function filterByCategory(dt, activeBoxes) {
+ if (activeBoxes === undefined) {
+ activeBoxes = getActiveBoxes();
+ }
+ currentFilter.taskCategory = activeBoxes;
+ dt.column(0).search(categoryQuery(activeBoxes), regex=true).draw();
+ }
+
+ function categoryQuery(activeBoxes) {
+ // Searched content will be
.
+ return '\\b(' + activeBoxes.join('|') + ')\\b';
+ }
+
+ function getActiveBoxes() {
+ var infoBoxes = $('.info-box');
+
+ var activeBoxes = [];
+ infoBoxes.each(function (i) {
+ if (infoBoxes[i].dataset.on === 'yes') {
+ activeBoxes.push(infoBoxes[i].dataset.category);
+ }
+ });
+ return activeBoxes;
+ }
+
+ function filterByTaskFamily(taskFamily, dt) {
+ currentFilter.taskFamily = taskFamily;
+ if (taskFamily === "") {
+ dt.column(1).search('').draw();
+ }
+ else {
+ dt.column(1).search('^' + taskFamily + '$', regex = true).draw();
+ }
+ }
+
+ function toggleInfoBox(infoBox, activate) {
+ var infoBoxColor = infoBox.dataset.color;
+ var infoBoxIcon = $(infoBox).find('.info-box-icon');
+ var colorClass = 'bg-' + infoBoxColor;
+
+ if ((infoBox.dataset.on === undefined) || (infoBox.dataset.on === 'no') || activate) {
+ infoBox.dataset.on = 'yes';
+ infoBoxIcon.removeClass(colorClass);
+ $(infoBox).addClass(colorClass);
+ }
+ else {
+ infoBox.dataset.on = 'no';
+ $(infoBox).removeClass(colorClass);
+ infoBoxIcon.addClass(colorClass);
+ }
+ }
+
+ function renderSidebar(tasks) {
+ // tasks is a list of task names
+ var counts = {};
+ $.each(tasks, function(i) {
+ var name = tasks[i];
+ if (counts[name] === undefined) {
+ counts[name] = 0;
+ }
+ counts[name] += 1;
+ });
+ var taskList = [];
+ $.each(counts, function (name) {
+ var dotIndex = name.indexOf('.');
+ var prefix = 'Others';
+ if (dotIndex > 0) {
+ prefix = name.slice(0, dotIndex);
+ }
+ var prefixList = taskList.find(function (pref) {
+ return pref.name == prefix;
+ })
+ if (prefixList) {
+ prefixList.tasks.push({name: name, count: counts[name]});
+ } else {
+ prefixList = {
+ name: prefix,
+ tasks: [{name: name, count: counts[name]}]
+ }
+ taskList.push(prefixList);
+ }
+
+ });
+ taskList.sort(function(a,b){
+ if (a.name == 'Others') {
+ if (b.name == 'Others') {
+ return 0;
+ }
+ return 1;
+ } else if (b.name == 'Others') {
+ return -1;
+ }
+ return a.name.localeCompare(b.name);
+ });
+ taskList.forEach(function(p){
+ p.tasks.sort(function(a,b){
+ return a.name.localeCompare(b.name);
+ });
+ });
+ return renderTemplate("sidebarTemplate", {"tasks": taskList});
+ }
+
+ function selectSidebarItem(item) {
+ var sidebarItems = $('.sidebar').find('li');
+ sidebarItems.each(function (i) {
+ var item2 = sidebarItems[i];
+ if (item2.dataset.task === undefined) {
+ return;
+ }
+ if (item === item2) {
+ if ($(item2).hasClass('active')) {
+ // item is active, deselect
+ $(item2).removeClass('active');
+ $(item2).find('.badge').removeClass('bg-green');
+ }
+ else {
+ // select item
+ $(item2).addClass('active');
+ $(item2).find('.badge').addClass('bg-green');
+ }
+ }
+ else {
+ // clear any selection
+ $(item2).removeClass('active');
+ $(item2).find('.badge').removeClass('bg-green');
+ }
+ });
+ }
+
+ function renderWarnings() {
+ return renderTemplate(
+ "warningsTemplate",
+ {missingCategories: $.map(missingCategories, function (v, k) {return v;})}
+ );
+ }
+
+ function processWorker(worker) {
+ worker.encoded_first_task = encodeURIComponent(worker.first_task);
+ worker.tasks = worker.running.map(taskToDisplayTask);
+ worker.tasks.sort(function(task1, task2) { return task1.timeRunning - task2.timeRunning; });
+ worker.start_time = new Date(worker.started * 1000).toLocaleString();
+ worker.active = new Date(worker.last_active * 1000).toLocaleString();
+ worker.is_disabled = worker.state === 'disabled';
+ return worker;
+ }
+
+ function renderWorkers(workers) {
+ return renderTemplate("workerTemplate", {"workerList": workers.map(processWorker)});
+ }
+
+ function processResource(resource) {
+ resource.tasks = resource.running.map(taskToDisplayTask);
+ resource.percent_used = 100 * resource.num_used / resource.num_total;
+ if (resource.percent_used >= 100) {
+ resource.bar_type = 'danger';
+ resource.percent_used = 100;
+ } else if (resource.percent_used > 50) {
+ resource.bar_type = 'warning';
+ } else {
+ resource.bar_type = 'success';
+ }
+ return resource;
+ }
+
+ function renderResources(resources) {
+ return renderTemplate("resourceTemplate", {
+ "resources": resources.map(processResource).sort(function(r1, r2) {
+ if (r1.percent_used > r2.percent_used)
+ return -1;
+ else if (r1.percent_used < r2.percent_used)
+ return 1;
+ else if (r1.num_used > r2.num_used)
+ return -1;
+ else if (r1.num_used < r2.num_used)
+ return 1;
+ else if (r1.name < r2.name)
+ return -1;
+ else if (r1.name > r2.name)
+ return 1;
+ else
+ return 0;
+ })
+ });
+ }
+
+ function switchTab(tabId) {
+ $(".tabButton").parent().removeClass("active");
+ $(".tab-pane").removeClass("active");
+ $("#" + tabId).addClass("active");
+ $(".navbar-nav li").removeClass("active");
+ $(".js-nav-link[data-tab=" + tabId + "]").parent().addClass("active");
+ updateSidebar(tabId);
+ }
+
+ function showErrorTrace(data) {
+ data.error = decodeError(data.error);
+ if (data.taskParams) {
+ data.taskParams = Object.entries(data.taskParams).map(([k,v]) => `--${k.replace(/_/g, '-')} ${JSON.stringify(v)}`).join(" ");
+ }
+ $("#errorModal").empty().append(renderTemplate("errorTemplate", data));
+ $("#errorModal").modal({});
+ }
+
+ function minMaxWindow(button) {
+
+ const icon = $(button).find(".icon");
+
+ const isExpanded = icon.hasClass("fa-minus");
+
+ if (isExpanded) {
+ $(".modal-dialog").removeClass("expanded");
+ icon.removeClass("fa-minus").addClass("fa-plus");
+ } else {
+ $(".modal-dialog").addClass("expanded");
+ icon.removeClass("fa-plus").addClass("fa-minus");
+ }
+ }
+
+ function showStatusMessage(data) {
+ $("#statusMessageModal").empty().append(renderTemplate("statusMessageTemplate", data));
+ $("#statusMessageModal").modal({});
+ var refreshInterval = setInterval(function() {
+ if ($("#statusMessageModal").is(":hidden"))
+ clearInterval(refreshInterval);
+ else {
+ luigi.getTaskStatusMessage(data.taskId, function(data) {
+ if (data.statusMessage === null)
+ $("#statusMessageModal pre").hide();
+ else {
+ $("#statusMessageModal pre").html(data.statusMessage).show();
+ }
+ });
+ luigi.getTaskProgressPercentage(data.taskId, function(data) {
+ // show or hide the progress bar container in the message modal
+ $("#statusMessageModal .progress").toggle(data.progressPercentage !== null);
+
+ // adjust the status of both progress bars (message modal and worker list)
+ var value = data.progressPercentage || 0;
+ var progressBars = $('#statusMessageModal .progress-bar, ' +
+ '.worker-table tbody .taskProgressBar[data-task-id="' + data.taskId + '"]');
+ progressBars.attr('aria-valuenow', value)
+ .text(value + '%')
+ .css({'width': value + '%'});
+ });
+ }
+ },
+ 500
+ );
+ }
+
+ function showSchedulerMessageModal(data) {
+ var $modal = $("#schedulerMessageModal");
+
+ $modal.empty().append(renderTemplate("schedulerMessageTemplate", data));
+ var $input = $modal.find("#schedulerMessageInput");
+ var $send = $modal.find("#schedulerMessageButton");
+ var $awaitResponse = $modal.find("#schedulerMessageAwaitResponse");
+ var $responseContainer = $modal.find("#schedulerMessageResponse");
+ var $responseSpinner = $responseContainer.find("pre > i");
+ var $responseContent = $responseContainer.find("pre > div");
+
+ $input.on("keypress", function($event) {
+ if (event.keyCode == 13) {
+ $send.trigger("click");
+ $event.preventDefault();
+ }
+ });
+
+ $send.on("click", function($event) {
+ var content = $input.val();
+ var awaitResponse = $awaitResponse.prop("checked");
+ if (content && data.worker) {
+ if (awaitResponse) {
+ $responseContainer.show();
+ $responseSpinner.show();
+ $responseContent.empty();
+ luigi.sendSchedulerMessage(data.worker, data.taskId, content, function(messageId) {
+ var interval = window.setInterval(function() {
+ luigi.getSchedulerMessageResponse(data.taskId, messageId, function(response) {
+ if (response != null) {
+ clearInterval(interval);
+ $responseSpinner.hide();
+ $responseContent.html(response);
+ }
+ });
+ }, 1000);
+ });
+ $event.stopPropagation();
+ } else {
+ $responseContainer.hide();
+ luigi.sendSchedulerMessage(data.worker, data.taskId, content);
+ }
+ }
+ });
+
+ $modal.on("shown.bs.modal", function() {
+ $input.focus();
+ });
+
+ $modal.modal({});
+ }
+
+ function preProcessGraph(dependencyGraph) {
+ var extraNodes = [];
+ var seen = {};
+ $.each(dependencyGraph, function(i, node) {
+ seen[node.taskId] = true;
+ });
+ $.each(dependencyGraph, function(i, node) {
+ $.each(node.deps, function(j, dep) {
+ if (!seen[dep]) {
+ seen[dep] = true;
+ var paramsStrs = (/\((.*)\)/.exec(dep) || ['', ''])[1].split(', ');
+ var params = {};
+ $.each(paramsStrs, function(i, param) {
+ if (param !== "") {
+ var kv = param.split('=');
+ params[kv[0]] = kv[1];
+ }
+ });
+
+ extraNodes.push({
+ name: (/(\w+)\(/.exec(dep) || [])[1],
+ taskId: dep,
+ deps: [],
+ params: params,
+ status: "TRUNCATED"
+ });
+ }
+ });
+ });
+ return dependencyGraph.concat(extraNodes);
+ }
+
+ function makeGraphCallback(visType, taskId, paint) {
+ function depGraphCallbackD3(dependencyGraph) {
+ $("#searchError").empty();
+ $("#searchError").removeClass();
+ if(dependencyGraph.length > 0) {
+ $("#dependencyTitle").text(dependencyGraph[0].display_name);
+ if(dependencyGraph != '{}'){
+ for (var id in dependencyGraph) {
+ if (dependencyGraph[id].deps.length > 0) {
+ //console.log(asingInput(dependencyGraph, id));
+ dependencyGraph[id].inputQueue = asingInput(dependencyGraph, id);
+ dependencyGraph[id].inputThroughput = 50;
+ dependencyGraph[id].count = 5;
+ dependencyGraph[id].consumers = 1;
+ }else{
+ dependencyGraph[id].inputThroughput = 50;
+ dependencyGraph[id].count = 5;
+ dependencyGraph[id].consumers = 1;
+ }
+ }
+ }
+ } else {
+ $("#searchError").addClass("alert alert-error");
+ $("#searchError").text("Couldn't find task " + taskId);
+ }
+ drawGraphETL(dependencyGraph, paint);
+ bindGraphEvents();
+ }
+
+ function depGraphCallback (dependencyGraph) {
+ $("#graphPlaceholder svg").empty();
+ $("#searchError").empty();
+ $("#searchError").removeClass();
+ if(dependencyGraph.length > 0) {
+ $("#dependencyTitle").text(dependencyGraph[0].display_name);
+ var hashBaseObj = URI.parseQuery(location.hash.replace('#', ''));
+ delete hashBaseObj.taskId;
+ var hashBase = '#' + URI.buildQuery(hashBaseObj) + '&taskId=';
+ $("#graphPlaceholder").get(0).graph.updateData(dependencyGraph, hashBase);
+ $("#graphContainer").show();
+ bindGraphEvents();
+ } else {
+ $("#searchError").addClass("alert alert-error");
+ $("#searchError").text("Couldn't find task " + taskId);
+ }
+ }
+
+ function processedCallback(callback) {
+ function processed(dependencyGraph) {
+ return callback(preProcessGraph(dependencyGraph));
+ }
+ return processed;
+ }
+
+ if (visType == 'd3') {
+ return processedCallback(depGraphCallbackD3);
+ }
+ else {
+ return processedCallback(depGraphCallback);
+ }
+ }
+
+ function processHashChange(paint) {
+ var hash = decodeURIComponent(location.hash);
+ // Convert fragment params to object.
+ var fragmentQuery = URI.parseQuery(location.hash.replace('#', '')); // "http://example.org/#!/foo/bar/baz.html");
+
+ if (fragmentQuery.tab == "workers") {
+ switchTab("workerList");
+ } else if (fragmentQuery.tab == "resources") {
+ expandResources(fragmentQuery.resources);
+ switchTab("resourceList");
+ } else if (fragmentQuery.tab == "graph") {
+ var taskId = fragmentQuery.taskId;
+ var hideDone = fragmentQuery.hideDone === '1' ? true : false;
+
+ // Populate fields with values from hash.
+ $('#hideDoneCheckbox').prop('checked', hideDone);
+ $("#invertCheckbox").prop('checked', fragmentQuery.invert === '1' ? true : false);
+ $("#js-task-id").val(fragmentQuery.taskId);
+
+ // Empty errors.
+ $("#searchError").empty();
+ $("#searchError").removeClass();
+
+ var visType = fragmentQuery.visType || VISTYPE_DEFAULT;
+ if (taskId) {
+ var depGraphCallback = makeGraphCallback(visType, taskId, paint);
+
+ if (fragmentQuery.invert) {
+ luigi.getInverseDependencyGraph(taskId, depGraphCallback, !hideDone);
+ } else {
+ luigi.getDependencyGraph(taskId, depGraphCallback, !hideDone);
+ }
+ }
+ updateVisType(visType);
+ initVisualisation(visType);
+ switchTab("dependencyGraph");
+ } else {
+ // Tasks tab.
+
+ // Populate fields with values from hash.
+ if (fragmentQuery.length) {
+ $('select[name=taskTable_length]').val(fragmentQuery.length);
+ }
+ $("#serverSideCheckbox").prop('checked', fragmentQuery.filterOnServer === '1' ? true : false);
+ dt.search(fragmentQuery.search__search);
+
+ $('#familySidebar li').removeClass('active');
+ $('#familySidebar li .badge').removeClass('bg-green');
+ if (fragmentQuery.family) {
+ family_item = $('#familySidebar li[data-task="' + fragmentQuery.family + '"]');
+ family_item.addClass('active');
+ family_item.find('.badge').addClass('bg-green');
+ filterByTaskFamily(fragmentQuery.family, dt);
+ }
+
+ if (fragmentQuery.statuses) {
+ var statuses = JSON.parse(fragmentQuery.statuses);
+ $.each(statuses, function (status) {
+ toggleInfoBox($('#' + statuses[status] + '_info')[0], true);
+ });
+ filterByCategory(dt, statuses);
+ }
+
+ if (fragmentQuery.order) {
+ dt.order([fragmentQuery.order.split(',')]);
+ }
+ dt.draw();
+ switchTab("taskList");
+ }
+ }
+
+ function bindGraphEvents() {
+ var fragmentQuery = URI.parseQuery(location.hash.replace('#', ''));
+ var visType = fragmentQuery.visType;
+ if (visType === 'd3') {
+ $('.node').click(function(event) {
+ var taskDiv = $(this).find('.taskNode');
+ var taskId = taskDiv.attr("data-task-id");
+ event.preventDefault();
+ // NOTE : hasClass() not reliable inside SVG
+ if ($(this).attr('class').match(/\bFAILED\b/)) {
+ luigi.getErrorTrace(taskId, function (error) {
+ showErrorTrace(error);
+ });
+ }
+ else {
+ fragmentQuery['taskId'] = taskId;
+ window.location.href = 'index.html#' + URI.buildQuery(fragmentQuery);
+ }
+ });
+ }
+ else {
+ $(".graph-node-a").click(function(event) {
+ var taskId = $(this).attr("data-task-id");
+ var status = $(this).attr("data-task-status");
+ if (status == "FAILED") {
+ event.preventDefault();
+ luigi.getErrorTrace(taskId, function(error) {
+ showErrorTrace(error);
+ });
+ }
+ });
+ }
+ }
+
+ function bindListEvents() {
+ $(window).on('hashchange', processHashChange);
+
+ $('#serverSideCheckbox').click(function(e) {
+ e.preventDefault();
+ changeState('filterOnServer', this.checked ? '1' : null);
+ updateTasks();
+ });
+
+ $("#invertCheckbox").click(function(e) {
+ e.preventDefault();
+ changeState('invert', this.checked ? '1' : null);
+ });
+
+ $('#hideDoneCheckbox').click(function(e) {
+ // Copy checkbox value to hash.
+ e.preventDefault();
+ changeState('hideDone', this.checked ? '1' : null);
+ });
+ $("a[href=#list]").click(function() { location.hash=""; });
+ $("#loadTaskForm").submit(function(event) {
+ event.preventDefault();
+ var taskId = $(this).find("input").val();
+ changeState('taskId', taskId.length > 0 ? taskId : null);
+ });
+
+ $('.info-box').on('click', function () {
+ toggleInfoBox(this);
+ filterByCategory(dt);
+ });
+
+ $('input[name=vis-type]').on('change', function () {
+ changeState('visType', $(this).val());
+ });
+
+ /*
+ Note: The #filter-input element is used by LuigiAPI to constrain requests to the server.
+ When the accompanying button is pressed we force a reload.
+ */
+ $('#serverSide').on('change', 'label', function () {
+ updateTasks();
+ });
+ }
+
+ function asingInput(worker, id){
+ if (worker[id].deps.length > 0) {
+ //console.log(worker[id].deps);
+ return worker[id].deps;
+ }
+ }
+
+ function getDurations(tasks, listId){
+ var durations = {};
+ for (var i = 0; i < listId.length; i++) {
+ for (var j = 0; j < tasks.length; j++) {
+ if (listId[i] === tasks[j].taskId) {
+ // The duration of the task from when it started running to when it finished.
+ var finishTime = new Date(tasks[j].last_updated*1000);
+ var startTime = new Date(tasks[j].time_running*1000);
+ durations[listId[i]] = new Date(finishTime - startTime);
+ }
+ }
+ }
+ return durations;
+ }
+
+ function getParam(tasks, id){
+ for (var i = 0; i < tasks.length; i++) {
+ if (tasks[i].taskId === id) {
+ return tasks[i].worker_running;
+ }
+ }
+ }
+
+ function getStatusTasks(tasks){
+ var status;
+ for (var i = 0; i < tasks.length; i++) {
+ if (tasks[i].status === "DONE") {
+ status = true;
+ } else {
+ return false;
+ }
+ }
+ return status;
+ }
+
+ function drawGraphETL(tasks, paint){
+ // Set up zoom support
+ var svg = d3.select("#mysvg");
+ var inner = svg.select("g"),
+ zoom = d3.behavior.zoom().on("zoom", function() {
+ inner.attr("transform", "translate(" + d3.event.translate + ")" +
+ "scale(" + d3.event.scale + ")");
+ });
+ svg.call(zoom);
+
+ // Create map of taskId to task
+ var taskIdMap = {};
+ $.each(tasks, function (i, task) {
+ taskIdMap[task.taskId] = task;
+ });
+
+ var render = new dagreD3.render();
+ // Left-to-right layout
+ var g = new dagreD3.graphlib.Graph();
+ g.setGraph({
+ nodesep: 70,
+ ranksep: 50,
+ rankdir: "LR",
+ marginx: 20,
+ marginy: 20,
+ height: 400,
+ ranker: "longest-path"
+ });
+
+ function draw(isUpdate) {
+ for (var id in tasks) {
+ var task = tasks[id];
+ var className = task.status;
+
+ var html = "";
+ html += "";
+ html += ""+task.name+"";
+ html += ""+ task.status +"";
+ html += "
";
+ g.setNode(task.taskId, {
+ labelType: "html",
+ label: html,
+ rx: 5,
+ ry: 5,
+ padding: 0,
+ class: className
+ });
+ if (task.inputQueue) {
+ for (var i = 0; i < task.inputQueue.length; i++) {
+ // Destination node may not be in tasks if this is an inverted graph
+ if (taskIdMap[task.inputQueue[i]] !== undefined) {
+ if (task.status === "DONE") {
+ var durations = getDurations(tasks, task.inputQueue);
+ var duration = durations[task.inputQueue[i]];
+ var oneDayInMilliseconds = 24 * 60 * 60 * 1000;
+ var durationLabel;
+ if (duration.getTime() < oneDayInMilliseconds) {
+ // Label task duration in stripped ISO format (hh:mm:ss.f)
+ durationLabel = duration.toISOString().substr(11, 12);
+ } else {
+ durationLabel = "> 24h";
+ }
+ g.setEdge(task.inputQueue[i], task.taskId, {
+ label: durationLabel,
+ width: 40
+ });
+ } else {
+ g.setEdge(task.inputQueue[i], task.taskId, {
+ width: 40
+ });
+ }
+ }
+ }
+
+ }
+ }
+ var styleTooltip = function(name, description) {
+ return "" + name + "
" + description + "
";
+ };
+ inner.call(render, g);
+ if(paint){
+ // Zoom and scale to fit
+ var zoomScale = zoom.scale();
+ var graphWidth = g.graph().width + 80;
+ var graphHeight = g.graph().height + 40;
+ var width = parseInt(svg.style("width").replace(/px/, ""));
+ var height = parseInt(svg.style("height").replace(/px/, ""));
+ zoomScale = Math.min(width / graphWidth, height / graphHeight);
+ var translate = [(width/2) - ((graphWidth*zoomScale)/2), (height/2) - ((graphHeight*zoomScale)/2)];
+ zoom.translate(translate);
+ zoom.scale(zoomScale);
+ zoom.event(isUpdate ? svg.transition().duration(3000) : d3.select("#mysvg"));
+ }
+
+ inner.selectAll("g.node")
+ .attr("title", function(v) { return styleTooltip(v, getParam(tasks, v)); })
+ .each(function(v) { $(this).tipsy({ gravity: "w", opacity: 1, html: true }); });
+ }
+ draw();
+ }
+
+ /*
+ DataTables functions
+ */
+ // Remove tasks of a given category and add new ones.
+ function updateTaskCategory(dt, category, tasks) {
+ var taskMap = {};
+
+ var mostImportantCategory = function (cat1, cat2) {
+ var priorities = [
+ 'RUNNING',
+ 'BATCH_RUNNING',
+ 'DONE',
+ 'PENDING',
+ 'UPSTREAM_DISABLED',
+ 'UPSTREAM_FAILED',
+ 'DISABLED',
+ 'FAILED'
+ ];
+ // NOTE : -1 indicates not in list
+ var i1 = priorities.indexOf(cat1);
+ var i2 = priorities.indexOf(cat2);
+ var ret;
+ if (i1 > i2) {
+ ret = cat1;
+ }
+ else {
+ ret = cat2;
+ }
+ return ret;
+ };
+
+ dt.rows(function (i, data) {
+ taskMap[data.taskId] = data.category;
+ return data.category === category;
+ }).remove();
+
+ var taskCount;
+ /* Check for integers in tasks. This indicates max-shown-tasks was exceeded */
+ if (tasks.length === 1 && typeof(tasks[0]) === 'number') {
+ taskCount = tasks[0] === -1 ? 'unknown' : tasks[0];
+ missingCategories[category] = {name: category, count: taskCount};
+ }
+ else {
+ var displayTasks = tasks.map(taskToDisplayTask);
+ displayTasks = displayTasks.filter(function (obj) {
+ if (obj === null) {
+ return false;
+ }
+ if (category === mostImportantCategory(category, taskMap[obj.taskId])) {
+ obj.category = category;
+ return true;
+ }
+ return false;
+ });
+ dt.rows.add(displayTasks);
+ taskCount = displayTasks.length;
+ delete missingCategories[category];
+ }
+
+ $('#'+category+'_info').find('.info-box-number').html(taskCount);
+ $('#'+category+'_info i.fa').removeClass().addClass('fa fa-'+taskIcons[category]);
+ }
+
+ function updateCurrentFilter() {
+ var content;
+ currentFilter.tableFilter = dt.search();
+
+ if ((currentFilter.tableFilter === "") &&
+ ($.isEmptyObject(currentFilter.taskCategory)) &&
+ (currentFilter.taskFamily === "")) {
+
+ content = '';
+ }
+ else {
+ if (currentFilter.taskCategory !== "") {
+ currentFilter.catNames = $.map(currentFilter.taskCategory, function (x) {
+ return {name: x};
+ });
+ }
+
+ content = renderTemplate('currentFilterTemplate', currentFilter);
+ }
+
+ $('#currentFilter').html(content);
+ }
+
+ function initVisualisation(newVisType) {
+
+ // Prepare graphPlaceholder for D3 code
+ if (newVisType == 'd3') {
+ $('#graphPlaceholder').empty();
+ $('#graphPlaceholder').html('');
+ }
+ else {
+ $('#graphPlaceholder').empty();
+ var graph = new Graph.DependencyGraph($("#graphPlaceholder")[0]);
+ $("#graphPlaceholder")[0].graph = graph;
+ }
+ }
+
+ function updateTasks() {
+ $('.status-info .info-box-number').text('?');
+ $('.status-info i.fa').removeClass().addClass('fa fa-spinner fa-pulse');
+
+ var ajax1 = luigi.getRunningTaskList(function(runningTasks) {
+ updateTaskCategory(dt, 'RUNNING', runningTasks);
+ });
+
+ var ajax2 = luigi.getBatchRunningTaskList(function(batchRunningTasks) {
+ updateTaskCategory(dt, 'BATCH_RUNNING', batchRunningTasks);
+ });
+
+ var ajax3 = luigi.getFailedTaskList(function(failedTasks) {
+ updateTaskCategory(dt, 'FAILED', failedTasks);
+ });
+
+ var ajax4 = luigi.getUpstreamFailedTaskList(function(upstreamFailedTasks) {
+ updateTaskCategory(dt, 'UPSTREAM_FAILED', upstreamFailedTasks);
+ });
+
+ var ajax5 = luigi.getDisabledTaskList(function(disabledTasks) {
+ updateTaskCategory(dt, 'DISABLED', disabledTasks);
+ });
+
+ var ajax6 = luigi.getUpstreamDisabledTaskList(function(upstreamDisabledTasks) {
+ updateTaskCategory(dt, 'UPSTREAM_DISABLED', upstreamDisabledTasks);
+ });
+
+ var ajax7 = luigi.getPendingTaskList(function(pendingTasks) {
+ updateTaskCategory(dt, 'PENDING', pendingTasks);
+ });
+
+ var ajax8 = luigi.getDoneTaskList(function(doneTasks) {
+ updateTaskCategory(dt, 'DONE', doneTasks);
+ });
+
+ $.when(ajax1, ajax2, ajax3, ajax4, ajax5, ajax6, ajax7, ajax8).done(function () {
+ dt.draw();
+
+ $('.sidebar').html(renderSidebar(dt.column(1).data()));
+ var selectedFamily = $('.sidebar-menu').find('li[data-task="' + currentFilter.taskFamily + '"]')[0];
+ selectSidebarItem(selectedFamily);
+
+ if (selectedFamily) {
+ var selectedUl = $(selectedFamily).parent();
+ selectedUl.show();
+ selectedUl.prev().addClass('expanded');
+ } else {
+ var others = $('.sidebar-folder:contains(Others)')
+ others.addClass('expanded')
+ others.next().show()
+ }
+
+ $('.sidebar-menu').on('click', 'li:not(.sidebar-folder)', function (e) {
+ e.stopPropagation();
+ if (this.dataset.task) {
+ selectSidebarItem(this);
+ if ($(this).hasClass('active')) {
+ filterByTaskFamily(this.dataset.task, dt);
+ }
+ else {
+ filterByTaskFamily("", dt);
+ }
+ }
+ });
+
+ $('.sidebar-menu').on('click', '.sidebar-folder', function () {
+ const ul = this.nextElementSibling;
+ $(ul).slideToggle()
+ this.classList.toggle('expanded')
+ })
+
+ $('#clear-task-filter').on('click', function () {
+ filterByTaskFamily("", dt);
+ });
+
+ if ($.isEmptyObject(missingCategories)) {
+ $('#warnings').html('');
+ }
+ else {
+ $('#warnings').html(renderWarnings());
+ }
+
+ processHashChange();
+ });
+ }
+
+ function updateSidebar(tabName) {
+ if (tabName === 'taskList') {
+ $('body').removeClass('sidebar-collapse');
+ }
+ else {
+ $('body').addClass('sidebar-collapse');
+ }
+ }
+
+ // Error strings may or may not be JSON encoded, depending on client version
+ // Decoding an unencoded string may raise an exception.
+ function decodeError(error) {
+ var decoded;
+ try {
+ decoded = JSON.parse(error);
+ }
+ catch (e) {
+ decoded = error;
+ }
+ return decoded;
+ }
+
+ /**
+ * Return HTML of a task parameter dictionary
+ * @param params: task parameter dictionary
+ */
+ function renderParams(params) {
+ var htmls = [];
+ for (var key in params) {
+ htmls.push('' + escapeHtml(key) +
+ '=' + escapeHtml(params[key]) + '');
+ }
+ return htmls.join(', ');
+ }
+
+ /**
+ * Updates the number of worker processes of a worker
+ * @param worker: the id of the worker
+ * @param n: the number of processes to set
+ */
+ function updateWorkerProcesses(worker, n) {
+ n = Math.max(1, n);
+
+ // the spinner is just for visual feedback
+ var $label = $('#workerList').find('#label-n-workers[data-worker="' + worker + '"]');
+ $label.html('');
+
+ luigi.setWorkerProcesses(worker, n, function() {
+ $label.text(n);
+ });
+ }
+
+ /**
+ * Updates the number of units of a given resource available in the scheduler
+ * @param resource: the name of the resource
+ * @param n: the number of units to set the resource limit to
+ */
+ function updateResourceCount(resource, n) {
+ var progressBar = $('#' + resource + '-resource-box .progress-bar');
+ var used = /(\S+)\//.exec(progressBar.text())[1];
+ nVal = parseInt(n);
+ if (isNaN(nVal) || nVal < 0) {
+ return;
+ }
+ usedVal = parseInt(used);
+ width = Math.floor(100 * usedVal / nVal);
+ if (width < 0) {
+ width = 0;
+ }
+ if (width > 100) {
+ width = 100;
+ }
+ luigi.updateResource(resource, n, function() {
+ progressBar.text(usedVal + '/' + nVal);
+ progressBar.attr('style', 'width: ' + width + '%');
+ });
+ }
+
+ /**
+ * Returns the current units of a resource used
+ * @param resource: the name of the resource
+ */
+ function currentResourceCount(resource) {
+ var progressBar = $('#' + resource + '-resource-box .progress-bar');
+ var count = /\/(\S+)/.exec(progressBar.text())[1];
+ return parseInt(count);
+ }
+
+ function changeState(key, value) {
+ var fragmentQuery = URI.parseQuery(location.hash.replace('#', ''));
+ if (value) {
+ fragmentQuery[key] = value;
+ } else {
+ delete fragmentQuery[key];
+ }
+ location.hash = '#' + URI.buildQuery(fragmentQuery);
+ }
+
+ function expandedResources() {
+ return $('.resource-box.in').toArray().map(function (val) { return val.dataset.resource; });
+ }
+
+ function expandResources(resources) {
+ if (resources === undefined) {
+ resources = [];
+ } else {
+ resources = JSON.parse(resources);
+ }
+ $('.resource-box').each(function (i, item) {
+ if (resources.indexOf(item.dataset.resource) === -1) {
+ $(item).collapse('hide');
+ } else {
+ $(item).collapse('show');
+ }
+ });
+ }
+
+ /**
+ * Create the pause/unpause toggle
+ */
+ function createPauseToggle(checked) {
+ var check = checked ? " checked" : "";
+ var html = $('');
+ $('#pause-form').append(html);
+ $('#pause').bootstrapToggle({
+ on: 'Running',
+ off: 'Paused',
+ onstyle: 'success',
+ offstyle: 'danger'
+ });
+ $('#pause').change(function() {
+ if (this.checked) {
+ luigi.unpause();
+ } else {
+ luigi.pause();
+ }
+ })
+ }
+
+ $(document).ready(function() {
+ loadTemplates();
+
+ luigi.hasTaskHistory(function(hasTaskHistory) {
+ if (hasTaskHistory) {
+ $('#topNavbar').append(renderTemplate('topNavbarItem', {
+ label: "History",
+ href: "../../history",
+ }).children()[0]);
+ }
+ });
+
+ luigi.isPauseEnabled(function(enabled) {
+ if (enabled) {
+ luigi.isPaused(createPauseToggle);
+ }
+ });
+
+ luigi.getWorkerList(function(workers) {
+ $("#workerList").append(renderWorkers(workers));
+
+ $('.worker-table tbody').on('click', 'td .statusMessage', function() {
+ var data = $(this).data();
+ showStatusMessage(data);
+ });
+
+ $('.worker-table tbody').on('click', 'td .schedulerMessage', function() {
+ var data = $(this).data();
+ showSchedulerMessageModal(data);
+ });
+ });
+
+ luigi.getResourceList(function(resources) {
+ $("#resourceList").append(renderResources(resources));
+ expandResources(URI.parseQuery(location.hash.replace('#', '')).resources);
+ $('.resources-collapse').click(function (e) {
+ e.preventDefault();
+ var collapse_block = $(this.dataset.target);
+ if (collapse_block.hasClass('collapsing')) {
+ return;
+ }
+ var resource = collapse_block.attr('data-resource');
+ var resourceList = expandedResources();
+ var resourceIdx = resourceList.indexOf(resource);
+ if (resourceIdx === -1) {
+ resourceList.push(resource);
+ } else {
+ resourceList.splice(resourceIdx, 1);
+ }
+ changeState('resources', resourceList.length > 0 ? JSON.stringify(resourceList) : null);
+ collapse_block.collapse('toggle');
+ });
+ });
+
+ dt = $('#taskTable').DataTable({
+ stateSave: true,
+ stateSaveCallback: function(settings, data) {
+ // Save data table state to browser's hash.
+ var state = URI.parseQuery(location.hash.replace('#', ''));
+
+ if (data.search.search) {
+ state.search__search = data.search.search;
+ } else {
+ delete state.search__search;
+ }
+
+ var family_search = data.columns[1].search.search;
+ if (family_search) {
+ state.family = family_search.substring(1, family_search.length - 1);
+ } else {
+ delete state.family;
+ }
+
+ if (currentFilter.taskCategory.length > 0) {
+ state.statuses = JSON.stringify(currentFilter.taskCategory);
+ } else {
+ delete state.statuses;
+ }
+
+ if (data.order && data.order.length) {
+ state.order = '' + data.order[0][0] + ',' + data.order[0][1];
+ }
+
+ if (data.length && data.length !== 10) {
+ // Keep in hash only if length is not default.
+ state.length = data.length;
+ } else {
+ delete state.length;
+ }
+
+ if (state.filterOnServer) {
+ state.filterOnServer = '1';
+ }
+ location.hash = '#' + URI.buildQuery(state);
+ },
+ stateLoadCallback: function(settings) {
+ // Restore datatable state from browser's hash.
+ var fragmentQuery = URI.parseQuery(location.hash.replace('#', ''));
+
+ var order = [];
+ if (fragmentQuery.order) {
+ order = [fragmentQuery.order.split(',')];
+ }
+
+ var family_search = {};
+ if (fragmentQuery.family) {
+ family_search = {'search': '^' + fragmentQuery.family + '$', 'regex': true};
+ }
+
+ var status_search = {};
+ if (fragmentQuery.statuses) {
+ var statuses = JSON.parse(fragmentQuery.statuses);
+ currentFilter.taskCategory = statuses;
+ status_search = {'search': categoryQuery(statuses), 'regex': true};
+ }
+
+ // Prepare state for datatable.
+ var o = {
+ order: order, // Table rows order.
+ length: fragmentQuery.length, // Entries on page.
+ start: 0, // Pagination initial page.
+ time: new Date().getTime(), // Current time to help datatable.js to handle asynchronous.
+ columns: [
+ {visible: true, search: status_search},
+ {visible: true, search: family_search}, // Name column
+ {visible: true, search: {}}, // Details column
+ {visible: true, search: {}}, // Priority column
+ {visible: true, search: {}}, // Time column
+ {visible: true, search: {}} // Actions column
+ ],
+ // Search input state.
+ search: {
+ caseInsensitive: true,
+ search: fragmentQuery.search__search
+ }
+ };
+
+ return o;
+ },
+ dom: 'l<"#serverSide">frtip',
+ language: {
+ search: 'Filter table:'
+ },
+ columns: [
+ {
+ data: 'category',
+ render: function (data, type, row) {
+ return taskCategoryIcon(data) + ' ' + data;
+ }
+ },
+ {data: 'taskName'},
+ {
+ data: 'taskParams',
+ render: function(data, type, row) {
+ var params = JSON.parse(data);
+ if (row.resources !== '{}') {
+ return '' + renderParams(params) + '
' + row.resources + '
';
+ } else {
+ return '' + renderParams(params) + '
';
+ }
+ }
+ },
+ {data: 'priority', width: "2em"},
+ {data: 'displayTime'},
+ {
+ className: 'details-control',
+ orderable: false,
+ data: null,
+ render: function (data, type, row) {
+ return Mustache.render(templates.actionsTemplate, row);
+ }
+ }
+ ]
+ });
+
+ dt.on('draw', updateCurrentFilter);
+
+ $('#serverSide').html('');
+
+ // If using server-side filter we need to updateTasks every time the filter changes
+
+ $('#taskTable_filter').on('keyup paste', 'input', function () {
+ if ($('#serverSideCheckbox')[0].checked) {
+ clearTimeout(typingTimer);
+ if ($(this).val) {
+ typingTimer = setTimeout(updateTasks, 400);
+ }
+ }
+ });
+
+ processHashChange();
+ updateTasks();
+ bindListEvents();
+
+ $('#taskTable tbody').on('click', 'td.details-control .showError', function () {
+ var tr = $(this).closest('tr');
+ var row = dt.row( tr );
+ var data = row.data();
+ luigi.getErrorTrace(data.taskId, function(error) {
+ showErrorTrace(error);
+ });
+ } );
+
+ $('#errorModal').on('click', '#expandModal', function () {
+ minMaxWindow(this);
+ });
+
+ $('#taskTable tbody').on('click', 'td.details-control .forgiveFailures', function (ev) {
+ var that = $(this);
+ var tr = that.closest('tr');
+ var row = dt.row( tr );
+ var data = row.data();
+ luigi.forgiveFailures(data.taskId, function(data) {
+ if (ev.altKey) {
+ updateTasks(); // update may not be cheap
+ } else {
+ that.tooltip('hide');
+ that.remove();
+ }
+ });
+ } );
+
+ $('#taskTable tbody').on('click', 'td.details-control .markAsDone', function (ev) {
+ var that = $(this);
+ var tr = that.closest('tr');
+ var row = dt.row( tr );
+ var data = row.data();
+ luigi.markAsDone(data.taskId, function(data) {
+ if (ev.altKey) {
+ updateTasks(); // update may not be cheap
+ } else {
+ that.tooltip('hide');
+ that.remove();
+ }
+ });
+ } );
+
+ $('#taskTable tbody').on('click', 'td.details-control .re-enable-button', function (ev) {
+ var that = $(this);
+ luigi.reEnable(that.attr("data-task-id"), function(data) {
+ if (ev.altKey) {
+ updateTasks(); // update may not be cheap
+ } else {
+ that.tooltip('hide');
+ that.remove();
+ }
+ });
+ });
+
+ $('#taskTable tbody').on('click', 'td.details-control .statusMessage', function () {
+ var data = $(this).data();
+ showStatusMessage(data);
+ });
+
+ $('#taskTable tbody').on('click', 'td.details-control .schedulerMessage', function () {
+ var data = $(this).data();
+ showSchedulerMessageModal(data);
+ });
+
+ $('.navbar-nav').on('click', 'a', function () {
+ var tabName = $(this).data('tab');
+ updateSidebar(tabName);
+ });
+
+ $('#workerList').on('show.bs.modal', '#disableWorkerModal', function (event) {
+ var triggerButton = $(event.relatedTarget);
+ $('#disableWorkerButton').data('trigger', triggerButton);
+ });
+
+ $('#workerList').on('click', '#disableWorkerButton', function() {
+ var triggerButton = $(this).data('trigger');
+ var worker = triggerButton.data('worker');
+
+ luigi.disableWorker(worker);
+
+ // show the worker as disabled in the visualiser
+ var box = triggerButton.parents('.box').addClass('box-solid box-default');
+
+ // remove the worker tools
+ box.find('.box-tools').remove();
+ });
+
+ $('#workerList').on('click', '#btn-increment-workers', function($event) {
+ var worker = $(this).data("worker");
+ var $label = $('#workerList').find('#label-n-workers[data-worker="' + worker + '"]');
+ var n = parseInt($label.text());
+ if (!isNaN(n)) {
+ updateWorkerProcesses(worker, n + 1);
+ }
+ $event.preventDefault();
+ });
+
+ $('#workerList').on('click', '#btn-decrement-workers', function($event) {
+ var worker = $(this).data("worker");
+ var $label = $('#workerList').find('#label-n-workers[data-worker="' + worker + '"]');
+ var n = parseInt($label.text());
+ if (!isNaN(n)) {
+ updateWorkerProcesses(worker, n - 1);
+ }
+ $event.preventDefault();
+ });
+
+ $('#workerList').on('show.bs.modal', '#setWorkersModal', function($event) {
+ $('#setWorkersButton').data('worker', $($event.relatedTarget).data('worker'));
+ var $input = $(this).find('#setWorkersInput').on('keypress', function($event) {
+ if (event.keyCode == 13) {
+ $('#workerList').find('#setWorkersButton').trigger('click');
+ }
+ $event.stopPropagation();
+ });
+ setTimeout(function() {
+ $input.focus();
+ }.bind(this), 600);
+ });
+
+ $('#workerList').on('hidden.bs.modal', '#setWorkersModal', function() {
+ $(this).find('#setWorkersInput').off('keypress').val('');
+ });
+
+ $('#workerList').on('click', '#setWorkersButton', function($event) {
+ var worker = $(this).data('worker');
+ var n = parseInt($("#setWorkersInput").val());
+ if (!isNaN(n)) {
+ updateWorkerProcesses(worker, n);
+ }
+ $event.preventDefault();
+ });
+
+ $('#resourceList').on('click', '.btn-increment-resources', function($event) {
+ $event.preventDefault();
+ var resource = $(this).data('resource');
+ var count = currentResourceCount(resource);
+ updateResourceCount(resource, count + 1);
+ });
+
+ $('#resourceList').on('click', '.btn-decrement-resources', function($event) {
+ $event.preventDefault();
+ var resource = $(this).data('resource');
+ var count = currentResourceCount(resource);
+ updateResourceCount(resource, count - 1);
+ });
+
+ $('#resourceList').on('show.bs.modal', '#setResourcesModal', function($event) {
+ $('#setResourcesButton').data('resource', $($event.relatedTarget).data('resource'));
+ var $input = $(this).find('#setResourcesInput').on('keypress', function($event) {
+ if (event.keyCode == 13) {
+ $('#resourceList').find('#setResourcesButton').trigger('click');
+ }
+ $event.stopPropagation();
+ });
+ setTimeout(function() {
+ $input.focus();
+ }.bind(this), 600);
+ });
+
+ $('#resourceList').on('hidden.bs.modal', '#setResourcesModal', function() {
+ $(this).find('#setResourcesInput').off('keypress').val('');
+ });
+
+ $('#resourceList').on('click', '#setResourcesButton', function($event) {
+ var resource = $(this).data('resource');
+ var n = parseInt($("#setResourcesInput").val());
+ updateResourceCount(resource, n);
+ $event.preventDefault();
+ });
+ $('.js-nav-link').click(function(e) {
+ // User followed tab from navigation link. Copy state from fields to hash.
+ e.preventDefault();
+ var state = {};
+ var tabId = $(this).attr('data-tab');
+
+ if (tabId == 'taskList') {
+ var order = dt.order();
+ var search = dt.search();
+ state.tab = 'tasks';
+
+ if ($('select[name=taskTable_length]').val() !== '10') {
+ // Add length to hash only if the value is not default.
+ state.length = $('select[name=taskTable_length]').val();
+ }
+
+ if ($('#serverSideCheckbox').is(':checked')) {
+ state.filterOnServer = '1';
+ }
+
+ var family = $('#familySidebar li.active').attr('data-task');
+ if (family) {
+ state.family = family;
+ } else {
+ delete state.family;
+ }
+
+ if (currentFilter.taskCategory.length > 0) {
+ state.statuses = JSON.stringify(currentFilter.taskCategory);
+ } else {
+ delete state.statuses;
+ }
+
+ if (search) {
+ state.search__search = search;
+ }
+
+ if (order.length > 0) {
+ state.order = '' + order[0][0] + ',' + order[0][1];
+ }
+
+ } else if (tabId == 'dependencyGraph') {
+ state.tab = 'graph';
+
+ // Get state from fields.
+
+ if ($('#hideDoneCheckbox').is(':checked')) {
+ state.hideDone = '1';
+ }
+
+ if ($('#idTaskForm input.search-query').val()) {
+ state.taskId = $('#idTaskForm input.search-query').val();
+ }
+
+ if ($('#invertCheckbox').is(':checked')) {
+ state.invert = '1';
+ }
+
+ state.visType = $('input[name=vis-type]:checked').val();
+ } else if (tabId == 'workerList') {
+ state.tab = 'workers';
+ } else if (tabId == 'resourceList') {
+ state.resources = JSON.stringify(expandedResources());
+ state.tab = 'resources';
+ }
+
+ location.hash = '#' + URI.buildQuery(state);
+ });
+
+ processHashChange();
+ });
+}