blob: d29a558b408ae4e64ed299a0a010960759419b1d [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Active selection border styles. These don't need to be custom classes
// since they're just combinations of bootstrap classes.
const topSelectorStyle = `border-black border-3 border-start-0
border-end-0 border-top-0`;
const bottomSelectorStyle = `border-top-0 border-start-0
border-end-0 border-light border-2`;
//maps repo status indicators to labels/colors
const head_status = {
"-": ["No change", "secondary"],
"A": ["Added", "success"],
"M": ["Modified", "success"],
"D": ["Deleted", "danger"],
"R": ["Renamed", "secondary-emphasis"],
"C": ["Copied", "secondary-emphasis"],
"T": ["Mode changed", "secondary-emphasis"],
"U": ["Unmerged", "danger"],
}
const working_status = {
"-": ["New", "success"],
"m": ["Modified", "success"],
"d": ["Deleted", "danger"],
"u": ["Unmerged", "danger"]
}
//Panel selection/styling functions
function showPackages() {
$("#packagesPanel").show();
$("#imagesPanel").hide();
$("#showPackages").addClass(topSelectorStyle);
$("#showImages").removeClass(topSelectorStyle);
}
function showImages() {
$("#packagesPanel").hide();
$("#imagesPanel").show();
$("#showImages").addClass(topSelectorStyle);
$("#showPackages").removeClass(topSelectorStyle);
}
function showLogs() {
$("#logsPanel").show()
$("#repoPanel").hide()
$("#showLogs").addClass(bottomSelectorStyle)
$("#showRepo").removeClass(bottomSelectorStyle)
}
function showRepo() {
$("#logsPanel").hide()
$("#repoPanel").show()
$("#showRepo").addClass(bottomSelectorStyle)
$("#showLogs").removeClass(bottomSelectorStyle)
}
function logItemHTML(commandName, logText, now, completed) {
return `
<li class = "row bg-dark border-bottom border-1 border-black" id = "` + now.format("x") + `liveLog">
<div class="col-6"><p2 class = "small text-light ms-2"><code class = "text-light">`+ commandName + `</code></p2></div>
<div class = "col-2" id="`+ now.format("x") + `status">
`+ (completed ?
`<p2 class="small text-success ms">Completed</p2>`
: `<p2 class="small log-running">Running</p2>`)
+ ` </div>
<div class = "col-2">
<a href="#log`+ now.format("x") + `" data-bs-toggle="collapse" role="button"
aria-controls="log`+ now.format("x") + `" aria-expanded="false"
class = "d-flex align-items-center log-expander link-underline
link-underline-opacity-0 classic-link">
View Log
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-right-fill" viewBox="0 0 16 16">
<path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
</svg>
</a>
</div>
<div class="col"><p2 class = "small text-light ms-2">`+ now.format("MMM D h:mma") + `</p2></div>
<div class = "bg-black small overflow-auto border-top collapse log-box" id = "log`+ now.format("x") + `">
<code class = "small text-light log-text" id = "log`+ now.format("x") + `Text" >` + logText + `</code>
</div>
</li>
`
}
function populateLiveLog(commandName, logText, now) {
//log panel already exists
if ($("#" + now.format("x") + "liveLog").length) {
$("#log" + now.format("x") + "Text").html(logText);
}
else {
//Create it
$("#noLogs").remove()
$("#logsList").html(
logItemHTML(commandName, logText, now, false) + $("#logsList").html()
)
//Rotates the log expander arrow
$(".log-expander").on('click', function () {
$(this).children("svg").toggleClass("rotate-log-button")
})
}
}
function populateHistoricLogs() {
$.ajax({
url: "/get-logs",
type: "POST",
success: function (response) {
foundUpdate = false
allLogsHTML = ``;
response.forEach(function (log) {
now = moment.unix(log.time)
allLogsHTML += logItemHTML(log.command, log.logs, now, true);
if(log.command == "update_chroot" && !foundUpdate){
foundUpdate = true
$("#chrootLastUpdated").html(moment(log.time, "X").format("MMM d, YYYY"))
}
})
if(!foundUpdate){
$("#chrootLastUpdated").html($("#chrootCreated").html())
}
if (response.length < 1) {
allLogsHTML = `
<div class = "row bg-dark" id = "noLogs">
<p2 class = "text-light ms-2">
No log history found...
</p2>
</div>
`
}
$("#logsList").html(allLogsHTML);
$(".log-expander").on('click', function () {
$(this).children("svg").toggleClass("rotate-log-button")
})
},
error: function (xhr) {
console.log("failure")
}
})
}
function clearLogs() {
$("#clearLogs").addClass("disabled");
$.post({
url: "/clear-logs",
success: (response) => {
$("#clearLogs").removeClass("disabled");
populateHistoricLogs()
}
})
}
//Makes request to gRPC server via python routes and generates HTML
//for repo files dynamically.
function populateRepoFiles() {
$("#repoStatusRefresh").addClass("disabled")
$.post({
url: "/repo-refresh",
success: function (response) {
$("#repoProject").removeClass("placeholder bg-light me-3");
$("#repoBranch").removeClass("placeholder bg-light me-3");
$("#repoProject").html(response.project);
$("#repoBranch").html(response.branch);
var filesHTML = "";
response.files.forEach(function (file) {
filesHTML += `
<li class="row bg-dark border-top border-black
p-0 m-0 align-items-center">
<div class="col-6 text-break ms-2">
<p2 class = "small text-light">` + file.file + `</p2>
</div>
<div align = "center" class="col">
<p2 class = "small text-` + head_status[file.head][1] + `">
` + head_status[file.head][0] + `
</p2>
</div>
<div align = "center" class="col">
<p2 class = "small text-` + working_status[file.working][1] +
`">
` + working_status[file.working][0] + `
</p2>
</div>
</li>
`
})
$("#repoFilesList").html(filesHTML);
$("#repoStatusRefresh").removeClass("disabled");
},
error: function (xhr) {
console.log("failure");
}
});
}
function repoSync() {
now = moment()
window.onbeforeunload = function () {
return true;
};
$("#repoSync").addClass("disabled");
$.post({
url: "/repo-sync",
success: function (response) {
$("#repoSync").removeClass("disabled");
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"repo_sync",
e.currentTarget.response,
now,
)
}
}
})
}
var board_packages = {}
function populatePackages(board = "") {
$.ajax({
url: "/get-packages",
type: "POST",
data: JSON.stringify({
board: board
}),
contentType: "application/json",
success: function (response) {
allPackagesHTML = "";
allImagesHTML = "";
done = false;
jQuery.each(response, function (board, data) {
$.post({
url: "/all-packages",
data: JSON.stringify({
board: board
}),
contentType: "application/json",
success: function(response){
board_packages[board] = [];
response.forEach((item) => {
board_packages[board].push(new Option(item))
})
if(!done){
done = true;
board_packages[board].forEach((item) => {
$('#addPackagePackageSelect').append(item).trigger('change');
})
}
}
})
packages = data.packages
images = data.images
if (packages.length > 0) {
allPackagesHTML += `<ul class="p-0 board-package-list"
id= "`+ board + `-package-list">
`
packages.forEach(function (pack) {
allPackagesHTML +=
`<li class="row bg-dark border-top border-black p-0 m-0 align-items-center">
<div class="col-7"><p2 class = "small text-light m-4">` + pack.name + `</p2></div>
<div class="col-2 d-flex">
<p2 class = "col small text-success"></p2>
<p2 class = "col small text-danger"></p2>
</div>
<div class="col">
<button type="button" class="small btn btn-success btn-sm pt-0 pb-0 mt-1 mb-1 build-single"
id = "` + pack.name + `-build">Build</button>
<button type="button" class="small btn btn-secondary btn-sm pt-0 pb-0" data-bs-toggle="modal"
data-bs-target="#` + pack.name.replace("/", "-") + `-info-modal" >Info</button>
<button type="button" class="small btn btn-danger btn-sm pt-0 pb-0 workon-stop"
id = "` + pack.name + `-stop">Stop</button>
</div>
</li>
<div class="modal fade" id="` + pack.name.replace("/", "-") + `-info-modal" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header">
<h1 class="modal-title fs-5 text-light">Package Info</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p2 class = "row text-white-50 ms-1">Package Name</p2>
<p2 class = "row text-light ms-1">`+ pack.name + `</p2>
<p2 class = "row text-white-50 ms-1 mt-4">Repositor`+ (pack.repo.length > 1 ? `ies` : `y`) + `</p2>
`+
pack.repo.reduce(function (base, curr) {
return base += `<p2 class = "row text-light ms-1"">` + curr + `</p2>`
}, "")
+ `
<p2 class = "row text-white-50 ms-1 mt-4">Source Director`+ (pack.source.length > 1 ? `ies` : `y`) + `</p2>
`+
pack.source.reduce(function (base, curr) {
return base += `<p2 class = "row text-light ms-1"">` + curr + `</p2>`
}, "")
+ `
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
`
})
allPackagesHTML += `</ul>`
}
else {
allPackagesHTML += `
<row class="board-package-list small text-light bg-dark border-top border-black p-0 m-0"
id= `+ board + `-package-list>
<p2 class = "ps-5">No packages to display for this board...</p2>
</row>`
}
if (images.length > 0) {
allImagesHTML += `<ul class="p-0 board-image-list"
id= "`+ board + `-image-list">`
images.forEach(function (image) {
allImagesHTML +=
`<li class="row bg-dark border-top border-black p-0 m-0 align-items-center">
<div class="col-8"><p2 style = "display: block;" class = "small text-light ms-3">` + image.path
+ `</p2></div>
<div class="col-3 d-flex">
<p2 class = "small text-light ms">` + image.type + `</p2>
</div>`
+ (image.latest ? `<div class = "col-1"> <p2 class = "text-success"> (Latest)</p2></div>` : ``) +
+`</li>`
})
allImagesHTML += `</ul>`
}
else {
allImagesHTML += `
<row class="board-image-list small text-light bg-dark border-top border-black p-0 m-0"
id= `+ board + `-image-list>
<p2 class = "ps-5">No images to display for this board...</p2>
</row>`
}
})
if (!board) {
$("#allBoardPackages").html(allPackagesHTML);
$("#allBoardImages").html(allImagesHTML);
} else {
$(`#` + board + `-package-list`).remove()
$("#allBoardPackages").html($("#allBoardPackages").html() + allPackagesHTML);
}
//Packages board dropdown selection logic
var boardName = $('#boardSelector li a.active').html()
$('.board-selector-title').html(boardName);
$('.board-package-list').hide()
$('.board-image-list').hide()
$('#' + boardName + "-package-list").show()
$('#' + boardName + "-image-list").show()
$(".workon-stop").on('click', workonStop);
$(".build-single").on('click', buildSinglePackage)
$('#boardSelector li').on('click', changeBoardSelectorActive)
},
complete: function (data) {
$("#addPackageBoardSelect").on('select2:select', (e) => {
board = e.params.data.text
$("#addPackagePackageSelect").empty().trigger("change");
board_packages[board].forEach((item) => {
$('#addPackagePackageSelect').append(item).trigger('change');
})
})
},
failure: function (xhr) {
console.log("failure");
}
})
}
function confirmDelete() {
var btnDisabled = $("#deleteSubmit").attr("disabled");
if (typeof btnDisabled == 'undefined' || btnDisabled == false) {
$("#deleteSubmit").attr("disabled", "");
}
else {
$("#deleteSubmit").removeAttr("disabled");
}
}
function changeBoardSelectorActive() {
var boardName = $(this).find('a').html()
$('.board-selector-title').html(boardName);
$('.board-list-item').removeClass('active');
$(this).find('a').addClass('active');
$('.board-package-list').hide()
$('#' + boardName + "-package-list").show()
$('.board-image-list').hide()
$('#' + boardName + "-image-list").show()
}
function workonStart() {
var board = $("#addPackageBoardSelect").select2("data")[0].text;
var pack = $("#addPackagePackageSelect").select2("data")[0].text;
$.post({
url: "/workon-start",
data: JSON.stringify({
board: board,
package: pack
}),
contentType: "application/json",
success: function (response) {
console.log("success");
populatePackages(board = board);
},
error: function (xhr) {
console.log("failure");
}
});
}
//Will make cros_workon stop request via Flask routes.
function workonStop() {
$(this).addClass("disabled");
var board = $(this).parents("ul")[0].id;
board = board.substring(0, board.length - 13)
$.post({
url: "/workon-stop",
data: JSON.stringify({
board: board,
package: this.id.substring(0, this.id.length - 5)
}),
contentType: "application/json",
success: function (response) {
console.log("success");
populatePackages(board = board);
},
error: function (xhr) {
console.log("failure");
}
});
}
function updateChroot() {
$("#updateChrootSubmit").addClass("disabled");
window.onbeforeunload = function () {
return true;
};
buildSource = $('#buildSourceCheck').is(':checked');
toolchainChanged = $("#toolchainChangedCheck").is(":checked");
toolchainTargets = []
$("#updateToolchainTargets").select2("data").forEach(function (board) {
toolchainTargets.push(board.text);
})
now = moment()
$.ajax({
url: "/update-chroot",
type: "POST",
data: JSON.stringify({
buildSource: buildSource,
toolchainChanged: toolchainChanged,
toolchainTargets: toolchainTargets
}),
contentType: "application/json",
success: function (response) {
$("#updateChrootSubmit").removeClass("disabled");
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
error: function (xhr) {
console.log("failure");
console.log(xhr)
$("#updateChrootSubmit").removeClass("disabled");
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"update_chroot",
e.currentTarget.response,
now,
)
}
}
})
}
function replaceChroot() {
$("#replaceSubmit").addClass("disabled");
window.onbeforeunload = function () {
return true;
};
bootstrap = $("#replaceBootstrap").is(":checked");
noUseImage = $("#replaceNoUseImage").is(":checked");
version = $("replaceSDKVersion").value;
$.ajax({
url: "/replace-chroot",
type: "POST",
data: JSON.stringify({
bootstrap: bootstrap,
noUseImage: noUseImage,
version: version
}),
dataType: "json",
contentType: "application/json",
success: function (response) {
$("#replaceSubmit").removeClass("disabled");
location.reload();
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
error: function (xhr) {
console.log("failure");
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"replace_chroot",
e.currentTarget.response,
now
)
}
}
})
}
function createChroot() {
$("#createSubmit").addClass("disabled");
window.onbeforeunload = function () {
return true;
};
bootstrap = $("#createBootstrap").is(":checked");
noUseImage = $("#createNoUseImage").is(":checked");
version = $("createSDKVersion").value;
$.ajax({
url: "/replace-chroot", //can use replace route, uses the same endpoint
type: "POST",
data: JSON.stringify({
bootstrap: bootstrap,
noUseImage: noUseImage,
version: version
}),
dataType: "json",
contentType: "application/json",
success: function (response) {
$("#createSubmit").removeClass("disabled");
location.reload();
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
error: function (xhr) {
console.log("failure");
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"create_chroot",
e.currentTarget.response,
now
)
}
}
})
}
function deleteChroot() {
$("#deleteSubmit").addClass("disabled");
window.onbeforeunload = function () {
return true;
};
$.ajax({
url: "/delete-chroot", //can use replace route, uses the same endpoint
type: "POST",
dataType: "json",
contentType: "application/json",
success: function (response) {
$("#deleteSubmit").removeClass("disabled");
location.reload();
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
error: function (xhr) {
console.log("failure");
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"delete_chroot",
e.currentTarget.response,
now
)
}
}
})
}
function buildPackages() {
$("#buildPackagesSubmit").addClass("disabled");
window.onbeforeunload = function () {
return true;
};
chrootCurrent = $("#buildChrootCurrent").is(":checked");
replace = $("#buildReplace").is(":checked");
toolchainChanged = $("#buildToolchainChanged").is(":checked");
CQPrebuilts = $("#buildCQPrebuilts").is(":checked");
buildTarget = $("#buildBuildTarget").select2("data")[0].text;
compileSource = $("#buildCompileSource").is(":checked");
dryrun = $("#buildDryrun").is(":checked");
workon = $("#buildWorkon").is(":checked");
now = moment();
$.ajax({
url: "/build-packages",
type: "POST",
data: JSON.stringify({
chrootCurrent: chrootCurrent,
replace: replace,
toolchainChanged: toolchainChanged,
CQPrebuilts: CQPrebuilts,
buildTarget: buildTarget,
compileSource: compileSource,
dryrun: dryrun,
workon: workon
}),
contentType: "application/json",
success: function (response) {
$("#buildPackagesSubmit").removeClass("disabled");
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
error: function (xhr) {
console.log("failure");
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"build_packages",
e.currentTarget.response,
now
)
}
}
})
}
function buildSinglePackage() {
$(this).addClass("disabled");
var board = $(this).parents("ul")[0].id;
board = board.substring(0, board.length - 13)
now = moment()
var p = $(this)[0].id;
$.post({
url: "/build-packages",
data: JSON.stringify({
buildTarget: board,
package: p.substring(0,p.length-6),
chrootCurrent: false,
replace: false,
toolchainChanged: false,
CQPrebuilts: false,
compileSource: false,
dryrun: false,
workon: false
}),
contentType: "application/json",
success: function (response) {
$(this).removeClass("disabled");
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"build_packages",
e.currentTarget.response,
now
)
}
}
})
}
function buildImage() {
$("#buildImage").addClass("disabled");
now = moment()
window.onbeforeunload = function () {
return true;
};
$.post({
url: "/build-image",
data: JSON.stringify({
buildTarget: $("#buildImageBuildTarget").select2("data")[0].text,
imageTypes: $("#buildImageImageTypes").select2("data").map((x) => x.text),
disableRootfsVerification: $("#rootfsVerification").is(":checked"),
version: $("#imageVersion").val(),
diskLayout: $("#diskLayout").val(),
builderPath: $("#builderPath").val(),
baseIsRecovery: $("#baseIsRecovery").is(":checked")
}),
contentType: "application/json",
success: function (response) {
$("#buildImage").removeClass("disabled");
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"build_image",
e.currentTarget.response,
now
)
}
}
})
}
function populateChrootInfo() {
$.post({
url: "/chroot-info",
success: (response) => {
if(response.ready){
format = "ddd MMM DD HH:mm:ss YYYY"
$("#chrootCreated").html(moment(response.dateCreated, format).format("MMM D, YYYY"))
$("#chrootLastUpdated").html($("#chrootCreated").html())
$("#chrootPath").html(response.path.path)
$("#chrootVersion").html(response.version.version)
}else{
$("#createChrootModal").modal("show")
}
}
})
}
function customEndpoint(){
now = moment()
window.onbeforeunload = () => {return true;}
$.post({
url: "/custom",
data: JSON.stringify({
endpoint: $("#customEndpoint").select2("data")[0].text,
request: $("#customRequest").val()
}),
contentType: "application/json",
success: function (response) {
window.onbeforeunload = null;
$("#" + now.format("x") + "status").html(`<p2 class="small text-success">Completed</p2>`)
},
xhrFields: {
onprogress: function (e) {
populateLiveLog(
"custom",
e.currentTarget.response,
now
)
}
}
})
}
$(document).ready(function () {
//Show default active panels (logs/packages)
showPackages();
showLogs();
//Fetch repo and workon data
populateRepoFiles();
populatePackages();
populateHistoricLogs()
populateChrootInfo();
//Activates tooltips
const tooltipTriggerList = document.querySelectorAll(
'[data-bs-toggle="tooltip"]'
)
const tooltipList = [...tooltipTriggerList].map(
tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)
)
//Activates select2s (the large dropdowns with search bars)
$(".update-select").select2({
dropdownParent: $("#updateChrootModal"),
theme: 'bootstrap-5',
width: '100%',
placeholder: "Select a board..."
});
$(".add-package-select").select2({
dropdownParent: $("#addPackagesModal"),
theme: 'bootstrap-5'
});
$(".build-packages-select").select2({
dropdownParent: $("#buildPackagesModal"),
theme: 'bootstrap-5',
width: '100%'
});
$(".build-image-select").select2({
dropdownParent: $("#buildImageModal"),
theme: 'bootstrap-5',
width: '100%'
});
$(".custom-select").select2({
dropdownParent: $("#customModal"),
theme: 'bootstrap-5',
width: '100%'
});
//Button listeners
$("#addPackageSubmit").on("click", workonStart);
$("#confirmDelete").on('click', confirmDelete);
$("#showPackages").on('click', showPackages);
$("#showImages").on('click', showImages);
$("#showLogs").on('click', showLogs);
$("#showRepo").on('click', showRepo);
$("#repoStatusRefresh").on("click", populateRepoFiles);
$("#updateChrootSubmit").on("click", updateChroot);
$("#replaceSubmit").on("click", replaceChroot);
$("#buildPackagesSubmit").on("click", buildPackages);
$("#buildImageSubmit").on("click", buildImage);
$("#clearLogs").on("click", clearLogs);
$("#repoSync").on("click", repoSync);
$("#customSubmit").on("click", customEndpoint);
});