blob: eca6da59b61cf9cf0eb7995e0d9855b01f5910a6 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author: Brett Slatkin (bslatkin@google.com)
*/
// Global variables.
var AUTO_REFRESH = true;
var ROOT_PIPELINE_ID = null;
var STATUS_MAP = null;
var LANG = null;
// Adjusts the height/width of the embedded status console iframe.
function adjustStatusConsole() {
var statusConsole = $('#status-console');
var detail = $('#detail');
var sidebar = $('#sidebar');
var control = $('#control');
// NOTE: 16 px here is the height of the resize grip in most browsers.
// Need to specify this explicitly because some browsers (eg, Firefox)
// cause the overflow scrollbars to bounce around the page randomly when
// they accidentally overlap the resize grip.
if (statusConsole.css('display') == 'none') {
var paddingAndMargin = detail.outerHeight() - detail.height() + 16;
detail.css('max-height', (sidebar.outerHeight() - paddingAndMargin) + 'px');
} else {
detail.css('max-height', '200px');
statusConsole.width(
$(window).width() - sidebar.outerWidth());
statusConsole.height(
$(window).height() - (statusConsole.offset().top + 16));
}
}
// Gets the ID of the pipeline info in the left-nav.
function getTreePipelineElementId(value) {
if (value.indexOf('#item-pipeline-') == 0) {
return value;
} else {
return '#item-pipeline-' + value;
}
}
// Scrolls to element of the pipeline in the tree.
function scrollTreeToPipeline(pipelineIdOrElement) {
var element = pipelineIdOrElement;
if (!(pipelineIdOrElement instanceof jQuery)) {
element = $(getTreePipelineElementId(pipelineIdOrElement));
}
$('#sidebar').scrollTop(element.attr('offsetTop'));
$('#sidebar').scrollLeft(element.attr('offsetLeft'));
}
// Opens all pipelines down to the target one if not already expanded and
// scroll that pipeline into view.
function expandTreeToPipeline(pipelineId) {
if (pipelineId == null) {
return;
}
var elementId = getTreePipelineElementId(pipelineId);
var parents = $(elementId).parents('.expandable');
if (parents.size() > 0) {
// The toggle function will scroll to highlight the pipeline.
parents.children('.hitarea').click();
} else {
// No children, so just scroll.
scrollTreeToPipeline(pipelineId);
}
}
// Handles when the user toggles a leaf of the tree.
function handleTreeToggle(index, element) {
var parentItem = $(element).parent();
var collapsing = parentItem.hasClass('expandable');
if (collapsing) {
} else {
// When expanded be sure the pipeline and its children are showing.
scrollTreeToPipeline(parentItem);
}
}
// Counts the number of total and active children for the given pipeline.
// Will include the supplied pipeline in the totals.
function countChildren(pipelineId) {
var current = STATUS_MAP.pipelines[pipelineId];
if (!current) {
return [0, 0];
}
var total = 1;
var done = 0;
if (current.status == 'done') {
done += 1;
}
for (var i = 0, n = current.children.length; i < n; i++) {
var parts = countChildren(current.children[i]);
total += parts[0];
done += parts[1];
}
return [total, done];
}
// Create the readable name for the pipeline name.
function prettyName(name, sidebar) {
var adjustedName = name;
if (sidebar) {
var adjustedName = name;
var parts = name.split('.');
if (parts.length > 0) {
adjustedName = parts[parts.length - 1];
}
}
return adjustedName.replace(/\./, '.<wbr>');
}
// Constructs the info div for a stage.
function constructStageNode(pipelineId, infoMap, sidebar) {
if (!infoMap) {
return;
}
var containerDiv = $('<div class="status-box">');
containerDiv.addClass('status-' + infoMap.status);
var detailDiv = $('<div class="detail-link">');
if (sidebar) {
detailDiv.append($('<div class="selected-indicator">').html('&#x2023;'));
}
var detailLink = $('<a>');
detailLink.attr('href', '#pipeline-' + pipelineId);
detailLink.attr('title', 'ID #' + pipelineId);
detailLink.attr('id', 'link-pipeline-' + pipelineId);
detailLink.html(prettyName(infoMap.classPath, sidebar));
detailDiv.append(detailLink);
containerDiv.append(detailDiv);
// ID of the pipeline
if (!sidebar) {
var pipelineIdDiv = $('<div class="status-pipeline-id">');
pipelineIdDiv.text('ID #' + pipelineId);
containerDiv.append(pipelineIdDiv);
}
// Broad status category.
var statusTitleDiv = $('<div class="status-title">');
if (!sidebar) {
statusTitleDiv.append($('<span>').text('Status: '));
}
statusTitleDiv.append($('<span>').text(infoMap.status));
containerDiv.append(statusTitleDiv);
// Determine timing information based on state.
var statusTimeLabel = null;
var statusTimeMs = null;
var statusRuntimeDiv = null;
if (infoMap.status == 'done') {
statusRuntimeDiv = $('<div class="status-runtime">');
var statusTimeSpan = $('<span class="status-time-label">');
statusTimeSpan.text('Run time: ');
statusRuntimeDiv.append(statusTimeSpan);
var runtimeSpan = $('<span class="status-runtime-value">');
runtimeSpan.text(getElapsedTimeString(
infoMap.startTimeMs, infoMap.endTimeMs));
statusRuntimeDiv.append(runtimeSpan);
statusTimeLabel = 'Complete';
statusTimeMs = infoMap.endTimeMs;
} else if (infoMap.status == 'run') {
statusTimeLabel = 'Started';
statusTimeMs = infoMap.startTimeMs;
} else if (infoMap.status == 'retry') {
statusTimeLabel = 'Will run';
statusTimeMs = infoMap.startTimeMs;
} else if (infoMap.status == 'finalizing') {
statusTimeLabel = 'Complete';
statusTimeMs = infoMap.endTimeMs;
} else if (infoMap.status == 'aborted' ||
infoMap.status == 'canceled') {
statusTimeLabel = 'Aborted';
statusTimeMs = infoMap.endTimeMs;
} else if (infoMap.status == 'waiting') {
// Do nothing.
}
// Last abort message, if any.
if (infoMap.abortMessage) {
var abortMessageDiv = $('<div class="status-message abort">');
abortMessageDiv.append($('<span>').text('Abort Message: '));
abortMessageDiv.append(
$('<span class="status-message-text">').text(infoMap.abortMessage));
containerDiv.append(abortMessageDiv);
}
// Last error message that caused a retry, if any.
if (infoMap.lastRetryMessage) {
var errorMessageDiv = $('<div class="status-message error">');
errorMessageDiv.append($('<span>').text('Retry Message: '));
errorMessageDiv.append(
$('<span class="status-message-text">').text(infoMap.lastRetryMessage));
containerDiv.append(errorMessageDiv);
}
// User-supplied status message.
if (infoMap.statusMessage) {
var statusMessageDiv = $('<div class="status-message normal">');
statusMessageDiv.append($('<span>').text('Message: '));
statusMessageDiv.append(
$('<span class="status-message-text">').text(infoMap.statusMessage));
containerDiv.append(statusMessageDiv);
}
// Completed children count.
if (infoMap.status == 'run' || infoMap.status == 'done') {
var counts = countChildren(pipelineId);
var totalChildren = counts[0];
var doneChildren = counts[1];
// Do not count ourselves
totalChildren--;
if (infoMap.status == 'done') {
doneChildren--;
}
if (totalChildren > 0 && doneChildren < totalChildren) {
var doneChildrenDiv = $('<div class="active-children">');
doneChildrenDiv.append($('<span>').text('Children: '));
var countText = '' + doneChildren + ' / ' + totalChildren + ' done';
doneChildrenDiv.append($('<span>').text(countText));
containerDiv.append(doneChildrenDiv);
}
}
// Number of attempts, if more than one.
if (infoMap.currentAttempt > 1) {
var attemptDiv = $('<div class="status-attempt">');
var attemptTitle = 'Attempt: ';
if (infoMap.status == 'retry') {
attemptTitle = 'Next Attempt: ';
} else if (infoMap.status == 'done') {
attemptTitle = 'Attempts: ';
}
attemptDiv.append($('<span>').text(attemptTitle));
var attemptText = '' + infoMap.currentAttempt + ' / ' +
infoMap.maxAttempts + '';
attemptDiv.append($('<span>').text(attemptText));
containerDiv.append(attemptDiv);
}
// Runtime if present.
if (statusRuntimeDiv) {
containerDiv.append(statusRuntimeDiv);
}
// Next retry time, complete time, start time.
if (statusTimeLabel && statusTimeMs) {
var statusTimeDiv = $('<div class="status-time">');
var statusTimeSpan = $('<span class="status-time-label">');
statusTimeSpan.text(statusTimeLabel + ': ');
statusTimeDiv.append(statusTimeSpan);
var sinceSpan = $('<abbr class="timeago status-time-since">');
var isoDate = getIso8601String(statusTimeMs);
sinceSpan.attr('title', isoDate);
sinceSpan.text(isoDate);
sinceSpan.timeago();
statusTimeDiv.append(sinceSpan);
containerDiv.append(statusTimeDiv);
}
// User-supplied status links.
var linksDiv = $('<div class="status-links">');
if (!sidebar) {
linksDiv.append($('<span>').text('Links: '));
}
var foundLinks = 0;
if (infoMap.statusConsoleUrl) {
var link = $('<a class="status-console">');
link.attr('href', infoMap.statusConsoleUrl);
link.text('Console');
link.click(function(event) {
selectPipeline(pipelineId);
event.preventDefault();
});
linksDiv.append(link);
foundLinks++;
}
if (infoMap.statusLinks) {
$.each(infoMap.statusLinks, function(key, value) {
var link = $('<a>');
link.attr('href', value);
link.text(key);
link.click(function(event) {
selectPipeline(pipelineId, key);
event.preventDefault();
});
linksDiv.append(link);
foundLinks++;
});
}
if (foundLinks > 0) {
containerDiv.append(linksDiv);
}
// Retry parameters.
if (!sidebar) {
var retryParamsDiv = $('<div class="status-retry-params">');
retryParamsDiv.append(
$('<div class="retry-params-title">').text('Retry parameters'));
var backoffSecondsDiv = $('<div class="retry-param">');
$('<span>').text('Backoff seconds: ').appendTo(backoffSecondsDiv);
$('<span>')
.text(infoMap.backoffSeconds)
.appendTo(backoffSecondsDiv);
retryParamsDiv.append(backoffSecondsDiv);
var backoffFactorDiv = $('<div class="retry-param">');
$('<span>').text('Backoff factor: ').appendTo(backoffFactorDiv);
$('<span>')
.text(infoMap.backoffFactor)
.appendTo(backoffFactorDiv);
retryParamsDiv.append(backoffFactorDiv);
containerDiv.append(retryParamsDiv);
}
function renderCollapsableValue(value, container) {
var stringValue = $.toJSON(value);
var SPLIT_LENGTH = 200;
if (stringValue.length < SPLIT_LENGTH) {
container.append($('<span>').text(stringValue));
return;
}
var startValue = stringValue.substr(0, SPLIT_LENGTH);
var endValue = stringValue.substr(SPLIT_LENGTH);
// Split the end value with <wbr> tags so it looks nice; force
// word wrapping never works right.
var moreSpan = $('<span class="value-disclosure-more">');
for (var i = 0; i < endValue.length; i += SPLIT_LENGTH) {
moreSpan.append(endValue.substr(i, SPLIT_LENGTH));
moreSpan.append('<wbr/>');
}
var betweenMoreText = '...(' + endValue.length + ' more) ';
var betweenSpan = $('<span class="value-disclosure-between">')
.text(betweenMoreText);
var toggle = $('<a class="value-disclosure-toggle">')
.text('Expand')
.attr('href', '');
toggle.click(function(e) {
e.preventDefault();
if (moreSpan.css('display') == 'none') {
betweenSpan.text(' ');
toggle.text('Collapse');
} else {
betweenSpan.text(betweenMoreText);
toggle.text('Expand');
}
moreSpan.toggle();
});
container.append($('<span>').text(startValue));
container.append(moreSpan);
container.append(betweenSpan);
container.append(toggle);
}
// Slot rendering
function renderSlot(slotKey) {
var filledMessage = null;
var slot = STATUS_MAP.slots[slotKey];
var slotDetailDiv = $('<div class="slot-detail">');
if (!slot) {
var keyAbbr = $('<abbr>');
keyAbbr.attr('title', slotKey);
keyAbbr.text('Pending slot');
slotDetailDiv.append(keyAbbr);
return slotDetailDiv;
}
if (slot.status == 'filled') {
var valueDiv = $('<span class="slot-value-container">');
valueDiv.append($('<span>').text('Value: '));
var valueContainer = $('<span class="slot-value">');
renderCollapsableValue(slot.value, valueContainer);
valueDiv.append(valueContainer);
slotDetailDiv.append(valueDiv);
var filledDiv = $('<div class="slot-filled">');
filledDiv.append($('<span>').text('Filled: '));
var isoDate = getIso8601String(slot.fillTimeMs);
filledDiv.append(
$('<abbr class="timeago">')
.attr('title', isoDate)
.text(isoDate)
.timeago());
slotDetailDiv.append(filledDiv);
filledMessage = 'Filled by';
} else {
filledMessage = 'Waiting for';
}
var filledMessageDiv = $('<div class="slot-message">');
filledMessageDiv.append(
$('<span>').text(filledMessage + ': '));
var otherPipeline = STATUS_MAP.pipelines[slot.fillerPipelineId];
if (otherPipeline) {
var fillerLink = $('<a class="slot-filler">');
fillerLink
.attr('title', 'ID #' + slot.fillerPipelineId)
.attr('href', '#pipeline-' + slot.fillerPipelineId)
.text(otherPipeline.classPath);
fillerLink.click(function(event) {
selectPipeline(slot.fillerPipelineId);
event.preventDefault();
});
filledMessageDiv.append(fillerLink);
} else {
filledMessageDiv.append(
$('<span class="status-pipeline-id">')
.text('ID #' + slot.fillerPipelineId));
}
slotDetailDiv.append(filledMessageDiv);
return slotDetailDiv;
}
// Argument/ouptut rendering
function renderParam(key, valueDict) {
var paramDiv = $('<div class="status-param">');
var nameDiv = $('<span class="status-param-name">');
nameDiv.text(key + ':');
paramDiv.append(nameDiv);
if (valueDict.type == 'slot' && STATUS_MAP.slots) {
paramDiv.append(renderSlot(valueDict.slotKey));
} else {
var valueDiv = $('<span class="status-param-value">');
renderCollapsableValue(valueDict.value, valueDiv);
paramDiv.append(valueDiv);
}
return paramDiv;
}
if (!sidebar && (
!$.isEmptyObject(infoMap.kwargs) || infoMap.args.length > 0)) {
var paramDiv = $('<div class="param-container">');
paramDiv.append(
$('<div class="param-container-title">')
.text('Parameters'));
// Positional arguments
$.each(infoMap.args, function(index, valueDict) {
paramDiv.append(renderParam(index, valueDict));
});
// Keyword arguments in alphabetical order
var keywordNames = [];
$.each(infoMap.kwargs, function(key, value) {
keywordNames.push(key);
});
keywordNames.sort();
$.each(keywordNames, function(index, key) {
paramDiv.append(renderParam(key, infoMap.kwargs[key]));
});
containerDiv.append(paramDiv);
}
// Outputs in alphabetical order, but default first
if (!sidebar) {
var outputContinerDiv = $('<div class="outputs-container">');
outputContinerDiv.append(
$('<div class="outputs-container-title">')
.text('Outputs'));
var outputNames = [];
$.each(infoMap.outputs, function(key, value) {
if (key != 'default') {
outputNames.push(key);
}
});
outputNames.sort();
outputNames.unshift('default');
$.each(outputNames, function(index, key) {
outputContinerDiv.append(renderParam(
key, {'type': 'slot', 'slotKey': infoMap.outputs[key]}));
});
containerDiv.append(outputContinerDiv);
}
// Related pipelines
function renderRelated(relatedList, relatedTitle, classPrefix) {
var relatedDiv = $('<div>');
relatedDiv.addClass(classPrefix + '-container');
relatedTitleDiv = $('<div>');
relatedTitleDiv.addClass(classPrefix + '-container-title');
relatedTitleDiv.text(relatedTitle);
relatedDiv.append(relatedTitleDiv);
$.each(relatedList, function(index, relatedPipelineId) {
var relatedInfoMap = STATUS_MAP.pipelines[relatedPipelineId];
if (relatedInfoMap) {
var relatedLink = $('<a>');
relatedLink
.addClass(classPrefix + '-link')
.attr('title', 'ID #' + relatedPipelineId)
.attr('href', '#pipeline-' + relatedPipelineId)
.text(relatedInfoMap.classPath);
relatedLink.click(function(event) {
selectPipeline(relatedPipelineId);
event.preventDefault();
});
relatedDiv.append(relatedLink);
} else {
var relatedIdDiv = $('<div>');
relatedIdDiv
.addClass(classPrefix + '-pipeline-id')
.text('ID #' + relatedPipelineId);
relatedDiv.append(relatedIdDiv);
}
});
return relatedDiv;
}
// Run after
if (!sidebar && infoMap.afterSlotKeys.length > 0) {
var foundPipelineIds = [];
$.each(infoMap.afterSlotKeys, function(index, slotKey) {
if (STATUS_MAP.slots[slotKey]) {
var slotDict = STATUS_MAP.slots[slotKey];
if (slotDict.fillerPipelineId) {
foundPipelineIds.push(slotDict.fillerPipelineId);
}
}
});
containerDiv.append(
renderRelated(foundPipelineIds, 'Run after', 'run-after'));
}
// Spawned children
if (!sidebar && infoMap.children.length > 0) {
containerDiv.append(
renderRelated(infoMap.children, 'Children', 'child'));
}
return containerDiv;
}
// Recursively creates the sidebar. Use null nextPipelineId to create from root.
function generateSidebar(statusMap, nextPipelineId, rootElement) {
var currentElement = null;
if (nextPipelineId) {
currentElement = $('<li>');
// Value should match return of getTreePipelineElementId
currentElement.attr('id', 'item-pipeline-' + nextPipelineId);
} else {
currentElement = rootElement;
nextPipelineId = statusMap.rootPipelineId;
}
var parentInfoMap = statusMap.pipelines[nextPipelineId];
currentElement.append(
constructStageNode(nextPipelineId, parentInfoMap, true));
if (statusMap.pipelines[nextPipelineId]) {
var children = statusMap.pipelines[nextPipelineId].children;
if (children.length > 0) {
var treeElement = null;
if (rootElement) {
treeElement =
$('<ul id="pipeline-tree" class="treeview-black treeview">');
} else {
treeElement = $('<ul>');
}
$.each(children, function(index, childPipelineId) {
var childElement = generateSidebar(statusMap, childPipelineId);
treeElement.append(childElement);
});
currentElement.append(treeElement);
}
}
return currentElement;
}
function selectPipeline(pipelineId, linkName) {
if (linkName) {
location.hash = '#pipeline-' + pipelineId + ';' + linkName;
} else {
location.hash = '#pipeline-' + pipelineId;
}
}
// Depth-first search for active pipeline.
function findActivePipeline(pipelineId, isRoot) {
var infoMap = STATUS_MAP.pipelines[pipelineId];
if (!infoMap) {
return null;
}
// This is an active leaf node.
if (infoMap.children.length == 0 && infoMap.status != 'done') {
return pipelineId;
}
// Sort children by start time only.
var children = infoMap.children.slice(0);
children.sort(function(a, b) {
var infoMapA = STATUS_MAP.pipelines[a];
var infoMapB = STATUS_MAP.pipelines[b];
if (!infoMapA || !infoMapB) {
return 0;
}
if (infoMapA.startTimeMs && infoMapB.startTimeMs) {
return infoMapA.startTimeMs - infoMapB.startTimeMs;
} else {
return 0;
}
});
for (var i = 0; i < children.length; ++i) {
var foundPipelineId = findActivePipeline(children[i], false);
if (foundPipelineId != null) {
return foundPipelineId;
}
}
return null;
}
function getSelectedPipelineId() {
var prefix = '#pipeline-';
var pieces = location.hash.split(';', 2);
if (pieces[0].indexOf(prefix) == 0) {
return pieces[0].substr(prefix.length);
}
return null;
}
/* Event handlers */
function handleHashChange() {
var prefix = '#pipeline-';
var hash = location.hash;
var pieces = hash.split(';', 2);
var pipelineId = null;
if (pieces[0].indexOf(prefix) == 0) {
pipelineId = pieces[0].substr(prefix.length);
} else {
// Bad hash, just show the root pipeline.
location.hash = '';
return;
}
if (!pipelineId) {
// No hash means show the root pipeline.
pipelineId = STATUS_MAP.rootPipelineId;
}
var rootMap = STATUS_MAP.pipelines[STATUS_MAP.rootPipelineId];
var infoMap = STATUS_MAP.pipelines[pipelineId];
if (!rootMap || !infoMap) {
// Hash not found.
return;
}
// Clear any selection styling.
$('.selected-link').removeClass('selected-link');
if (pieces[1]) {
// Show a specific status link.
var statusLink = $(getTreePipelineElementId(pipelineId))
.find('.status-links>a:contains("' + pieces[1] + '")');
if (statusLink.size() > 0) {
var selectedLink = $(statusLink[0]);
selectedLink.addClass('selected-link');
$('#status-console').attr('src', selectedLink.attr('href'));
$('#status-console').show();
} else {
// No console link for this pipeline; ignore it.
$('#status-console').hide();
}
} else {
// Show the console link.
var consoleLink = $(getTreePipelineElementId(pipelineId))
.find('a.status-console');
if (consoleLink.size() > 0) {
var selectedLink = $(consoleLink[0]);
selectedLink.addClass('selected-link');
$('#status-console').attr('src', selectedLink.attr('href'));
$('#status-console').show();
} else {
// No console link for this pipeline; ignore it.
$('#status-console').hide();
}
}
// Mark the pipeline as selected.
var selected = $('#link-pipeline-' + pipelineId);
selected.addClass('selected-link');
selected.parents('.status-box').addClass('selected-link');
// Title is always the info for the root pipeline, to make it easier to
// track across multiple tabs.
document.title = rootMap.classPath + ' - ID #' + STATUS_MAP.rootPipelineId;
// Update the detail status frame.
var stageNode = constructStageNode(pipelineId, infoMap, false);
$('#overview').remove();
stageNode.attr('id', 'overview');
$('#detail').append(stageNode);
// Make sure everything is the right size.
adjustStatusConsole();
}
function handleAutoRefreshClick(event) {
var loc = window.location;
var newSearch = null;
if (!AUTO_REFRESH && event.target.checked) {
newSearch = '?root=' + ROOT_PIPELINE_ID;
} else if (AUTO_REFRESH && !event.target.checked) {
newSearch = '?root=' + ROOT_PIPELINE_ID + '&auto=false';
}
if (newSearch != null) {
loc.replace(
loc.protocol + '//' + loc.host + loc.pathname +
newSearch + loc.hash);
}
}
function handleRefreshClick(event) {
var loc = window.location;
if (AUTO_REFRESH) {
newSearch = '?root=' + ROOT_PIPELINE_ID;
} else {
newSearch = '?root=' + ROOT_PIPELINE_ID + '&auto=false';
}
loc.href = loc.protocol + '//' + loc.host + loc.pathname + newSearch;
return false;
}
function handleDeleteClick(event) {
var ajaxRequest = {
type: 'GET',
url: 'rpc/delete?root_pipeline_id=' + ROOT_PIPELINE_ID,
dataType: 'text',
error: function(request, textStatus) {
if (request.status == 404) {
setButter('Pipeline is already deleted');
} else {
setButter('Delete request failed: ' + textStatus);
}
window.setTimeout(function() {
clearButter();
}, 5000);
},
success: function(data, textStatus, request) {
setButter('Delete request was sent');
window.setTimeout(function() {
window.location.href = 'list';
}, 5000);
}
};
$.ajax(jQuery.extend({}, ajaxRequest));
}
function handleAbortClick(event) {
var ajaxRequest = {
type: 'GET',
url: 'rpc/abort?root_pipeline_id=' + ROOT_PIPELINE_ID,
dataType: 'text',
error: function(request, textStatus) {
setButter('Abort request failed: ' + textStatus);
window.setTimeout(function() {
clearButter();
}, 5000);
},
success: function(data, textStatus, request) {
setButter('Abort request was sent');
window.setTimeout(function() {
clearButter();
window.location.reload();
}, 5000);
}
};
if (confirm('Are you sure you want to abort the pipeline', 'Abort')) {
$.ajax(jQuery.extend({}, ajaxRequest));
}
}
/* Initialization. */
function initStatus() {
if (window.location.search.length > 0 &&
window.location.search[0] == '?') {
var query = window.location.search.substr(1);
var pieces = query.split('&');
$.each(pieces, function(index, param) {
var mapping = param.split('=');
if (mapping.length != 2) {
return;
}
if (mapping[0] == 'auto' && mapping[1] == 'false') {
AUTO_REFRESH = false;
} else if (mapping[0] == 'root') {
ROOT_PIPELINE_ID = mapping[1];
if (ROOT_PIPELINE_ID.match(/^pipeline-/)) {
ROOT_PIPELINE_ID = ROOT_PIPELINE_ID.substring(9);
}
}
});
}
if (!Boolean(ROOT_PIPELINE_ID)) {
setButter('Missing root param' +
'. For a job list click <a href="list">here</a>.',
true, null, true);
return;
}
var loadingMsg = 'Loading... #' + ROOT_PIPELINE_ID;
var attempts = 1;
var ajaxRequest = {
type: 'GET',
url: 'rpc/tree?root_pipeline_id=' + ROOT_PIPELINE_ID,
dataType: 'text',
error: function(request, textStatus) {
if (request.status == 404) {
if (++attempts <= 5) {
setButter(loadingMsg + ' [attempt #' + attempts + ']');
window.setTimeout(function() {
$.ajax(jQuery.extend({}, ajaxRequest));
}, 2000);
} else {
setButter('Could not find pipeline #' + ROOT_PIPELINE_ID +
'. For a job list click <a href="list">here</a>.',
true, null, true);
}
} else if (request.status == 449) {
var root = request.getResponseHeader('root_pipeline_id');
var newURL = '?root=' + root + '#pipeline-' + ROOT_PIPELINE_ID;
window.location.replace(newURL);
} else {
getResponseDataJson(textStatus);
}
},
success: function(data, textStatus, request) {
var response = getResponseDataJson(null, data);
if (response) {
clearButter();
STATUS_MAP = response;
LANG = request.getResponseHeader('Pipeline-Lang');
initStatusDone();
}
}
};
setButter(loadingMsg);
$.ajax(jQuery.extend({}, ajaxRequest));
}
function initStatusDone() {
jQuery.timeago.settings.allowFuture = true;
// Update the root pipeline ID to match what the server returns. This handles
// the case where the ID specified is for a child node. We always want to
// show status up to the root.
ROOT_PIPELINE_ID = STATUS_MAP.rootPipelineId;
// Generate the sidebar.
generateSidebar(STATUS_MAP, null, $('#sidebar'));
// Turn the sidebar into a tree.
$('#pipeline-tree').treeview({
collapsed: true,
unique: false,
cookieId: 'pipeline Id here',
toggle: handleTreeToggle
});
$('#sidebar').show();
var rootStatus = STATUS_MAP.pipelines[STATUS_MAP.rootPipelineId].status;
var isFinalState = /^done$|^aborted$|^canceled$/.test(rootStatus);
// Init the control panel.
$('#auto-refresh').click(handleAutoRefreshClick);
if (!AUTO_REFRESH) {
$('#auto-refresh').attr('checked', '');
} else {
if (!isFinalState) {
// Only do auto-refresh behavior if we're not in a terminal state.
window.setTimeout(function() {
var loc = window.location;
var search = '?root=' + ROOT_PIPELINE_ID;
loc.replace(loc.protocol + '//' + loc.host + loc.pathname + search);
}, 30 * 1000);
}
}
$('.refresh-link').click(handleRefreshClick);
$('.abort-link').click(handleAbortClick);
$('.delete-link').click(handleDeleteClick);
if (LANG == 'Java') {
if (isFinalState) {
$('.delete-link').show();
} else {
$('.abort-link').show();
}
}
$('#control').show();
// Properly adjust the console iframe to match the window size.
$(window).resize(adjustStatusConsole);
window.setTimeout(adjustStatusConsole, 0);
// Handle ajax-y URL fragment events.
$(window).hashchange(handleHashChange);
$(window).hashchange(); // Trigger for initial load.
// When there's no hash selected, auto-navigate to the most active node.
if (window.location.hash == '' || window.location.hash == '#') {
var activePipelineId = findActivePipeline(STATUS_MAP.rootPipelineId, true);
if (activePipelineId) {
selectPipeline(activePipelineId);
} else {
// If there's nothing active, then select the root.
selectPipeline(ROOT_PIPELINE_ID);
}
}
// Scroll to the current active node.
expandTreeToPipeline(getSelectedPipelineId());
}