|
|
@@ -1,13 +1,395 @@
|
|
|
<template>
|
|
|
- project-pipeline
|
|
|
+ <ElTabs class="tabs" v-model="activeTab">
|
|
|
+ <ElTabPane label="流水线" name="pipeline">
|
|
|
+ <ElTable :data="pipelines" width="100%">
|
|
|
+ <ElTableColumn
|
|
|
+ prop="id"
|
|
|
+ label="ID"
|
|
|
+ align="center"
|
|
|
+ width="60"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="name"
|
|
|
+ label="名称"
|
|
|
+ align="center"
|
|
|
+ width="180"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="username"
|
|
|
+ label="最近执行人"
|
|
|
+ align="center"
|
|
|
+ width="180"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="最近执行时间"
|
|
|
+ align="center"
|
|
|
+ :formatter="(row) => timeFormatter(row, 'startTime')"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="result"
|
|
|
+ label="执行状态"
|
|
|
+ align="center"
|
|
|
+ width="100"
|
|
|
+ :filters="[{ text: '成功', value: '成功' }, { text: '失败', value: '失败' }]"
|
|
|
+ :filter-method="(value, row) => {
|
|
|
+ return row.result === value;
|
|
|
+ }"
|
|
|
+ filter-placement="bottom-end"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <template v-if="scope.row.result === '成功'">
|
|
|
+ <ElTag type="success">{{scope.row.result}}</ElTag>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="scope.row.result === '失败'">
|
|
|
+ <ElTag type="danger">{{scope.row.result}}</ElTag>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="scope.row.result === '执行中'">
|
|
|
+ <ElTag>{{scope.row.result}}<i class="el-icon-loading"></i></ElTag>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <ElTag type="warning">未知状态</ElTag>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="详情"
|
|
|
+ align="center"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <ElButton
|
|
|
+ @click="handleClick('detail', scope.row.id)"
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ :disabled="scope.row.result==='执行中'"
|
|
|
+ >最近执行详情</ElButton>
|
|
|
+ <ElButton @click="handleClick('history', scope.row.id)" type="text" size="small">历史</ElButton>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ align="center"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <span>操作</span>
|
|
|
+ <ElButton @click="handleClick('create')" type="text" class="add-button">新建</ElButton>
|
|
|
+ </template>
|
|
|
+ <template #default="scope">
|
|
|
+ <ElButton @click="handleClick('trigger', scope.row.id)" type="text" size="small">触发</ElButton>
|
|
|
+ <ElButton @click="handleClick('edit', scope.row.id)" type="text" size="small">编辑</ElButton>
|
|
|
+ <ElButton @click="handleClick('delete', scope.row.id)" type="text" size="small">删除</ElButton>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ </ElTable>
|
|
|
+ <ElDialog v-model="showPipelineDetailModal" destroy-on-close>
|
|
|
+ <template #title>最近一次流水线构建的执行结果详情</template>
|
|
|
+ <pre class="pipeline-detail">{{detail}}</pre>
|
|
|
+ </ElDialog>
|
|
|
+ <ElDialog v-model="showPipelineHistoryModal" destroy-on-close>
|
|
|
+ <template #title>流水线构建历史</template>
|
|
|
+ <ElTable
|
|
|
+ :data="pipelineHistory"
|
|
|
+ :default-sort="{
|
|
|
+ props: 'id',
|
|
|
+ }"
|
|
|
+ height="400"
|
|
|
+ >
|
|
|
+ <ElTableColumn type="expand">
|
|
|
+ <template #default="scope">
|
|
|
+ <pre class="pipeline-detail">{{scope.row.details}}</pre>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="id"
|
|
|
+ label="构建ID"
|
|
|
+ align="center"
|
|
|
+ width="100"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="pipelineId"
|
|
|
+ label="流水线ID"
|
|
|
+ align="center"
|
|
|
+ width="100"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="username"
|
|
|
+ label="触发人"
|
|
|
+ align="center"
|
|
|
+ width="180"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="执行时间"
|
|
|
+ align="center"
|
|
|
+ :formatter="(row) => timeFormatter(row, 'startTime')"
|
|
|
+ ></ElTableColumn>
|
|
|
+ </ElTable>
|
|
|
+ </ElDialog>
|
|
|
+ <ElDialog
|
|
|
+ v-model="showPipelineEditModal"
|
|
|
+ :before-close="(done) => {
|
|
|
+ jsonValid=true;
|
|
|
+ done();
|
|
|
+ }"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <template #title>
|
|
|
+ <span>编辑流水线配置</span>
|
|
|
+ <span class="button-group">
|
|
|
+ <ElButton type="text">脚本教程</ElButton>
|
|
|
+ <ElButton type="text">模板详解</ElButton>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ <ElInput
|
|
|
+ type="textarea"
|
|
|
+ :rows="16"
|
|
|
+ placeholder="请输入流水线配置"
|
|
|
+ @input="lintJson"
|
|
|
+ v-model="pipelineConfig">
|
|
|
+ ></ElInput>
|
|
|
+ <pre v-if="!jsonValid" :innerText="hint"></pre>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button type="primary" @click="handlePipelineUpdateConfirm" :disabled="!jsonValid">确 定</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </ElDialog>
|
|
|
+ <ElDialog
|
|
|
+ v-model="showPipelineCreateModal"
|
|
|
+ :before-close="(done) => {
|
|
|
+ $refs['createPipelineForm'].resetFields();
|
|
|
+ done();
|
|
|
+ }"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <template #title>创建流水线</template>
|
|
|
+ <ElForm
|
|
|
+ :model="createPipelineForm"
|
|
|
+ ref="createPipelineForm"
|
|
|
+ label-position="top"
|
|
|
+ label-suffix=":"
|
|
|
+ class="create-pipeline-form"
|
|
|
+ >
|
|
|
+ <ElFormItem label="名称" prop="name" required>
|
|
|
+ <ElInput v-model="createPipelineForm.name" placeholder="请输入流水线名称"></ElInput>
|
|
|
+ </ElFormItem>
|
|
|
+ <ElFormItem label="模板" prop="template" required>
|
|
|
+ <ElSelect v-model="createPipelineForm.template" placeholder="请选择模板">
|
|
|
+ <ElOption
|
|
|
+ v-for="(template, index) in templates"
|
|
|
+ :key="index" :label="template"
|
|
|
+ :value="template"
|
|
|
+ ></ElOption>
|
|
|
+ </ElSelect>
|
|
|
+ </ElFormItem>
|
|
|
+ </ElForm>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button type="primary" @click="handlePipelineCreateConfirm">确 定</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </ElDialog>
|
|
|
+ </ElTabPane>
|
|
|
+ <ElTabPane label="部署产物" name="production">
|
|
|
+ <ElTable :data="productions" width="100%">
|
|
|
+ <ElTableColumn
|
|
|
+ prop="id"
|
|
|
+ label="ID"
|
|
|
+ align="center"
|
|
|
+ width="60"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="name"
|
|
|
+ label="名称"
|
|
|
+ align="center"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ prop="pipelineId"
|
|
|
+ label="流水线ID"
|
|
|
+ align="center"
|
|
|
+ width="100"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="部署时间"
|
|
|
+ align="center"
|
|
|
+ :formatter="(row) => timeFormatter(row, 'deployedTime')"
|
|
|
+ ></ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="URL"
|
|
|
+ align="center"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <ElButton type="text" @click="open(scope.row.accessUrl, '_blank')">
|
|
|
+ {{scope.row.accessUrl}}
|
|
|
+ </ElButton>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn
|
|
|
+ label="运行状态"
|
|
|
+ align="center"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ <template v-if="scope.row.status === '正常'">
|
|
|
+ <ElTag type="success">{{scope.row.status}}</ElTag>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <ElTag type="danger">{{scope.row.status}}</ElTag>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn>
|
|
|
+ <template #default="scope">
|
|
|
+ <ElButton type="text" @click="deleteProduction(scope.row.id)">
|
|
|
+ <i class="el-icon-delete-solid"></i>
|
|
|
+ </ElButton>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ </ElTable>
|
|
|
+ </ElTabPane>
|
|
|
+ </ElTabs>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-export default {
|
|
|
+import jsonlint from '@/utils/jsonlint';
|
|
|
+
|
|
|
+import { PipelineAPI } from '@/api';
|
|
|
+import debounce from '@/utils/debounce';
|
|
|
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ activeTab: 'pipeline',
|
|
|
+ pipelines: [],
|
|
|
+ productions: [],
|
|
|
+ showPipelineCreateModal: false,
|
|
|
+ showPipelineEditModal: false,
|
|
|
+ showPipelineHistoryModal: false,
|
|
|
+ showPipelineDetailModal: false,
|
|
|
+ detail: '',
|
|
|
+ pipelineHistory: [],
|
|
|
+ pipelineConfig: '',
|
|
|
+ jsonValid: true,
|
|
|
+ editingPipelineId: -1,
|
|
|
+ templates: ['Java项目', 'MySQL'],
|
|
|
+ createPipelineForm: {
|
|
|
+ name: '',
|
|
|
+ template: '',
|
|
|
+ },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ async mounted() {
|
|
|
+ this.pipelines = await PipelineAPI.getPipelinesByProjectId(this.$store.state.workspace.currentProjectId);
|
|
|
+ this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ timeFormatter: (row, property) => `${row[property].year}/${row[property].month}/${row[property].day} `
|
|
|
+ + `${row[property].hours}:${row[property].minutes}:${row[property].seconds}`,
|
|
|
+ async handleClick(action, id) {
|
|
|
+ switch (action) {
|
|
|
+ case 'create': {
|
|
|
+ this.showPipelineCreateModal = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'trigger': {
|
|
|
+ await PipelineAPI.triggerDeployment({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: id,
|
|
|
+ });
|
|
|
+ this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'detail': {
|
|
|
+ this.showPipelineDetailModal = true;
|
|
|
+ this.detail = Array.prototype.find.call(this.pipelines, (pipeline) => pipeline.id === id).details;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'history': {
|
|
|
+ this.showPipelineHistoryModal = true;
|
|
|
+ this.pipelineHistory = await PipelineAPI.getPipelineHistory({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: id,
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'edit': {
|
|
|
+ this.showPipelineEditModal = true;
|
|
|
+ this.editingPipelineId = id;
|
|
|
+ this.pipelineConfig = await PipelineAPI.getPipeline({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: id,
|
|
|
+ }).configJson || '{}';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'delete': {
|
|
|
+ PipelineAPI.deletePipeline({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: id,
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ console.warn('Unknown type of action.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // eslint-disable-next-line func-names
|
|
|
+ // FIXME: debounce效果有点问题,待排查
|
|
|
+ lintJson: debounce(function (value) {
|
|
|
+ try {
|
|
|
+ const result = jsonlint.parse(value);
|
|
|
+ this.pipelineConfig = JSON.stringify(result, null, ' ');
|
|
|
+ this.jsonValid = true;
|
|
|
+ } catch (e) {
|
|
|
+ this.jsonValid = false;
|
|
|
+ this.hint = e;
|
|
|
+ }
|
|
|
+ }, 500),
|
|
|
+ async handlePipelineUpdateConfirm() {
|
|
|
+ await PipelineAPI.updatePipeline({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: this.editingPipelineId,
|
|
|
+ configJson: JSON.stringify(JSON.parse(this.pipelineConfig)),
|
|
|
+ });
|
|
|
+ this.showPipelineEditModal = false;
|
|
|
+ },
|
|
|
+ async handlePipelineCreateConfirm() {
|
|
|
+ this.$refs.createPipelineForm.validate(async (valid) => {
|
|
|
+ if (valid) {
|
|
|
+ await PipelineAPI.createPipeline({
|
|
|
+ projectId: this.$store.state.workspace.currentProjectId,
|
|
|
+ pipelineId: this.editingPipelineId,
|
|
|
+ name: this.createPipelineForm.name,
|
|
|
+ templateName: this.createPipelineForm.template,
|
|
|
+ });
|
|
|
+ this.showPipelineCreateModal = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ open(url, target) {
|
|
|
+ window.open(url, target);
|
|
|
+ },
|
|
|
+ async deleteProduction(id) {
|
|
|
+ await PipelineAPI.deleteInstance(id);
|
|
|
+ },
|
|
|
+ },
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
-<style>
|
|
|
-
|
|
|
+<style scoped>
|
|
|
+.tabs {
|
|
|
+ width: 80%;
|
|
|
+ margin: auto auto;
|
|
|
+}
|
|
|
+.add-button {
|
|
|
+ margin-left: 14px;
|
|
|
+}
|
|
|
+.pipeline-detail {
|
|
|
+ width: 100%;
|
|
|
+ word-break: break-word;
|
|
|
+ white-space: pre-wrap;
|
|
|
+}
|
|
|
+.create-pipeline-form {
|
|
|
+ width: 60%;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+.button-group {
|
|
|
+ margin-left: 40px;
|
|
|
+}
|
|
|
</style>
|