refactor(devops): 重构流水线相关代码

- 新增 WebSocket 消息解析工具类 WsMessageUtil
- 添加流水线实例节点明细查询功能
- 优化流水线重试逻辑,支持指定节点重试
- 新增多个流水线节点处理服务类
- 调整流水线模板获取方式
- 优化流水线状态检查逻辑
This commit is contained in:
2025-07-25 23:16:49 +08:00
parent 0497f13b89
commit d9a622e5bd
37 changed files with 535 additions and 206 deletions
@@ -1,118 +1,60 @@
<template>
<div
class="box-card"
:style="{
borderTopWidth: '5px',
borderTopStyle: 'solid',
borderTopColor: statusColorConst[pipelineData.currentNodeStatus ? pipelineData.currentNodeStatus : statusColorConst.pending.code].color
}"
:style="cardStyle"
>
<div>
<div style="float: left;">
<i
v-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.pending.code"
class="el-icon-time"
:style="{
fontSize: '22px',
color: statusColorConst.pending.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.running.code"
class="el-icon-loading"
:style="{
fontSize: '22px',
color: statusColorConst.running.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.success.code"
class="el-icon-success"
:style="{
fontSize: '22px',
color: statusColorConst.success.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.fail.code"
class="el-icon-error"
:style="{
fontSize: '22px',
color: statusColorConst.fail.color
}"
/>
<i
v-else
class="el-icon-time"
:style="{
fontSize: '22px',
color: statusColorConst.pending.color
}"
/>
<i :class="statusIconClass" :style="statusIconStyle" />
</div>
<div class="box-name">{{ pipelineData.name }}</div>
<div class="box-name">{{ currentTemplateData.name }}</div>
<div style="clear: both" />
</div>
<div class="box-current-node">
<a @click="onCurrentNodeClick">
{{ pipelineData.currentNodeMsg ? pipelineData.currentNodeMsg : statusColorConst.pending.label }}
{{ currentNodeDisplayText }}
</a>
</div>
<el-row>
<el-col :span="12" style="text-align: left">
<!-- 流水线状态为fail,且节点支持重试 -->
<div v-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === 'fail' && pipelineData.retry === true">
<!-- 流水线节点状态为fail,且节点支持重试 -->
<div v-if="showRetryButton">
<div class="box-buttons">
{{ '1' }}
<el-button
v-prevent-re-click="5000"
type="text"
size="medium"
:style="{
padding: 0,
margin: '0 10px 0 10px',
color: statusColorConst.running.color
}"
:style="getButtonStyle()"
@click="onNodeRetryClick"
>
重试
</el-button>
</div>
</div>
<!-- 流水线状态为success,且有需要满足条件的操作按钮 -->
<div v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.success.code && pipelineData.buttons && pipelineData.buttons.length > 0">
<!-- 流水线节点状态为success,且有操作按钮 -->
<div v-else-if="showOperationButtons">
<div class="box-buttons">
{{ '2' }}
<el-button
v-for="buttonItem in pipelineData.buttons"
v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service"
size="medium"
type="text"
:style="{
padding: 0,
margin: '0 10px 0 10px',
color: ['apply','agree','ok','success'].includes(buttonItem.code) ? statusColorConst.running.color : statusColorConst.fail.color,
display: ['success', 'fail'].includes(buttonItem.code) && pipelineData.status === buttonItem.code ? '' : 'none'
}"
:style="getButtonStyle(buttonItem)"
@click="onNodeOperationClick(buttonItem)"
>
{{ buttonItem.title }}
</el-button>
</div>
</div>
<!-- 流水线状态非pending,且有操作按钮 -->
<div v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus !== statusColorConst.pending.code && pipelineData.buttons && pipelineData.buttons.length > 0">
<!-- 流水线节点状态为running,且有操作按钮 -->
<div v-else-if="showOperationButtons">
<div class="box-buttons">
{{ '3' }}
<el-button
v-for="buttonItem in pipelineData.buttons"
v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service"
size="medium"
type="text"
:style="{
padding: 0,
margin: '0 10px 0 10px',
color: ['apply','agree','ok','success'].includes(buttonItem.code) ? statusColorConst.running.color : statusColorConst.fail.color
}"
:style="getButtonStyle(buttonItem)"
@click="onNodeOperationClick(buttonItem)"
>
{{ buttonItem.title }}
@@ -131,10 +73,27 @@
</el-col>
<div style="clear: both" />
</el-row>
<cute-preview-drawer ref="detailDrawer" :title="pipelineData.name">
<div style="padding: 20px">
这里是明细
</div>
<cute-preview-drawer ref="detailDrawer" :title="currentTemplateData.name">
<el-timeline>
<el-timeline-item timestamp="2018/4/12" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/12 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/3" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/3 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/2" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/2 20:46</p>
</el-card>
</el-timeline-item>
</el-timeline>
</cute-preview-drawer>
</div>
</template>
@@ -142,7 +101,9 @@
<script>
import dayjs from 'dayjs'
import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer'
import { mapGetters } from 'vuex'
import CsMessage from '@/utils/elementui/CsMessage'
import { retryPipeline } from '@/api/devops/pipelineInstance'
export default {
name: 'CutePipelineNode',
components: { CutePreviewDrawer },
@@ -153,34 +114,137 @@ export default {
default: function() {
return {}
}
},
instanceData: {
type: Object,
required: false,
default: function() {
return {}
}
}
},
data() {
return {
statusColorConst: {
pending: { code: 'pending', label: '未开始', color: '#C0C4CC' },
running: { code: 'running', label: '运行中', color: '#409EFF' },
success: { code: 'success', label: '执行成功', color: '#67C23A' },
fail: { code: 'fail', label: '执行失败', color: '#F56C6C' }
},
executeTimeStr: '',
pipelineData: {}
currentTemplateData: {},
currentInstanceData: {}
}
},
// 动态计算
computed: {
...mapGetters([
'statusConst'
]),
cardStyle() {
const statusKey = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[statusKey] || this.statusConst.pending
return {
borderTop: `5px solid ${statusConfig.color}`
}
},
statusIconClass() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
return this.statusConst[status].icon || 'el-icon-time'
},
statusIconStyle() {
return {
fontSize: '22px',
color: this.getStatusColor()
}
},
currentNodeDisplayText() {
return this.currentTemplateData.currentNodeMsg || this.statusConst.pending.label
},
showRetryButton() {
return this.currentTemplateData.currentNodeStatus === this.statusConst.fail.code &&
this.currentTemplateData.retry === true
},
showOperationButtons() {
const status = this.currentTemplateData.currentNodeStatus
const hasButtons = this.currentTemplateData.buttons && this.currentTemplateData.buttons.length > 0
return hasButtons && (
status === this.statusConst.success.code ||
status === this.statusConst.running.code
)
},
executeTimeStr() {
return this.renderDateTimeStr(this.currentTemplateData)
}
},
watch: {
templateData: {
handler(newVal, oldVal) {
this.pipelineData = { ...newVal }
this.executeTimeStr = this.renderDateTimeStr(newVal)
this.currentTemplateData = { ...newVal }
},
deep: true
},
instanceData: {
handler(newVal, oldVal) {
this.currentInstanceData = { ...newVal }
},
deep: true
}
},
mounted() {
this.pipelineData = this.templateData
this.executeTimeStr = this.renderDateTimeStr(this.templateData)
// 初始化时,设置流水线模板
this.currentTemplateData = this.templateData
this.currentInstanceData = this.instanceData
},
methods: {
getStatusColor() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[status] || this.statusConst.pending
return statusConfig.color
},
getButtonStyle(buttonItem) {
if (buttonItem) {
const isPositiveAction = ['apply', 'agree', 'ok', 'success'].includes(buttonItem.code)
const status = this.currentTemplateData.currentNodeStatus
const color = isPositiveAction
? this.statusConst.running.color
: this.statusConst.fail.color
let display = ''
if (status === this.statusConst.running.code) {
display = this.getRunningNodeButtonVisibleStatus(buttonItem)
} else if (status === this.statusConst.success.code) {
display = this.getSuccessNodeButtonVisibleStatus(buttonItem)
}
return {
padding: 0,
margin: '0 10px 0 10px',
color,
display
}
}
return {
padding: 0,
margin: '0 10px 0 10px',
color: this.statusConst.running.color
}
},
/**
* 运行中节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getRunningNodeButtonVisibleStatus(buttonItem) {
if (buttonItem.type === 'qrCodeDialog') {
return 'none'
}
return ''
},
/**
* 成功节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getSuccessNodeButtonVisibleStatus(buttonItem) {
const that = this
// 根据按钮编码
if (buttonItem.code === that.currentTemplateData.currentNodeStatus) {
return ''
}
return 'none'
},
renderDateTimeStr(newVal) {
let executeTimeStr = ''
switch (newVal.status) {
@@ -190,7 +254,7 @@ export default {
case 'running':
case 'success':
case 'fail':
if (newVal.currentNodeStatus !== this.statusColorConst.pending.code) {
if (newVal.currentNodeStatus !== this.statusConst.pending.code) {
if (newVal.startTime && newVal.finishTime) {
executeTimeStr = this.formatTimeDifference(newVal.startTime, newVal.finishTime)
return executeTimeStr
@@ -240,21 +304,27 @@ export default {
return `${diffInMilliseconds} 毫秒`
},
onCurrentNodeClick() {
const pipelineData = this.pipelineData || { click: false }
if (!pipelineData.click) {
console.warn('当前流水线节点不支持查看明细')
const currentTemplateData = this.currentTemplateData || { click: false }
if (!currentTemplateData.click) {
CsMessage.Warning('当前流水线节点不支持查看明细')
return
}
console.error('templateData', this.templateData)
this.$refs.detailDrawer.show()
},
onNodeRetryClick() {
const pipelineData = this.pipelineData || { retry: false }
if (!pipelineData.retry) {
console.warn('当前流水线节点不支持重试')
const currentTemplateData = this.currentTemplateData || { retry: false }
if (!currentTemplateData.retry) {
CsMessage.Warning('当前流水线节点不支持重试')
return
}
console.error('pipelineData', this.pipelineData)
const data = {
...this.currentInstanceData,
code: this.currentTemplateData.code
}
// console.error('data', data)
retryPipeline(data)
this.$emit('retry', data)
},
onNodeOperationClick(buttonInfo) {
console.error('buttonInfo', buttonInfo)