ExecutionDialog.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { ActionStatusDisplay } from './ActionStatusDisplay.js'
  2. import { OutputTerminal } from './OutputTerminal.js'
  3. // This ExecutionDialog is NOT a custom HTML element, but rather just picks up
  4. // the <dialog /> element out of index.html and just re-uses that - as only
  5. // one dialog can be shown at a time.
  6. export class ExecutionDialog {
  7. constructor () {
  8. this.dlg = document.querySelector('dialog#execution-results-popup')
  9. this.domIcon = document.getElementById('execution-dialog-icon')
  10. this.domTitle = document.getElementById('execution-dialog-title')
  11. this.domOutput = document.getElementById('execution-dialog-xterm')
  12. this.domOutputHtml = document.getElementById('execution-dialog-output-html')
  13. this.domOutputToggleBig = document.getElementById('execution-dialog-toggle-size')
  14. this.domOutputToggleBig.onclick = () => {
  15. this.toggleSize()
  16. }
  17. this.domBtnRerun = document.getElementById('execution-dialog-rerun-action')
  18. this.domBtnKill = document.getElementById('execution-dialog-kill-action')
  19. this.domDuration = document.getElementById('execution-dialog-duration')
  20. this.domStatus = new ActionStatusDisplay(document.getElementById('execution-dialog-status'))
  21. this.domExecutionBasics = document.getElementById('execution-dialog-basics')
  22. this.domExecutionDetails = document.getElementById('execution-dialog-details')
  23. window.terminal = new OutputTerminal()
  24. window.terminal.open(this.domOutput)
  25. }
  26. showOutput () {
  27. this.domOutput.hidden = false
  28. this.domOutput.hidden = false
  29. }
  30. toggleSize () {
  31. if (this.dlg.classList.contains('big')) {
  32. this.dlg.classList.remove('big')
  33. } else {
  34. this.dlg.classList.add('big')
  35. }
  36. window.terminal.fit()
  37. }
  38. reset () {
  39. this.executionSeconds = 0
  40. this.executionTrackingId = 'notset'
  41. this.dlg.classList.remove('big')
  42. this.domOutputToggleBig.hidden = false
  43. this.domIcon.innerText = ''
  44. this.domTitle.innerText = 'Waiting for result... '
  45. this.domDuration.innerText = ''
  46. // window.terminal.close()
  47. this.domBtnRerun.disabled = true
  48. this.domBtnRerun.onclick = () => {}
  49. this.domBtnKill.disabled = true
  50. this.domBtnKill.onclick = () => {}
  51. this.hideDetailsOnResult = false
  52. this.domExecutionBasics.hidden = false
  53. this.domExecutionDetails.hidden = true
  54. window.terminal.reset()
  55. window.terminal.fit()
  56. }
  57. show (actionButton) {
  58. if (typeof actionButton !== 'undefined' && actionButton != null) {
  59. this.domIcon.innerText = actionButton.domIcon.innerText
  60. }
  61. this.domBtnKill.disabled = false
  62. this.domBtnKill.onclick = () => {
  63. this.killAction()
  64. }
  65. clearInterval(window.executionDialogTicker)
  66. this.executionSeconds = 0
  67. this.executionTick()
  68. window.executionDialogTicker = setInterval(() => {
  69. this.executionTick()
  70. }, 1000)
  71. if (this.dlg.open) {
  72. this.dlg.close()
  73. }
  74. this.dlg.showModal()
  75. }
  76. rerunAction (actionId) {
  77. const actionButton = document.getElementById('actionButton-' + actionId)
  78. if (actionButton !== undefined) {
  79. actionButton.btn.click()
  80. }
  81. this.dlg.close()
  82. }
  83. killAction () {
  84. const killActionArgs = {
  85. executionTrackingId: this.executionTrackingId
  86. }
  87. window.fetch(window.restBaseUrl + 'KillAction', {
  88. cors: 'cors',
  89. method: 'POST',
  90. headers: {
  91. 'Content-Type': 'application/json'
  92. },
  93. body: JSON.stringify(killActionArgs)
  94. }).then((res) => {
  95. return res.json() // This isn't used by anything. UI is updated by OnExecutionFinished like normal.
  96. }).catch(err => {
  97. throw err
  98. })
  99. }
  100. executionTick () {
  101. this.executionSeconds++
  102. this.updateDuration(null)
  103. }
  104. hideEverythingApartFromOutput () {
  105. this.hideDetailsOnResult = true
  106. this.domExecutionBasics.hidden = true
  107. }
  108. fetchExecutionResult (executionTrackingId) {
  109. this.executionTrackingId = executionTrackingId
  110. const executionStatusArgs = {
  111. executionTrackingId: this.executionTrackingId
  112. }
  113. window.fetch(window.restBaseUrl + 'ExecutionStatus', {
  114. cors: 'cors',
  115. method: 'POST',
  116. headers: {
  117. 'Content-Type': 'application/json'
  118. },
  119. body: JSON.stringify(executionStatusArgs)
  120. }).then((res) => {
  121. if (res.ok) {
  122. return res.json()
  123. } else if (res.status === 404) {
  124. throw new Error('Execution not found: ' + executionTrackingId)
  125. } else {
  126. throw new Error(res.statusText)
  127. }
  128. }
  129. ).then((json) => {
  130. this.renderExecutionResult(json)
  131. }).catch(err => {
  132. console.log(err)
  133. this.renderError(err)
  134. })
  135. }
  136. updateDuration (logEntry) {
  137. if (logEntry == null) {
  138. this.domDuration.innerHTML = this.executionSeconds + ' seconds'
  139. } else if (!logEntry.executionStarted) {
  140. this.domDuration.innerHTML = logEntry.datetimeStarted + ' (request time). Not executed.'
  141. } else if (logEntry.executionStarted && !logEntry.executionFinished) {
  142. this.domDuration.innerHTML = logEntry.datetimeStarted
  143. } else {
  144. let delta = ''
  145. try {
  146. delta = (new Date(logEntry.datetimeStarted) - new Date(logEntry.datetimeStarted)) / 1000
  147. delta = new Intl.RelativeTimeFormat().format(delta, 'seconds').replace('in ', '').replace('ago', '')
  148. } catch (e) {
  149. console.warn('Failed to calculate delta', e)
  150. }
  151. this.domDuration.innerHTML = logEntry.datetimeStarted + ' &rarr; ' + logEntry.datetimeFinished
  152. if (delta !== '') {
  153. this.domDuration.innerHTML += ' (' + delta + ')'
  154. }
  155. }
  156. }
  157. renderExecutionResult (res) {
  158. this.res = res
  159. clearInterval(window.executionDialogTicker)
  160. if ("type" in res && res.type == "execution-dialog-output-html") {
  161. this.domOutputHtml.hidden = false
  162. this.domOutput.hidden = true
  163. this.domOutputHtml.innerHTML = res.logEntry.output
  164. this.domOutputHtml.hidden = false
  165. this.hideDetailsonResult = true
  166. } else {
  167. this.domOutput.hidden = false
  168. this.domOutputHtml.innerHTML = ''
  169. this.domOutputHtml.hidden = true
  170. }
  171. if (this.hideDetailsOnResult) {
  172. this.domExecutionDetails.hidden = true
  173. }
  174. this.executionTrackingId = res.logEntry.executionTrackingId
  175. this.domBtnRerun.disabled = !res.logEntry.executionFinished
  176. this.domBtnRerun.onclick = () => { this.rerunAction(res.logEntry.actionId) }
  177. this.domBtnKill.disabled = res.logEntry.executionFinished
  178. this.domStatus.update(res.logEntry)
  179. this.domIcon.innerHTML = res.logEntry.actionIcon
  180. this.domTitle.innerText = res.logEntry.actionTitle
  181. this.domTitle.title = 'Action ID: ' + res.logEntry.actionId + '\nExecution ID: ' + res.logEntry.executionTrackingId
  182. this.updateDuration(res.logEntry)
  183. window.terminal.reset()
  184. window.terminal.write(res.logEntry.output, () => {
  185. window.terminal.fit()
  186. })
  187. }
  188. renderError (err) {
  189. window.showBigError('execution-dlg-err', 'in the execution dialog', 'Failed to fetch execution result. ' + err, false)
  190. }
  191. }