ExecutionDetail.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <template>
  2. <div class="page-container">
  3. <el-card>
  4. <div slot="header">
  5. <span>执行详情</span>
  6. <el-button size="small" style="float:right" @click="$router.push('/orchestration')">返回列表</el-button>
  7. </div>
  8. <el-descriptions v-if="execution" :column="3" border size="medium" style="margin-bottom:20px">
  9. <el-descriptions-item label="执行ID">{{ execution.id }}</el-descriptions-item>
  10. <el-descriptions-item label="流ID">{{ execution.flowId }}</el-descriptions-item>
  11. <el-descriptions-item label="版本">{{ execution.flowVersion }}</el-descriptions-item>
  12. <el-descriptions-item label="状态">
  13. <el-tag :type="statusType(execution.status)">{{ statusLabel(execution.status) }}</el-tag>
  14. </el-descriptions-item>
  15. <el-descriptions-item label="触发来源">{{ triggerLabel(execution.triggerSource) }}</el-descriptions-item>
  16. <el-descriptions-item label="启动时间">{{ formatTime(execution.startedAt) }}</el-descriptions-item>
  17. <el-descriptions-item label="完成时间">{{ formatTime(execution.finishedAt) }}</el-descriptions-item>
  18. <el-descriptions-item label="错误原因">{{ execution.errorReason || '-' }}</el-descriptions-item>
  19. </el-descriptions>
  20. <!-- 手动干预 -->
  21. <div style="margin-bottom:15px">
  22. <el-button type="warning" size="small" v-if="execution && execution.status==='running'" @click="handleTerminate">终止流</el-button>
  23. <el-button type="primary" size="small" v-if="execution && execution.status==='running'" @click="handlePause">暂停</el-button>
  24. <el-button type="success" size="small" v-if="execution && execution.status==='paused'" @click="handleResume">恢复</el-button>
  25. </div>
  26. <!-- 节点树 -->
  27. <div class="adm-table-wrap">
  28. <el-table :data="nodes" border v-loading="loading" row-key="id" default-expand-all height="100%">
  29. <el-table-column prop="nodeId" label="节点ID" width="160"></el-table-column>
  30. <el-table-column prop="status" label="状态" width="120">
  31. <template slot-scope="scope">
  32. <el-tag :type="statusType(scope.row.status)" size="small">{{ statusLabel(scope.row.status) }}</el-tag>
  33. </template>
  34. </el-table-column>
  35. <el-table-column prop="generation" label="循环次数" width="100"></el-table-column>
  36. <el-table-column prop="taskId" label="关联任务ID" width="120">
  37. <template slot-scope="scope">{{ scope.row.taskId || '-' }}</template>
  38. </el-table-column>
  39. <el-table-column prop="conditionMetAt" label="条件达成时间" width="180">
  40. <template slot-scope="scope">{{ formatTime(scope.row.conditionMetAt) }}</template>
  41. </el-table-column>
  42. <el-table-column prop="loopTerminationReason" label="循环终止原因" width="150">
  43. <template slot-scope="scope">{{ scope.row.loopTerminationReason || '-' }}</template>
  44. </el-table-column>
  45. <el-table-column prop="errorReason" label="错误原因" min-width="150">
  46. <template slot-scope="scope">{{ scope.row.errorReason || '-' }}</template>
  47. </el-table-column>
  48. <el-table-column prop="createdAt" label="创建时间" width="170">
  49. <template slot-scope="scope">{{ formatTime(scope.row.createdAt) }}</template>
  50. </el-table-column>
  51. <el-table-column label="操作" width="180" fixed="right">
  52. <template slot-scope="scope">
  53. <el-button size="mini" type="danger" v-if="scope.row.status==='in_progress'||scope.row.status==='pending'" @click="handleNodeFail(scope.row)">标记失败</el-button>
  54. <el-button size="mini" type="warning" v-if="scope.row.status==='failed'||scope.row.status==='timeout'" @click="handleNodeRestart(scope.row)">重启节点</el-button>
  55. <el-button size="mini" type="success" v-if="scope.row.status==='in_progress'||scope.row.status==='pending'" @click="handleConditionMet(scope.row)">条件达成</el-button>
  56. </template>
  57. </el-table-column>
  58. </el-table>
  59. </div>
  60. </el-card>
  61. </div>
  62. </template>
  63. <script>
  64. import { getExecutionDetail, terminateExecution, pauseExecution, resumeExecution, failNode, restartNode, markConditionMet } from '@/api/orchestration'
  65. export default {
  66. name: 'ExecutionDetail',
  67. data() {
  68. return {
  69. executionId: Number(this.$route.params.id),
  70. execution: null,
  71. nodes: [],
  72. loading: false
  73. }
  74. },
  75. created() {
  76. this.loadDetail()
  77. },
  78. methods: {
  79. async loadDetail() {
  80. this.loading = true
  81. try {
  82. const res = await getExecutionDetail(this.executionId)
  83. this.execution = res.data.execution || null
  84. this.nodes = res.data.nodes || []
  85. } catch (e) {
  86. console.error(e)
  87. } finally {
  88. this.loading = false
  89. }
  90. },
  91. async handleTerminate() {
  92. this.$confirm('确认终止整个执行流?运行中的任务不会被删除。', '提示', { type: 'warning' }).then(async () => {
  93. try {
  94. await terminateExecution({ executionId: this.executionId, reason: '管理员手动终止' })
  95. this.$message.success('已终止')
  96. this.loadDetail()
  97. } catch (e) {
  98. this.$message.error(e.message || '操作失败')
  99. }
  100. }).catch(() => {})
  101. },
  102. async handlePause() {
  103. try {
  104. await pauseExecution(this.executionId)
  105. this.$message.success('已暂停')
  106. this.loadDetail()
  107. } catch (e) {
  108. this.$message.error(e.message || '操作失败')
  109. }
  110. },
  111. async handleResume() {
  112. try {
  113. await resumeExecution(this.executionId)
  114. this.$message.success('已恢复')
  115. this.loadDetail()
  116. } catch (e) {
  117. this.$message.error(e.message || '操作失败')
  118. }
  119. },
  120. async handleNodeFail(row) {
  121. try {
  122. await failNode(row.id, '管理员手动标记失败')
  123. this.$message.success('已标记失败')
  124. this.loadDetail()
  125. } catch (e) {
  126. this.$message.error(e.message || '操作失败')
  127. }
  128. },
  129. async handleNodeRestart(row) {
  130. try {
  131. await restartNode(row.id)
  132. this.$message.success('已重启节点')
  133. this.loadDetail()
  134. } catch (e) {
  135. this.$message.error(e.message || '操作失败')
  136. }
  137. },
  138. async handleConditionMet(row) {
  139. try {
  140. await markConditionMet(row.id)
  141. this.$message.success('已通知条件达成')
  142. this.loadDetail()
  143. } catch (e) {
  144. this.$message.error(e.message || '操作失败')
  145. }
  146. },
  147. statusType(s) {
  148. const map = {
  149. running: 'primary', paused: 'warning', terminated: 'danger', completed: 'success',
  150. pending: 'info', in_progress: 'primary', completed: 'success',
  151. failed: 'danger', timeout: 'warning', terminated: 'danger', skipped: 'info'
  152. }
  153. return map[s] || 'info'
  154. },
  155. statusLabel(s) {
  156. const map = {
  157. running: '运行中', paused: '已暂停', terminated: '已终止', completed: '已完成',
  158. pending: '等待中', in_progress: '进行中',
  159. failed: '失败', timeout: '超时', skipped: '跳过'
  160. }
  161. return map[s] || s
  162. },
  163. triggerLabel(s) {
  164. const map = { manual: '手动', cron: '定时', callback: '回调' }
  165. return map[s] || s
  166. },
  167. }
  168. }
  169. </script>
  170. <style scoped>
  171. .page-container { padding: 20px; }
  172. </style>