function init() { refresh_builds(); // initialise tooltips by selector $('body').tooltip({ selector: '[data-bs-toggle="tooltip"]' }); } function refresh_builds() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/builds"); // disable cache, thanks to: https://stackoverflow.com/questions/22356025/force-cache-control-no-cache-in-chrome-via-xmlhttprequest-on-f5-reload xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0"); xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT"); xhr.setRequestHeader("Pragma", "no-cache"); xhr.onload = function () { if (xhr.status === 200) { updateBuildsTable(JSON.parse(xhr.response)); } setTimeout(refresh_builds, 5000); } xhr.send(); } function showFeatures(row_num) { document.getElementById("featureModalBody").innerHTML = document.getElementById(`${row_num}_features_all`).innerHTML; var feature_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('featureModal')); feature_modal.show(); return; } function timeAgo(timestampStr) { const timestamp = parseFloat(timestampStr); const now = Date.now() / 1000; const diff = now - timestamp; if (diff < 0) return "In the future"; const hours = Math.floor(diff / 3600); const minutes = Math.floor((diff % 3600) / 60); return `${hours}h ${minutes}m`; } function updateBuildsTable(builds) { let output_container = document.getElementById('build_table_container'); if (builds.length == 0) { output_container.innerHTML = ``; return; } // hide any tooltips which are currently open // this is needed as they might get stuck // if the element to which they belong goes out of the dom tree $('.tooltip-button').tooltip("hide"); let table_body_html = ''; let row_num = 0; builds.forEach((build_info) => { let status_color = 'primary'; if (build_info['progress']['state'] == 'SUCCESS') { status_color = 'success'; } else if (build_info['progress']['state'] == 'PENDING') { status_color = 'warning'; } else if (build_info['progress']['state'] == 'FAILURE' || build_info['progress']['state'] == 'ERROR') { status_color = 'danger'; } const features_string = build_info['selected_features'].join(', ') const build_age = timeAgo(build_info['time_created']) const isSuccess = build_info['progress']['state'] === 'SUCCESS'; const downloadDisabled = !isSuccess ? 'disabled' : ''; const download_button_color = isSuccess ? 'primary' : 'secondary'; table_body_html += ` ${build_info['progress']['state']} ${build_age} ${build_info['git_hash'].substring(0,8)} ${build_info['board']} ${build_info['vehicle']} ${features_string.substring(0, 100)}... show more
${build_info['progress']['percent']}%
`; row_num += 1; }); let table_html = `${table_body_html}
Status Age Git Hash Board Vehicle Features Progress Actions
`; output_container.innerHTML = table_html; } const LogFetch = (() => { var stopFetch = true; var build_id = null; var scheduled_fetches = 0; function startLogFetch(new_build_id) { build_id = new_build_id; stopFetch = false; if (scheduled_fetches <= 0) { scheduled_fetches = 1; fetchLogFile(); } } function stopLogFetch() { stopFetch = true; } function getBuildId() { return build_id; } function fetchLogFile() { if (stopFetch || !build_id) { scheduled_fetches -= 1; return; } var xhr = new XMLHttpRequest(); xhr.open('GET', `/builds/${build_id}/artifacts/build.log`); // disable cache, thanks to: https://stackoverflow.com/questions/22356025/force-cache-control-no-cache-in-chrome-via-xmlhttprequest-on-f5-reload xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0"); xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT"); xhr.setRequestHeader("Pragma", "no-cache"); xhr.onload = () => { if (xhr.status == 200) { let logTextArea = document.getElementById('logTextArea'); let autoScrollSwitch = document.getElementById('autoScrollSwitch'); logTextArea.textContent = xhr.responseText; if (autoScrollSwitch.checked) { logTextArea.scrollTop = logTextArea.scrollHeight; } if (xhr.responseText.includes('BUILD_FINISHED')) { stopFetch = true; } } if (!stopFetch) { setTimeout(fetchLogFile, 3000); } else { scheduled_fetches -= 1; } } xhr.send(); } return {startLogFetch, stopLogFetch, getBuildId}; })(); function launchLogModal(build_id) { document.getElementById('logTextArea').textContent = `Fetching build log...\nBuild ID: ${build_id}`; LogFetch.startLogFetch(build_id); let logModalElement = document.getElementById('logModal'); logModalElement.addEventListener('hide.bs.modal', () => { LogFetch.stopLogFetch(); }); let logModal = bootstrap.Modal.getOrCreateInstance(logModalElement); logModal.show(); } // Trigger auto-download if state changes from "RUNNING" to "SUCCESS" let previousState = null; let autoDownloadIntervalId = null; async function tryAutoDownload(buildId) { if (!autoDownloadIntervalId) { return; } try { const apiUrl = `/builds/${buildId}` const response = await fetch(apiUrl); const data = await response.json(); const currentState = data.progress?.state; if (previousState === "RUNNING" && currentState === "SUCCESS") { console.log("Build completed successfully. Starting download..."); document.getElementById(`${buildId}-download-btn`).click(); } // Stop running if the build is in a terminal state if (["FAILURE", "SUCCESS", "ERROR"].includes(currentState)) { clearInterval(autoDownloadIntervalId); return; } previousState = currentState; } catch (err) { console.error("Failed to fetch build status:", err); } };