ActionExecConditionsView.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <template>
  2. <Section :title="'Execution conditions: ' + actionTitle" :padding="false">
  3. <template #toolbar>
  4. <router-link :to="{ name: 'ActionDetails', params: { actionId: route.params.actionId } }" class="button neutral">
  5. Back to action details
  6. </router-link>
  7. </template>
  8. <div v-if="action" class="padding content">
  9. <p>
  10. These entries mirror the automatic triggers from your OliveTin configuration for this action.
  11. You can always run the action manually as well.
  12. </p>
  13. <h3 class="exec-type-heading">
  14. On demand
  15. <a class="doc-link" :href="execConditionDocs.onDemand" target="_blank" rel="noopener noreferrer">Documentation</a>
  16. </h3>
  17. <p>
  18. Manual execution from the web UI (dashboard or action details), or via the API (for example StartAction),
  19. is always available when your user is allowed to execute the action.
  20. </p>
  21. <template v-if="action.execOnStartup">
  22. <h3 class="exec-type-heading">
  23. <code>execOnStartup</code>
  24. <a class="doc-link" :href="execConditionDocs.startup" target="_blank" rel="noopener noreferrer">Documentation</a>
  25. </h3>
  26. <p>Runs once when OliveTin starts.</p>
  27. </template>
  28. <template v-if="nonEmptyList(action.execOnCron)">
  29. <h3 class="exec-type-heading">
  30. <code>execOnCron</code>
  31. <a class="doc-link" :href="execConditionDocs.cron" target="_blank" rel="noopener noreferrer">Documentation</a>
  32. </h3>
  33. <ul>
  34. <li v-for="(line, idx) in action.execOnCron" :key="'cron-' + idx"><code>{{ line }}</code></li>
  35. </ul>
  36. </template>
  37. <template v-if="nonEmptyList(action.execOnFileCreatedInDir)">
  38. <h3 class="exec-type-heading">
  39. <code>execOnFileCreatedInDir</code>
  40. <a class="doc-link" :href="execConditionDocs.fileCreated" target="_blank" rel="noopener noreferrer">Documentation</a>
  41. </h3>
  42. <ul>
  43. <li v-for="(dir, idx) in action.execOnFileCreatedInDir" :key="'created-' + idx"><code>{{ dir }}</code></li>
  44. </ul>
  45. </template>
  46. <template v-if="nonEmptyList(action.execOnFileChangedInDir)">
  47. <h3 class="exec-type-heading">
  48. <code>execOnFileChangedInDir</code>
  49. <a class="doc-link" :href="execConditionDocs.fileChanged" target="_blank" rel="noopener noreferrer">Documentation</a>
  50. </h3>
  51. <ul>
  52. <li v-for="(dir, idx) in action.execOnFileChangedInDir" :key="'changed-' + idx"><code>{{ dir }}</code></li>
  53. </ul>
  54. </template>
  55. <template v-if="action.execOnCalendarFile">
  56. <h3 class="exec-type-heading">
  57. <code>execOnCalendarFile</code>
  58. <a class="doc-link" :href="execConditionDocs.calendar" target="_blank" rel="noopener noreferrer">Documentation</a>
  59. </h3>
  60. <p><code>{{ action.execOnCalendarFile }}</code></p>
  61. </template>
  62. <template v-if="nonEmptyList(action.execOnWebhooks)">
  63. <h3 class="exec-type-heading">
  64. <code>execOnWebhook</code>
  65. <a class="doc-link" :href="execConditionDocs.webhook" target="_blank" rel="noopener noreferrer">Documentation</a>
  66. </h3>
  67. <ul class="webhook-list">
  68. <li v-for="(wh, idx) in action.execOnWebhooks" :key="'wh-' + idx">
  69. <span v-if="wh.template">template: <code>{{ wh.template }}</code></span>
  70. <span v-if="wh.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span>
  71. <span v-if="nonEmptyObject(wh.matchHeaders)"> · matchHeaders: <code>{{ wh.matchHeaders }}</code></span>
  72. <span v-if="nonEmptyObject(wh.matchQuery)"> · matchQuery: <code>{{ wh.matchQuery }}</code></span>
  73. <span v-if="!webhookHasCondition(wh)">Webhook trigger (no conditions in response)</span>
  74. </li>
  75. </ul>
  76. </template>
  77. <p v-if="!hasConfiguredTriggers" class="muted">
  78. This action has no automatic triggers in configuration besides on-demand execution.
  79. </p>
  80. </div>
  81. <div v-else-if="!loading" class="padding empty-state">
  82. <p>Could not load this action.</p>
  83. <router-link :to="{ name: 'Actions' }">Return to index</router-link>
  84. </div>
  85. </Section>
  86. </template>
  87. <script setup>
  88. import { ref, computed, onMounted, watch } from 'vue'
  89. import { useRoute } from 'vue-router'
  90. import Section from 'picocrank/vue/components/Section.vue'
  91. const route = useRoute()
  92. const action = ref(null)
  93. const actionTitle = ref('Action')
  94. const loading = ref(true)
  95. const execConditionDocs = {
  96. onDemand: 'https://docs.olivetin.app/action_execution/ondemand.html',
  97. startup: 'https://docs.olivetin.app/action_execution/onstartup.html',
  98. cron: 'https://docs.olivetin.app/action_execution/oncron.html',
  99. fileCreated: 'https://docs.olivetin.app/action_execution/onfilecreated.html',
  100. fileChanged: 'https://docs.olivetin.app/action_execution/onfilechanged.html',
  101. calendar: 'https://docs.olivetin.app/action_execution/oncalendar.html',
  102. webhook: 'https://docs.olivetin.app/action_execution/onwebhook.html',
  103. }
  104. function nonEmptyList(list) {
  105. return Array.isArray(list) && list.length > 0
  106. }
  107. function nonEmptyObject(object) {
  108. return object && Object.keys(object).length > 0
  109. }
  110. function webhookHasCondition(webhook) {
  111. return webhook.template || webhook.matchPath || nonEmptyObject(webhook.matchHeaders) || nonEmptyObject(webhook.matchQuery)
  112. }
  113. const hasConfiguredTriggers = computed(() => {
  114. const a = action.value
  115. if (!a) {
  116. return false
  117. }
  118. if (a.execOnStartup) {
  119. return true
  120. }
  121. if (nonEmptyList(a.execOnCron) || nonEmptyList(a.execOnFileCreatedInDir) || nonEmptyList(a.execOnFileChangedInDir)) {
  122. return true
  123. }
  124. if (a.execOnCalendarFile) {
  125. return true
  126. }
  127. if (nonEmptyList(a.execOnWebhooks)) {
  128. return true
  129. }
  130. return false
  131. })
  132. async function fetchAction() {
  133. loading.value = true
  134. try {
  135. const actionId = route.params.actionId
  136. const response = await window.client.getActionBinding({ bindingId: actionId })
  137. action.value = response.action
  138. actionTitle.value = response.action?.title || 'Action'
  139. } catch (err) {
  140. console.error('Failed to fetch action:', err)
  141. window.showBigError('fetch-action-exec-conditions', 'getting action', err, false)
  142. action.value = null
  143. } finally {
  144. loading.value = false
  145. }
  146. }
  147. onMounted(fetchAction)
  148. watch(
  149. () => route.params.actionId,
  150. () => {
  151. action.value = null
  152. actionTitle.value = 'Action'
  153. fetchAction()
  154. }
  155. )
  156. </script>
  157. <style scoped>
  158. .content h3 {
  159. margin-top: 1.25rem;
  160. margin-bottom: 0.35rem;
  161. font-size: 1rem;
  162. }
  163. .exec-type-heading {
  164. display: flex;
  165. flex-wrap: wrap;
  166. align-items: baseline;
  167. gap: 0.35rem 0.75rem;
  168. }
  169. .exec-type-heading .doc-link {
  170. font-size: 0.85rem;
  171. font-weight: normal;
  172. }
  173. .content p,
  174. .content ul {
  175. margin: 0.35rem 0 0;
  176. }
  177. .webhook-list li {
  178. margin-bottom: 0.35rem;
  179. }
  180. .muted {
  181. color: var(--text-secondary);
  182. margin-top: 1.5rem;
  183. }
  184. .empty-state {
  185. text-align: center;
  186. color: var(--text-secondary);
  187. }
  188. .padding {
  189. padding: 1rem;
  190. }
  191. </style>