Files
graduate-design/orion-ops-vue/src/components/app/AppPipelineExecModal.vue
2026-03-06 14:01:32 +08:00

416 lines
12 KiB
Vue

<template>
<a-modal v-model="visible"
:width="550"
:maskStyle="{opacity: 0.8, animation: 'none'}"
:dialogStyle="{top: '64px', padding: 0}"
:bodyStyle="{padding: '8px'}"
:maskClosable="false"
:destroyOnClose="true">
<!-- 页头 -->
<template #title>
<span v-if="selectPipelinePage">选择流水线</span>
<span v-if="!selectPipelinePage">
<a-icon class="mx4 pointer span-blue"
title="重新选择"
v-if="visibleReselect && id"
@click="reselectPipelineList"
type="arrow-left"/>
执行流水线
</span>
</template>
<!-- 初始化骨架 -->
<a-skeleton v-if="initiating" active :paragraph="{rows: 8}"/>
<!-- 主体 -->
<a-spin v-else :spinning="loading">
<!-- 流水线选择 -->
<div class="pipeline-list-container" v-if="selectPipelinePage">
<!-- 无流水线数据 -->
<a-empty v-if="!pipelineList.length" description="请先配置应用流水线"/>
<!-- 流水线列表 -->
<div class="pipeline-list">
<div class="pipeline-item" v-for="pipeline of pipelineList"
:key="pipeline.id"
@click="choosePipeline(pipeline.id)">
<div class="pipeline-name">
<a-icon class="mx8" type="code-sandbox"/>
{{ pipeline.name }}
</div>
</div>
</div>
</div>
<!-- 执行配置 -->
<div class="pipeline-container" v-else>
<!-- 执行参数 -->
<a-form-model v-bind="layout">
<!-- 执行标题 -->
<a-form-model-item class="exec-form-item" label="执行标题" required>
<a-input class="name-input" v-model="submit.title" :maxLength="32" allowClear/>
</a-form-model-item>
<!-- 执行类型 -->
<a-form-model-item class="exec-form-item" label="执行类型" required>
<a-radio-group v-model="submit.timedExec" buttonStyle="solid">
<a-radio-button :value="type.value" v-for="type in TIMED_TYPE" :key="type.value">
{{ type.execLabel }}
</a-radio-button>
</a-radio-group>
</a-form-model-item>
<!-- 调度时间 -->
<a-form-model-item class="exec-form-item" label="调度时间"
v-if="submit.timedExec === TIMED_TYPE.TIMED.value" required>
<a-date-picker v-model="submit.timedExecTime" :showTime="true" format="YYYY-MM-DD HH:mm:ss"/>
</a-form-model-item>
<!-- 执行描述 -->
<a-form-model-item class="exec-form-item" label="执行描述">
<a-textarea class="description-input" v-model="submit.description" :maxLength="64" allowClear/>
</a-form-model-item>
</a-form-model>
<a-divider class="detail-divider">流水线操作</a-divider>
<!-- 执行操作 -->
<div class="pipeline-wrapper">
<a-timeline>
<a-timeline-item v-for="detail of details" :key="detail.id">
<div class="pipeline-detail-wrapper">
<!-- 操作名称 -->
<div class="pipeline-stage-type span-blue">
{{ detail.stageType | formatStageType('label') }}
</div>
<!-- 应用名称 -->
<div class="pipeline-app-name">
{{ detail.appName }}
</div>
<!-- 应用操作 -->
<div class="pipeline-handler">
<span class="pipeline-config-message auto-ellipsis-item"
:title="getConfigMessage(detail)"
v-text="getConfigMessage(detail)"/>
<!-- 设置 -->
<a-badge class="pipeline-set-badge" :dot="visibleConfigDot(detail)">
<span class="span-blue pointer" title="设置" @click="openSetting(detail)">设置</span>
</a-badge>
</div>
</div>
</a-timeline-item>
</a-timeline>
</div>
</div>
</a-spin>
<!-- 页脚 -->
<template #footer>
<!-- 关闭 -->
<a-button @click="close">关闭</a-button>
<!-- 执行 -->
<a-button type="primary"
:loading="loading"
:disabled="selectPipelinePage || loading || initiating"
@click="execPipeline">
执行
</a-button>
</template>
<!-- 事件 -->
<div class="pipeline-event-container">
<!-- 构建配置 -->
<AppPipelineExecBuildModal ref="buildSetting" @ok="pipelineConfigured"/>
<!-- 发布配置 -->
<AppPipelineExecReleaseModal ref="releaseSetting" @ok="pipelineConfigured"/>
</div>
</a-modal>
</template>
<script>
import { enumValueOf, STAGE_TYPE, TIMED_TYPE } from '@/lib/enum'
import AppPipelineExecBuildModal from '@/components/app/AppPipelineExecBuildModal'
import AppPipelineExecReleaseModal from '@/components/app/AppPipelineExecReleaseModal'
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 18 }
}
export default {
name: 'AppPipelineExecModal',
components: {
AppPipelineExecReleaseModal,
AppPipelineExecBuildModal
},
props: {
visibleReselect: Boolean
},
data: function() {
return {
TIMED_TYPE,
selectPipelinePage: false,
id: null,
visible: false,
initiating: false,
loading: false,
profileId: null,
pipelineList: [],
record: {},
submit: {
title: null,
description: null,
timedExec: null,
timedExecTime: null
},
details: [],
layout
}
},
methods: {
async openPipelineList(profileId) {
this.profileId = profileId
this.pipelineList = []
this.selectPipelinePage = true
this.loading = false
this.initiating = true
this.visible = true
await this.loadPipelineList()
this.initiating = false
},
async openPipeline(profileId, id) {
this.profileId = profileId
this.pipelineList = []
this.cleanPipelineData()
this.selectPipelinePage = false
this.loading = false
this.initiating = true
this.visible = true
await this.selectPipeline(id)
this.initiating = false
},
async choosePipeline(id) {
this.cleanPipelineData()
this.selectPipelinePage = false
this.loading = true
await this.selectPipeline(id)
this.loading = false
},
async loadPipelineList() {
const { data: { rows } } = await this.$api.getAppPipelineList({
profileId: this.profileId,
limit: 10000
})
this.pipelineList = rows
},
async selectPipeline(id) {
this.id = id
await this.$api.getAppPipelineDetail({
id
}).then(({ data }) => {
this.record = data
this.details = data.details
this.submit.title = `执行${data.name}`
})
},
async reselectPipelineList() {
this.selectPipelinePage = true
if (this.pipelineList.length) {
return
}
this.initiating = true
await this.loadPipelineList()
this.initiating = false
},
cleanPipelineData() {
this.record = {}
this.details = []
this.submit.title = null
this.submit.description = null
this.submit.timedExec = TIMED_TYPE.NORMAL.value
this.submit.timedExecTime = null
},
visibleConfigDot(detail) {
if (detail.stageType === STAGE_TYPE.BUILD.value) {
if (detail.repoId) {
return !detail.branchName
} else {
return false
}
} else {
return false
}
},
getConfigMessage(detail) {
if (detail.stageType === STAGE_TYPE.BUILD.value) {
// 构建
if (detail.repoId) {
if (detail.branchName) {
return `${detail.branchName} ${detail.commitId.substring(0, 7)}`
} else {
return '请选择构建版本'
}
} else {
return '无需配置'
}
} else {
// 发布
if (detail.buildSeq) {
return `发布版本: #${detail.buildSeq}`
} else {
return '发布版本: 最新版本'
}
}
},
openSetting(detail) {
if (detail.stageType === STAGE_TYPE.BUILD.value) {
this.$refs.buildSetting.open(detail)
} else {
this.$refs.releaseSetting.open(detail)
}
},
pipelineConfigured(detailId, config) {
this.details.filter(s => s.id === detailId).forEach((detail) => {
for (const configKey in config) {
detail[configKey] = config[configKey]
}
})
this.$set(this.details, 0, this.details[0])
},
execPipeline() {
// 检查参数
if (!this.submit.title) {
this.$message.warning('请输入执行标题')
return
}
if (this.submit.timedExec === TIMED_TYPE.TIMED.value) {
if (!this.submit.timedExecTime) {
this.$message.warning('请选择调度时间')
return
}
if (this.submit.timedExecTime.unix() * 1000 < Date.now()) {
this.$message.warning('调度时间需要大于当前时间')
return
}
} else {
this.submit.timedExecTime = undefined
}
for (const detail of this.details) {
if (detail.stageType === STAGE_TYPE.BUILD.value && detail.repoId && !detail.branchName) {
this.$message.warning(`请选择 ${detail.appName} 构建版本`)
return
}
}
// 封装数据
const request = {
pipelineId: this.id,
...this.submit,
details: []
}
for (const detail of this.details) {
request.details.push({
id: detail.id,
branchName: detail.branchName,
commitId: detail.commitId,
buildId: detail.buildId,
title: detail.title,
description: detail.description,
machineIdList: detail.machineIdList
})
}
this.loading = true
this.$api.submitAppPipelineTask(request).then(() => {
this.$message.success('已创建流水线任务')
this.$emit('submit')
this.visible = false
}).catch(() => {
this.loading = false
})
},
close() {
this.visible = false
this.loading = false
}
},
filters: {
formatStageType(type, f) {
return enumValueOf(STAGE_TYPE, type)[f]
}
}
}
</script>
<style lang="less" scoped>
.pipeline-container {
padding: 16px 16px 0 16px;
}
.pipeline-list {
margin: 0 4px 0 8px;
height: 355px;
overflow-y: auto;
.pipeline-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 4px;
padding: 4px 4px 4px 8px;
background: #F8F9FA;
border-radius: 4px;
height: 40px;
cursor: pointer;
.pipeline-name {
width: 300px;
text-overflow: ellipsis;
display: block;
overflow-x: hidden;
white-space: nowrap;
}
}
.pipeline-item:hover {
background: #E7F5FF;
}
}
.exec-form-item {
margin-bottom: 12px;
}
.detail-divider {
margin: 0 0 24px 0;
}
.pipeline-detail-wrapper {
display: flex;
align-items: center;
.pipeline-stage-type {
margin-right: 4px;
}
.pipeline-app-name {
width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pipeline-handler {
width: 180px;
margin-left: 8px;
display: flex;
align-items: center;
justify-content: flex-end;
.pipeline-config-message {
margin-right: 8px;
color: #000000;
font-size: 12px;
width: 144px;
text-align: end;
}
}
::v-deep .pipeline-set-badge .ant-badge-dot {
margin: -2px;
}
}
::v-deep .ant-timeline-item-last {
padding-bottom: 0;
}
</style>