| {% extends "layout.html" %} |
| |
| {% block head %} |
| {{ super() }} |
| <script type='text/javascript'> |
| // <![CDATA[ |
| // |
| |
| // Data loaded from the master's JSON interface. |
| var builders = {}; |
| var revisions = []; |
| |
| function addLinks(text) { |
| if (text && text.indexOf("<a href=") == -1) { |
| text = text.replace( |
| /(https?:\/\/.+?)(\s|\t|<|\n|\r|$)/g, '<a href="$1" target="_blank">$1</a>$2'); |
| } |
| return text; |
| } |
| |
| // |
| // Make the commit message display more or less. |
| // |
| function toggleMore(id, visible) { |
| if (visible) { |
| document.getElementById(id + "_long_desc").style.display = "inline"; |
| document.getElementById(id + "_morelink").style.display = "none"; |
| document.getElementById(id + "_lesslink").style.display = "inline"; |
| } else { |
| document.getElementById(id + "_long_desc").style.display = "none"; |
| document.getElementById(id + "_morelink").style.display = "inline"; |
| document.getElementById(id + "_lesslink").style.display = "none"; |
| } |
| } |
| |
| // |
| // Functions used to display the build status bubble on box click. |
| // |
| |
| // show the build status box. This is called when the user clicks on a block. |
| function showBuildBox(url, event) { |
| // Find the current curson position. |
| var cursorPosTop = (window.event ? window.event.clientY : event.pageY) |
| var cursorPosLeft = (window.event ? window.event.clientX : event.pageX) |
| |
| // Offset the position by 5, to make the window appears under the cursor. |
| cursorPosTop = cursorPosTop + document.body.scrollTop -5 ; |
| cursorPosLeft = cursorPosLeft + document.body.scrollLeft - 5; |
| |
| // Move the div (hidden) under the cursor. |
| var divBox = document.getElementById('divBox'); |
| divBox.style.top = parseInt(cursorPosTop) + 'px'; |
| divBox.style.left = parseInt(cursorPosLeft) + 'px'; |
| |
| // Reload the hidden frame with the build page we want to show. |
| // The onload even on this frame will update the div and make it visible. |
| document.getElementById("frameBox").src = url |
| |
| // We don't want to reload the page. |
| return false; |
| } |
| |
| // OnLoad handler for the iframe containing the build to show. |
| function updateDiv(event) { |
| // Get the frame innerHTML. |
| var iframeContent = document.getElementById("frameBox").contentWindow.document.body.innerHTML; |
| |
| // If there is any content, update the div, and make it visible. |
| if (iframeContent) { |
| var divBox = document.getElementById('divBox'); |
| divBox.innerHTML = iframeContent ; |
| divBox.style.display = "block"; |
| } |
| } |
| |
| // Util functions to know if an element is contained inside another element. |
| // We use this to know when we mouse out our build status div. |
| function containsDOM (container, containee) { |
| var isParent = false; |
| do { |
| if ((isParent = container == containee)) |
| break; |
| containee = containee.parentNode; |
| } while (containee != null); |
| |
| return isParent; |
| } |
| |
| // OnMouseOut handler. Returns true if the mouse moved out of the element. |
| // It is false if the mouse is still in the element, but in a blank part of it, |
| // like in an empty table cell. |
| function checkMouseLeave(element, event) { |
| if (element.contains && event.toElement) { |
| return !element.contains(event.toElement); |
| } |
| else if (event.relatedTarget) { |
| return !containsDOM(element, event.relatedTarget); |
| } |
| } |
| |
| // Obtain the current date and time in a nicely formatted string. |
| function getDateString() { |
| var date = new Date(); |
| return date.toDateString() + " at " + date.toTimeString(); |
| } |
| |
| // Set the "loading" message. |
| function setLoadingMessage(msg) { |
| var loading = document.getElementById("loading_div"); |
| if (loading) { |
| loading.innerHTML = msg; |
| } |
| } |
| |
| // Load the console page data. |
| function loadConsoleData() { |
| loadJSONP('/console_json', fixConsoleData); |
| setLoadingMessage("Loading..."); |
| } |
| |
| // Sort the categories, subcategories, and builders for display. |
| function fixConsoleData(data) { |
| var new_builders = {}; |
| var categories = Object.keys(data["builders"]); |
| categories.sort(); |
| for (var category_index in categories) { |
| var category = categories[category_index]; |
| new_builders[category] = {}; |
| var subcategories = Object.keys(data["builders"][category]); |
| subcategories.sort() |
| for (var subcategory_index in subcategories) { |
| var subcategory = subcategories[subcategory_index]; |
| new_builders[category][subcategory] = data["builders"][category][subcategory]; |
| for (var cat_full in new_builders[category][subcategory]) { |
| new_builders[category][subcategory][cat_full].sort(); |
| } |
| } |
| } |
| builders = new_builders; |
| revisions = data["revisions"]; |
| setLoadingMessage("Last loaded at " + getDateString()); |
| buildConsoleTable(); |
| } |
| |
| // Create a new row with two spacer cells. |
| function newRowWithSpacers(table) { |
| var row = table.insertRow(-1); |
| var spacer = row.insertCell(-1) |
| spacer.style.width = "1%"; |
| var spacer = row.insertCell(-1) |
| spacer.style.width = "1%"; |
| return row; |
| } |
| |
| function newHyperlink(href, text, open_in_new_tab) { |
| var link = document.createElement("a"); |
| link.href = href; |
| if (text) { |
| link.innerHTML = text; |
| } |
| if (open_in_new_tab == true) { |
| link.target = "_blank"; |
| } |
| return link; |
| } |
| |
| // Build the main console view table. |
| function buildConsoleTable() { |
| // The main console table. |
| var table = document.getElementById("console_table"); |
| |
| // Clear the table. |
| while (table.hasChildNodes()) { |
| table.removeChild(table.lastChild); |
| } |
| |
| // Count the number of columns, starting with 2 for the spacer TDs, so that |
| // we can get the colspans right. |
| var total_cols = 2; |
| |
| // Top row: Categories. |
| var top_row = newRowWithSpacers(table); |
| |
| // Second row: Subcategories. |
| var second_row = newRowWithSpacers(table); |
| |
| // Third row: Individual builder statuses. |
| var builder_row = newRowWithSpacers(table); |
| |
| // Keep track of the first and last visible categories. |
| var first_category = null; |
| var last_category = null; |
| |
| // This loop creates the content for the top three rows. |
| for (var category in builders) { |
| // Create the category headers with links to the waterfall for each. |
| var category_td = top_row.insertCell(-1); |
| category_td.id = "category_header_" + category; |
| var td_class = "DevStatus Alt category_" + category; |
| category_td.className = td_class; |
| var category_link = newHyperlink("/waterfall?"); |
| var h3 = document.createElement("h3"); |
| h3.innerHTML = category; |
| // Hyperlink to the waterfall page which displays all builders within this |
| // category. Each full category name is appended in the subsequent loops. |
| category_link.appendChild(h3); |
| category_td.appendChild(category_link); |
| |
| if (!allCategories[category]) { |
| // Hide the column header for this category if the associated checkbox |
| // isn't checked. |
| category_td.style.display = "none"; |
| } else { |
| // If the category is visible, mark it as a possible first or last visible |
| // category. |
| if (!first_category) { |
| first_category = category; |
| } |
| last_category = category; |
| } |
| |
| // Keep track of the colspan (number of builders) for this category as we |
| // loop over the subcategories. |
| var category_colspan = 0; |
| |
| // Loop over the subcategories. |
| for (var subcategory in builders[category]) { |
| // Create the subcategory headers with links to the waterfall for each. |
| var subcategory_td = second_row.insertCell(-1); |
| subcategory_td.className = "DevStatus category_" + category + "_subcategory_" + subcategory; |
| subcategory_td.colSpan = builders[category][subcategory].length; |
| // Hyperlink to the waterfall page which displays all builders within this |
| // subcategory. Each full category name is appended in the subsequent |
| // loops. |
| var subcategory_link = newHyperlink("/waterfall?", subcategory) |
| subcategory_td.appendChild(subcategory_link); |
| |
| // Hide the column header for this subcategory if the associated checkbox |
| // isn't checked. |
| if (!(allCategories[category] && allSubcategories[subcategory])) { |
| subcategory_td.style.display = "none"; |
| } |
| |
| // Loop over the full category names (eg. "Build|Ubuntu12|GateKeeper") |
| // within each subcategory. |
| for (var cat_full in builders[category][subcategory]) { |
| // Create a wrapper cell to hold the builder statuses for this category. |
| var builder_wrapper_td = builder_row.insertCell(-1); |
| builder_wrapper_td.className = "DevSlave Alt category_" + category + "_subcategory_" + subcategory; |
| // Create a sub-table within the builder wrapper cell. |
| var builder_wrapper_table = document.createElement("table"); |
| builder_wrapper_table.width = "100%"; |
| builder_wrapper_td.appendChild(builder_wrapper_table); |
| var builder_wrapper_table_tr = builder_wrapper_table.insertRow(-1); |
| // Hide the builder wrapper cell if the current category and subcategory |
| // aren't both displaying. |
| if (!(allCategories[category] && allSubcategories[subcategory])) { |
| builder_wrapper_td.style.display = "none"; |
| } |
| |
| // Append the full category name to the category and subcategory |
| // waterfall links. |
| if (category_link.href.indexOf("?", category_link.href.length - 1) == -1) { |
| category_link.href += "&"; |
| } |
| category_link.href += "category=" + cat_full; |
| if (subcategory_link.href.indexOf("?", subcategory_link.href.length - 1) == -1) { |
| subcategory_link.href += "&"; |
| } |
| subcategory_link.href += "category=" + cat_full; |
| |
| // Loop over the builders within this fully-qualified category. |
| for (var builder_index in builders[category][subcategory][cat_full]) { |
| // Create a colored cell with a link to this builder's status page. |
| var builder = builders[category][subcategory][cat_full][builder_index]; |
| var builder_name = builder.builderName; |
| var builder_td = builder_wrapper_table_tr.insertCell(-1); |
| builder_td.className = "DevSlaveBox"; |
| builder_td.builder_name = builder_name; |
| builder_link = newHyperlink(builder.url, "", true); |
| builder_link.id = "commentIndicator_" + builder_name; |
| builder_link.className = "DevSlaveBox " + builder.color; |
| if (builder_statuses[builder_name]) { |
| builder_link.classList.add("BuilderHasComment"); |
| } |
| builder_td.appendChild(builder_link); |
| |
| // Create the status tooltip for this builder. |
| var tooltip_div = document.createElement("div"); |
| tooltip_div.className = "BuilderTooltip RoundedRect"; |
| tooltip_div.innerHTML = builderCommentText(builder_name); |
| builder_td.appendChild(tooltip_div); |
| } |
| // Only count the columns which are being displayed toward the total |
| // colspan. |
| if (allCategories[category] && allSubcategories[subcategory]) { |
| total_cols++; |
| } |
| } |
| // Only cound the subcategories which are being displayed toward the |
| // categories' colspans. |
| if (allCategories[category] && allSubcategories[subcategory]) { |
| category_colspan += subcategory_td.colSpan; |
| } |
| } |
| category_td.colSpan = category_colspan; |
| } |
| |
| // Round the corners of the first and last visible category headers. |
| if (first_category) { |
| document.getElementById("category_header_" + first_category).classList.add("first"); |
| } |
| if (last_category) { |
| document.getElementById("category_header_" + last_category).classList.add("last") |
| } |
| |
| // Build rows for each revision. |
| for (var rev_index in revisions) { |
| // Information about this revision. |
| var revision = revisions[rev_index]; |
| |
| // String which alternates between "" and " Alt"; used for creating |
| // alternating shades of gray. |
| var alt = ""; |
| if (rev_index % 2) { |
| alt = " Alt"; |
| } |
| |
| // Row containing the commit hash, committer, and statuses for each builder |
| // at this revision. |
| var rev_status_tr = table.insertRow(-1); |
| // Cell containing the commit hash with a link to the commit in the |
| // repository. |
| var revision_td = rev_status_tr.insertCell(-1); |
| revision_td.className = "DevRev" + alt; |
| var revision_link = newHyperlink(revision.repository + "/+/" + revision.id, |
| revision.id.substring(0, 11), |
| true); |
| revision_td.appendChild(revision_link); |
| // Cell containing the committer's username. |
| var dev_name_td = rev_status_tr.insertCell(-1); |
| dev_name_td.className = "DevName" + alt; |
| dev_name_td.width = "1%"; |
| dev_name_td.innerHTML = revision.who; |
| |
| // Loop over the builders, obtaining the status and creating a build status |
| // popup link for each. |
| for (var category in builders) { |
| for (var subcategory in builders[category]) { |
| for (var cat_full in builders[category][subcategory]) { |
| // Wrapper cell containing statuses for builders in this fully- |
| // qualified category. |
| var build_status_td = rev_status_tr.insertCell(-1); |
| // Hide the build status cell if we're not displaying both the |
| // builder's category and subcategory. |
| if (!allCategories[category] || !allSubcategories[subcategory]) { |
| build_status_td.style.display = "none"; |
| } |
| build_status_td.className = "DevStatus category_" + category + "_subcategory_" + subcategory + alt; |
| // Table which will hold the individual builder statuses. |
| var build_status_wrapper = document.createElement("table"); |
| build_status_wrapper.width = "100%"; |
| build_status_td.appendChild(build_status_wrapper); |
| var builder_wrapper_tr = build_status_wrapper.insertRow(-1); |
| // Loop over the builders in this fully-qualified category. |
| for (var builder_index in builders[category][subcategory][cat_full]) { |
| var builder = builders[category][subcategory][cat_full][builder_index]; |
| // Loop over the list of builds at this revision for this builder. |
| // This is almost always a single build. |
| for (var build_index in revision.builds[builder.builderName]) { |
| var build = revision.builds[builder.builderName][build_index]; |
| // Create a colored cell representing the result of this build, |
| // with a link to open a popup with more detailed build step |
| // statuses. |
| var build_td = builder_wrapper_tr.insertCell(-1); |
| build_td.className = "DevStatusBox"; |
| var build_link = newHyperlink("#", "", true); |
| build_link.popup_url = build.url; |
| build_link.onclick = function(event) { showBuildBox(this.popup_url, event); return false; }; |
| build_link.title = build.pageTitle; |
| build_link.className = "DevStatusBox " + build.color + " " + build.tag; |
| build_td.appendChild(build_link); |
| } |
| } |
| } |
| } |
| } |
| |
| // Row containing the change description for this revision. |
| var rev_comment_tr = table.insertRow(-1); |
| var rev_comment_td = rev_comment_tr.insertCell(-1); |
| rev_comment_td.className = "DevComment" + alt; |
| rev_comment_td.colSpan = total_cols; |
| // Split the comment text into: first line (short) and remainder (long). |
| var comment_short_text_span = document.createElement("span"); |
| rev_comment_td.appendChild(comment_short_text_span); |
| var comment_long_text_span = document.createElement("span"); |
| comment_long_text_span.id = revision.id + "_long_desc"; |
| comment_long_text_span.style.display = "none"; |
| // Create "more" and "less" links. |
| var more_link = document.createElement("a"); |
| more_link.href = "javascript:toggleMore('" + revision.id + "', true)"; |
| more_link.id = revision.id + "_morelink"; |
| more_link.innerHTML = "more"; |
| rev_comment_td.appendChild(more_link); |
| var less_link = document.createElement("a"); |
| less_link.href = "javascript:toggleMore('" + revision.id + "', false)"; |
| less_link.id = revision.id + "_lesslink"; |
| less_link.style.display = "none"; |
| less_link.innerHTML = "less"; |
| rev_comment_td.appendChild(less_link); |
| rev_comment_td.appendChild(comment_long_text_span); |
| var comment_text_lines = revision.comments.split("\n"); |
| // Only include the "more" link if the comment has more than one line. |
| if (comment_text_lines.length > 1) { |
| comment_short_text_span.innerHTML = comment_text_lines[0] + " ... "; |
| var comment_long_text_para = document.createElement("p"); |
| comment_long_text_span.appendChild(comment_long_text_para); |
| for (var comment_line = 1; comment_line < comment_text_lines.length; ++comment_line) { |
| comment_long_text_span.innerHTML += comment_text_lines[comment_line] + "<br/>"; |
| } |
| comment_long_text_span.innerHTML = addLinks(comment_long_text_span.innerHTML); |
| } else { |
| comment_short_text_span.innerHTML = revision.comments; |
| more_link.style.display = "none"; |
| } |
| |
| // Optional row containing more information about the revision, for example |
| // test failures. |
| if (revision["details"] && revision["details"].length > 0) { |
| var details_tr = table.insertRow(-1); |
| var details_td = details_tr.insertCell(-1); |
| details_td.colSpan = total_cols; |
| details_td.className = "DevDetails bottom" + alt; |
| var details_list = document.createElement("ul"); |
| for (var detail_index in revision["details"]) { |
| var detail = revision["details"][detail_index]; |
| var detail_item = document.createElement("li"); |
| var detail_text = document.createElement("span"); |
| detail_text.innerHTML = detail["buildername"] + ": " + detail["status"] + " - "; |
| detail_item.appendChild(detail_text); |
| for (var log_index in detail["logs"]) { |
| var log = detail["logs"][log_index]; |
| var log_link = document.createElement("a"); |
| log_link.href = log["url"]; |
| log_link.innerHTML = log["name"]; |
| detail_item.appendChild(log_link); |
| } |
| details_list.appendChild(detail_item); |
| } |
| details_td.appendChild(details_list); |
| } else { |
| rev_comment_td.className += " bottom"; |
| } |
| |
| // Spacing row just containing whitespace. |
| var rev_spacing_tr = table.insertRow(-1); |
| rev_spacing_tr.className = "DevStatusSpacing"; |
| var rev_spacing_td = rev_spacing_tr.insertCell(-1); |
| } |
| } |
| |
| // ]]> |
| </script> |
| {% endblock %} |
| |
| {% block content %} |
| |
| <h1>Console View</h1> |
| |
| <div id="loading_div"></div> |
| |
| <div align="center"> |
| <table width="95%" class="Grid" border="0" cellspacing="0"> |
| <tr> |
| <td width="33%" align="left" class="left_align"> |
| {% if repository %} |
| <br><b>Repository:</b> {{ repository|e }} |
| {% endif %} |
| </td> |
| <td width="33%" align="center" class="center_align"> |
| <div align="center"> |
| <table class="info"> |
| <tr> |
| <td>Legend: </td> |
| <td class='legend success' title='All tests passed'>Passed</td> |
| <td class='legend failure' title='There is a new failure. Take a look!'>Failed</td> |
| <td class='legend warnings' title='It was failing before, and it is still failing. Make sure you did not introduce new regressions'>Failed Again</td> |
| <td class='legend running' title='The tests are still running'>Running</td> |
| <td class='legend running_failure' title='The tests are still running, but at least one step has failed.'>Running w/ failures</td> |
| <td class='legend exception' title='Something went wrong with the test, there is no result'>Exception</td> |
| <td class='legend offline' title='The builder is offline, as there are no slaves connected to it'>Offline</td> |
| <td class='legend notstarted' title='No result yet.'>No data</td> |
| <td class='legend BuilderHasComment' title='This builder has a comment.'>Builder comment</td> |
| </tr> |
| </table> |
| </div> |
| </td> |
| <td width="33%" align="right" class="right_align"> |
| <script type="text/javascript"> |
| // <![CDATA[ |
| function reload_page() { |
| name_value = document.getElementById('namebox').value |
| if (document.location.href.lastIndexOf('?') == -1) |
| document.location.href = document.location.href+ '?name=' + name_value; |
| else |
| document.location.href = document.location.href+ '&name=' + name_value; |
| } |
| // ]]> |
| </script> |
| <input id='namebox' name='name' type='text' style='color:#999;' |
| onblur='this.value = this.value || this.defaultValue; this.style.color = "#999";' |
| onfocus='this.value=""; this.style.color = "#000";' |
| value='Personalized for...'/> |
| <input type='submit' value='Go' onclick='reload_page()'/> |
| </td> |
| </tr> |
| </table> |
| </div> |
| |
| <br/> |
| <div align="center"> |
| <table id="console_table" width="96%"></table> |
| </div> |
| |
| |
| <div id="divBox" onmouseout="if (checkMouseLeave(this, event)) this.style.display = 'none'" class="BuildWaterfall" style="display: none;"> |
| </div> |
| |
| |
| <iframe id="frameBox" style="display: none;"></iframe> |
| |
| <script type="text/javascript"> |
| // replace 'onload="updateDiv(event);" with this, as iframe doesn't have onload event in xhtml |
| window.onload = function() { |
| document.getElementById('frameBox').onload = function(event) { |
| updateDiv(event); |
| }; |
| }; |
| </script> |
| |
| {% endblock %} |
| |
| |
| {% block footer %} |
| |
| {{ super() }} |
| {# <p>Debug info: {{ debuginfo }}</p> #} |
| {% endblock %} |