index.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. function init() {
  2. refresh_builds();
  3. // initialise tooltips by selector
  4. $('body').tooltip({
  5. selector: '[data-bs-toggle="tooltip"]'
  6. });
  7. }
  8. function refresh_builds() {
  9. var xhr = new XMLHttpRequest();
  10. xhr.open('GET', "/builds/status.json");
  11. // disable cache, thanks to: https://stackoverflow.com/questions/22356025/force-cache-control-no-cache-in-chrome-via-xmlhttprequest-on-f5-reload
  12. xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
  13. xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT");
  14. xhr.setRequestHeader("Pragma", "no-cache");
  15. xhr.onload = function () {
  16. if (xhr.status === 200) {
  17. updateBuildsTable(JSON.parse(xhr.response));
  18. }
  19. setTimeout(refresh_builds, 5000);
  20. }
  21. xhr.send();
  22. }
  23. function showFeatures(row_num) {
  24. document.getElementById("featureModalBody").innerHTML = document.getElementById(`${row_num}_features_all`).innerHTML;
  25. var feature_modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('featureModal'));
  26. feature_modal.show();
  27. return;
  28. }
  29. function updateBuildsTable(status_json) {
  30. let output_container = document.getElementById('build_table_container');
  31. if (Object.keys(status_json).length == 0) {
  32. output_container.innerHTML = `<div class="alert alert-success" role="alert" id="welcome_alert">
  33. <h4 class="alert-heading">Welcome!</h4>
  34. <p>No builds were queued to run on the server recently. To queue one, please click <a href="./add_build" class="alert-link">add a build</a>.</p>
  35. </div>`;
  36. return;
  37. }
  38. // hide any tooltips which are currently open
  39. // this is needed as they might get stuck
  40. // if the element to which they belong goes out of the dom tree
  41. $('.tooltip-button').tooltip("hide");
  42. let table_body_html = '';
  43. let row_num = 0;
  44. Object.keys(status_json).forEach((build_id) => {
  45. let build_info = status_json[build_id];
  46. let status_color = 'primary';
  47. if (build_info['status'] == 'Finished') {
  48. status_color = 'success';
  49. } else if (build_info['status'] == 'Pending') {
  50. status_color = 'warning';
  51. } else if (build_info['status'] == 'Failed' || build_info['status'] == 'Error') {
  52. status_color = 'danger';
  53. }
  54. table_body_html += `<tr>
  55. <td class="align-middle"><span class="badge text-bg-${status_color}">${build_info['status']}</span></td>
  56. <td class="align-middle">${build_info['age']}</td>
  57. <td class="align-middle"><a href="https://github.com/ArduPilot/ardupilot/commit/${build_info['git_hash_short']}">${build_info['git_hash_short']}</a></td>
  58. <td class="align-middle">${build_info['board']}</td>
  59. <td class="align-middle">${build_info['vehicle']}</td>
  60. <td class="align-middle" id="${row_num}_features">
  61. ${build_info['features'].substring(0, 100)}...
  62. <span id="${row_num}_features_all" style="display:none;">${build_info['features']}</span>
  63. <a href="javascript: showFeatures(${row_num});">show more</a>
  64. </td>
  65. <td class="align-middle">
  66. <div class="progress border" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
  67. <div class="progress-bar bg-${status_color}" style="width: ${build_info['progress']}%">${build_info['progress']}%</div>
  68. </div>
  69. </td>
  70. <td class="align-middle">
  71. <button class="btn btn-md btn-outline-primary m-1 tooltip-button" data-bs-toggle="tooltip" data-bs-animation="false" data-bs-title="View log" onclick="launchLogModal('${build_id}');">
  72. <i class="bi bi-file-text"></i>
  73. </button>
  74. <button class="btn btn-md btn-outline-primary m-1 tooltip-button" data-bs-toggle="tooltip" data-bs-animation="false" data-bs-title="Open build directory" onclick="window.location.href = '/builds/${build_id}';">
  75. <i class="bi bi-folder2-open"></i>
  76. </button>
  77. </td>
  78. </tr>`;
  79. row_num += 1;
  80. });
  81. let table_html = `<table class="table table-hover table-light shadow">
  82. <thead class="table-dark">
  83. <th scope="col" style="width: 5%">Status</th>
  84. <th scope="col" style="width: 5%">Age (hr:min)</th>
  85. <th scope="col" style="width: 5%">Git Hash</th>
  86. <th scope="col" style="width: 5%">Board</th>
  87. <th scope="col" style="width: 5%">Vehicle</th>
  88. <th scope="col">Features</th>
  89. <th scope="col" style="width: 15%">Progress</th>
  90. <th scope="col" style="width: 15%">Actions</th>
  91. </thead>
  92. <tbody>${table_body_html}</tbody>
  93. </table>`;
  94. output_container.innerHTML = table_html;
  95. }
  96. const LogFetch = (() => {
  97. var stopFetch = true;
  98. var build_id = null;
  99. var scheduled_fetches = 0;
  100. function startLogFetch(new_build_id) {
  101. build_id = new_build_id;
  102. stopFetch = false;
  103. if (scheduled_fetches <= 0) {
  104. scheduled_fetches = 1;
  105. fetchLogFile();
  106. }
  107. }
  108. function stopLogFetch() {
  109. stopFetch = true;
  110. }
  111. function getBuildId() {
  112. return build_id;
  113. }
  114. function fetchLogFile() {
  115. if (stopFetch || !build_id) {
  116. scheduled_fetches -= 1;
  117. return;
  118. }
  119. var xhr = new XMLHttpRequest();
  120. xhr.open('GET', `/builds/${build_id}/build.log`);
  121. // disable cache, thanks to: https://stackoverflow.com/questions/22356025/force-cache-control-no-cache-in-chrome-via-xmlhttprequest-on-f5-reload
  122. xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
  123. xhr.setRequestHeader("Expires", "Tue, 01 Jan 1980 1:00:00 GMT");
  124. xhr.setRequestHeader("Pragma", "no-cache");
  125. xhr.onload = () => {
  126. if (xhr.status == 200) {
  127. let logTextArea = document.getElementById('logTextArea');
  128. let autoScrollSwitch = document.getElementById('autoScrollSwitch');
  129. logTextArea.textContent = xhr.responseText;
  130. if (autoScrollSwitch.checked) {
  131. logTextArea.scrollTop = logTextArea.scrollHeight;
  132. }
  133. if (xhr.responseText.includes('BUILD_FINISHED')) {
  134. stopFetch = true;
  135. }
  136. }
  137. if (!stopFetch) {
  138. setTimeout(fetchLogFile, 3000);
  139. } else {
  140. scheduled_fetches -= 1;
  141. }
  142. }
  143. xhr.send();
  144. }
  145. return {startLogFetch, stopLogFetch, getBuildId};
  146. })();
  147. function launchLogModal(build_id) {
  148. document.getElementById('logTextArea').textContent = `Fetching build log...\nBuild ID: ${build_id}`;
  149. LogFetch.startLogFetch(build_id);
  150. let logModalElement = document.getElementById('logModal');
  151. logModalElement.addEventListener('hide.bs.modal', () => {
  152. LogFetch.stopLogFetch();
  153. });
  154. let logModal = bootstrap.Modal.getOrCreateInstance(logModalElement);
  155. logModal.show();
  156. }