ActionButton.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { marshalLogsJsonToHtml } from './marshaller.js'
  2. import './ArgumentForm.js'
  3. class ActionButton extends window.HTMLButtonElement {
  4. constructFromJson (json) {
  5. this.updateIterationTimestamp = 0
  6. this.title = json.title
  7. this.temporaryStatusMessage = null
  8. this.isWaiting = false
  9. this.actionCallUrl = window.restBaseUrl + 'StartAction'
  10. this.updateFromJson(json)
  11. this.onclick = () => {
  12. if (json.arguments.length > 0) {
  13. const frm = document.createElement('form', { is: 'argument-form' })
  14. frm.setup(json, (args) => {
  15. this.startAction(args)
  16. })
  17. document.body.appendChild(frm)
  18. } else {
  19. this.startAction()
  20. }
  21. }
  22. this.constructTemplate()
  23. this.updateHtml()
  24. this.setAttribute('id', 'actionButton_' + json.id)
  25. }
  26. updateFromJson (json) {
  27. // Fields that should not be updated
  28. //
  29. // title - as the callback URL relies on it
  30. // actionCallbackUrl - as it's based on the title
  31. // temporaryStatusMessage - as the button might be "waiting" on execution to finish while it's being updated.
  32. if (json.icon === '') {
  33. this.unicodeIcon = '&#x1f4a9'
  34. } else {
  35. this.unicodeIcon = unescape(json.icon)
  36. }
  37. }
  38. startAction (actionArgs) {
  39. this.disabled = true
  40. this.isWaiting = true
  41. this.updateHtml()
  42. this.classList = [] // Removes old animation classes
  43. if (actionArgs === undefined) {
  44. actionArgs = []
  45. }
  46. const startActionArgs = {
  47. actionName: this.title,
  48. arguments: actionArgs
  49. }
  50. window.fetch(this.actionCallUrl, {
  51. method: 'POST',
  52. headers: {
  53. 'Content-Type': 'application/json'
  54. },
  55. body: JSON.stringify(startActionArgs)
  56. }).then((res) => {
  57. if (res.ok) {
  58. return res.json()
  59. } else {
  60. throw new Error(res.statusText)
  61. }
  62. }
  63. ).then((json) => {
  64. marshalLogsJsonToHtml({ logs: [json.logEntry] })
  65. if (json.logEntry.timedOut) {
  66. this.onActionResult('actionTimeout', 'Timed out')
  67. } else if (json.logEntry.exitCode === -1337) {
  68. this.onActionError('Error')
  69. } else if (json.logEntry.exitCode !== 0) {
  70. this.onActionResult('actionNonZeroExit', 'Exit code ' + json.logEntry.exitCode)
  71. } else {
  72. this.onActionResult('actionSuccess', 'Success!')
  73. }
  74. }).catch(err => {
  75. this.onActionError(err)
  76. })
  77. }
  78. onActionResult (cssClass, temporaryStatusMessage) {
  79. this.temporaryStatusMessage = '[ ' + temporaryStatusMessage + ' ]'
  80. this.updateHtml()
  81. this.classList.add(cssClass)
  82. setTimeout(() => {
  83. this.classList.remove(cssClass)
  84. }, 1000)
  85. }
  86. onActionError (err) {
  87. console.log('callback error', err)
  88. this.disabled = false
  89. this.isWaiting = false
  90. this.updateHtml()
  91. this.classList.add('actionFailed')
  92. setTimeout(() => {
  93. this.classList.remove('actionFailed')
  94. }, 1000)
  95. }
  96. constructTemplate () {
  97. const tpl = document.getElementById('tplActionButton')
  98. const content = tpl.content.cloneNode(true)
  99. /*
  100. * FIXME: Should probably be using a shadowdom here, but seem to
  101. * get an error when combined with custom elements.
  102. */
  103. this.appendChild(content)
  104. this.domTitle = this.querySelector('.title')
  105. this.domIcon = this.querySelector('.icon')
  106. }
  107. updateHtml () {
  108. if (this.temporaryStatusMessage != null) {
  109. this.domTitle.innerText = this.temporaryStatusMessage
  110. this.domTitle.classList.add('temporaryStatusMessage')
  111. this.isWaiting = false
  112. this.disabled = false
  113. setTimeout(() => {
  114. this.temporaryStatusMessage = null
  115. this.domTitle.classList.remove('temporaryStatusMessage')
  116. this.updateHtml()
  117. }, 2000)
  118. } else if (this.isWaiting) {
  119. this.domTitle.innerText = 'Waiting...'
  120. } else {
  121. this.domTitle.innerText = this.title
  122. }
  123. this.domIcon.innerHTML = this.unicodeIcon
  124. }
  125. }
  126. window.customElements.define('action-button', ActionButton, { extends: 'button' })