SharedLogsTable.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <div class="logs-table overflow-x-auto scroll">
  3. <transition-expand>
  4. <div v-if="state.selected.length > 0" class="border-x border-t px-4 py-2">
  5. <ui-button @click="stopSelectedWorkflow"> Stop selected </ui-button>
  6. </div>
  7. </transition-expand>
  8. <table class="w-full">
  9. <tbody class="divide-y dark:divide-gray-800">
  10. <template v-if="running && running[0]?.state">
  11. <tr v-for="item in running" :key="item.id" class="p-2 border">
  12. <td v-if="!hideSelect" class="w-8">
  13. <ui-checkbox
  14. :model-value="state.selected.includes(item.id)"
  15. class="align-text-bottom"
  16. @change="toggleSelectedLog($event, item.id)"
  17. />
  18. </td>
  19. <td class="w-4/12">
  20. <router-link
  21. :to="`/logs/${item.id}/running`"
  22. class="inline-block text-overflow w-full align-middle min-h"
  23. style="min-height: 28px"
  24. >
  25. {{ item.state.name }}
  26. </router-link>
  27. </td>
  28. <td
  29. :title="t('log.duration')"
  30. class="log-time w-2/12 dark:text-gray-200"
  31. >
  32. <v-remixicon name="riTimerLine"></v-remixicon>
  33. <span>{{
  34. countDuration(item.state?.startedTimestamp, Date.now())
  35. }}</span>
  36. </td>
  37. <td title="Executing block" class="text-overflow">
  38. <ui-spinner color="text-accent" size="20" />
  39. <span class="align-middle inline-block ml-3 text-overflow">
  40. {{
  41. getTranslation(
  42. `workflow.blocks.${item.state.currentBlock[0].name}.name`,
  43. item.state.currentBlock[0].name
  44. )
  45. }}
  46. </span>
  47. </td>
  48. <td class="text-right">
  49. <span
  50. class="inline-block py-1 w-16 text-center text-sm rounded-md dark:text-black bg-blue-300"
  51. >
  52. {{ t('common.running') }}
  53. </span>
  54. </td>
  55. <td class="text-right">
  56. <ui-button small class="text-sm" @click="stopWorkflow(item.id)">
  57. {{ t('common.stop') }}
  58. </ui-button>
  59. </td>
  60. </tr>
  61. </template>
  62. <tr v-for="log in logs" :key="log.id" class="hoverable">
  63. <slot name="item-prepend" :log="log" />
  64. <td
  65. class="text-overflow w-4/12"
  66. style="min-width: 140px; max-width: 330px"
  67. >
  68. <router-link
  69. :to="`/logs/${log.id}`"
  70. class="inline-block text-overflow w-full align-middle min-h"
  71. style="min-height: 28px"
  72. >
  73. {{ log.name }}
  74. </router-link>
  75. </td>
  76. <td
  77. class="log-time w-3/12 dark:text-gray-200"
  78. style="min-width: 200px"
  79. >
  80. <v-remixicon
  81. :title="t('log.startedDate')"
  82. name="riCalendarLine"
  83. class="mr-2 inline-block align-middle"
  84. />
  85. <span :title="formatDate(log.startedAt, 'DD MMM YYYY, hh:mm A')">
  86. {{ formatDate(log.startedAt, 'relative') }}
  87. </span>
  88. </td>
  89. <td
  90. :title="t('log.duration')"
  91. class="log-time w-2/12 dark:text-gray-200"
  92. style="min-width: 85px"
  93. >
  94. <v-remixicon name="riTimerLine"></v-remixicon>
  95. <span>{{ countDuration(log.startedAt, log.endedAt) }}</span>
  96. </td>
  97. <td class="text-right">
  98. <span
  99. :class="statusColors[log.status]"
  100. :title="log.status === 'error' ? getErrorMessage(log) : null"
  101. class="inline-block py-1 w-16 text-center text-sm rounded-md dark:text-black"
  102. >
  103. {{ t(`logStatus.${log.status}`) }}
  104. </span>
  105. </td>
  106. <slot name="item-append" :log="log" />
  107. </tr>
  108. <slot name="table:append" />
  109. </tbody>
  110. </table>
  111. </div>
  112. </template>
  113. <script setup>
  114. import { reactive } from 'vue';
  115. import { useI18n } from 'vue-i18n';
  116. import { sendMessage } from '@/utils/message';
  117. import { countDuration } from '@/utils/helper';
  118. import dayjs from '@/lib/dayjs';
  119. defineProps({
  120. logs: {
  121. type: Array,
  122. default: () => [],
  123. },
  124. running: {
  125. type: Array,
  126. default: () => [],
  127. },
  128. hideSelect: Boolean,
  129. });
  130. const { t, te } = useI18n();
  131. const statusColors = {
  132. error: 'bg-red-200 dark:bg-red-300',
  133. success: 'bg-green-200 dark:bg-green-300',
  134. stopped: 'bg-yellow-200 dark:bg-yellow-300',
  135. };
  136. const state = reactive({
  137. selected: [],
  138. });
  139. function getTranslation(key, defText = '') {
  140. return te(key) ? t(key) : defText;
  141. }
  142. function stopWorkflow(stateId) {
  143. sendMessage('workflow:stop', stateId, 'background');
  144. }
  145. function toggleSelectedLog(selected, id) {
  146. if (selected) {
  147. state.selected.push(id);
  148. return;
  149. }
  150. const index = state.selected.indexOf(id);
  151. if (index !== -1) state.selected.splice(index, 1);
  152. }
  153. function formatDate(date, format) {
  154. if (format === 'relative') return dayjs(date).fromNow();
  155. return dayjs(date).format(format);
  156. }
  157. function getErrorMessage({ message }) {
  158. const messagePath = `log.messages.${message}`;
  159. if (message && te(messagePath)) {
  160. return t(messagePath);
  161. }
  162. return '';
  163. }
  164. function stopSelectedWorkflow() {
  165. state.selected.forEach((id) => {
  166. stopWorkflow(id);
  167. });
  168. state.selected = [];
  169. }
  170. </script>
  171. <style scoped>
  172. .log-time svg {
  173. @apply mr-2;
  174. }
  175. .log-time svg,
  176. .log-time span {
  177. display: inline-block;
  178. vertical-align: middle;
  179. }
  180. </style>