ActionExecConditionsView.vue 7.6 KB

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