refactor(devops): 重构流水线相关代码
- 新增 WebSocket 消息解析工具类 WsMessageUtil - 添加流水线实例节点明细查询功能 - 优化流水线重试逻辑,支持指定节点重试 - 新增多个流水线节点处理服务类 - 调整流水线模板获取方式 - 优化流水线状态检查逻辑
This commit is contained in:
@@ -8,6 +8,14 @@ export function startPipeline(data) {
|
||||
})
|
||||
}
|
||||
|
||||
export function retryPipeline(data) {
|
||||
return request({
|
||||
url: 'api/devops/pipelineInstance/retry',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function restartPipeline(data) {
|
||||
return request({
|
||||
url: 'api/devops/pipelineInstance/restart',
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* 在modules中定义后,需要到这注册
|
||||
* @type {any}
|
||||
*/
|
||||
const getters = {
|
||||
deployUploadApi: state => state.api.deployUploadApi,
|
||||
databaseUploadApi: state => state.api.databaseUploadApi,
|
||||
@@ -19,6 +23,7 @@ const getters = {
|
||||
druidSqlApi: state => state.api.druidSqlApi,
|
||||
swaggerApi: state => state.api.swaggerApi,
|
||||
websocketApi: state => state.api.websocketApi,
|
||||
sidebarRouters: state => state.permission.sidebarRouters
|
||||
sidebarRouters: state => state.permission.sidebarRouters,
|
||||
statusConst: state => state.pipeline.statusConst
|
||||
}
|
||||
export default getters
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const pipeline = {
|
||||
/**
|
||||
* 全局常量
|
||||
*/
|
||||
state: {
|
||||
statusConst: {
|
||||
pending: { code: 'pending', label: '未开始', color: '#C0C4CC', icon: 'el-icon-time' },
|
||||
running: { code: 'running', label: '运行中', color: '#409EFF', icon: 'el-icon-loading' },
|
||||
success: { code: 'success', label: '执行成功', color: '#67C23A', icon: 'el-icon-success' },
|
||||
fail: { code: 'fail', label: '执行失败', color: '#F56C6C', icon: 'el-icon-error' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default pipeline
|
||||
@@ -30,7 +30,9 @@ service.interceptors.request.use(
|
||||
// response 拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
return response.data
|
||||
const data = response.data
|
||||
// console.error('data', data)
|
||||
return data
|
||||
},
|
||||
error => {
|
||||
// 兼容blob下载出错json提示
|
||||
@@ -57,7 +59,7 @@ service.interceptors.response.use(
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
console.log(code)
|
||||
// console.log(code)
|
||||
if (code) {
|
||||
if (code === 401) {
|
||||
store.dispatch('LogOut').then(() => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
v-for="(template, index) in dynamicInstance.nodes"
|
||||
:key="template.code"
|
||||
:template-data.sync="dynamicInstance.nodes[index]"
|
||||
:instance-data.sync="dynamicInstance"
|
||||
@retry="onPipelineRetrySuccess"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="box-pipeline-content">
|
||||
@@ -24,6 +26,7 @@
|
||||
v-for="(template, index) in dynamicTemplateList"
|
||||
:key="template.code"
|
||||
:template-data.sync="dynamicTemplateList[index]"
|
||||
:instance-data.sync="dynamicInstance"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -462,7 +465,7 @@ export default {
|
||||
dynamicStartButtonLoading: false,
|
||||
// 当前模板
|
||||
dynamicTemplate: {
|
||||
id: 6
|
||||
id: 21
|
||||
},
|
||||
// 当前流水线节点模板
|
||||
dynamicTemplateList: [],
|
||||
@@ -553,6 +556,7 @@ export default {
|
||||
// 所有节点执行成功
|
||||
if (that.dynamicWsClient) {
|
||||
that.dynamicWsClient.close()
|
||||
sessionStorage.removeItem('pipelineInstanceId')
|
||||
}
|
||||
that.dynamicStartupStatus = that.dynamicStartupStatusMap.start.code
|
||||
} else {
|
||||
@@ -597,7 +601,7 @@ export default {
|
||||
* 启动流水线
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async startPipelineTest(data) {
|
||||
async startPipelineTest() {
|
||||
const that = this
|
||||
const args = {
|
||||
id: that.dynamicTemplate.id
|
||||
@@ -625,6 +629,10 @@ export default {
|
||||
console.error('error', e)
|
||||
that.dynamicStartButtonLoading = false
|
||||
}
|
||||
},
|
||||
onPipelineRetrySuccess(data) {
|
||||
sessionStorage.setItem('pipelineInstanceId', data.instanceId)
|
||||
this.connectWebSocketServer(data.instanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user