ActionExecConditionsView.vue 7.7 KB

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