mirror of https://github.com/certd/certd
chore: 模版创建流水线
parent
821c6d807d
commit
9296ba7492
|
@ -84,6 +84,7 @@ export abstract class BaseService<T> {
|
|||
...where,
|
||||
});
|
||||
await this.modifyAfter(idArr);
|
||||
return ids
|
||||
}
|
||||
|
||||
resolveIdArr(ids: string | any[]) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: "LayoutFooter"
|
||||
name: "LayoutFooter",
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
addForm: {
|
||||
onSuccess: ({ res }) => {
|
||||
router.push({ path: "/certd/pipeline/template/edit", query: { templateId: res.id, editMode: "true" } });
|
||||
},
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
|
@ -95,7 +100,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
width: 400,
|
||||
sorter: true,
|
||||
cellRender({ row, value }) {
|
||||
return <router-link to={{ path: "/certd/pipeline/template/edit", query: { templateId: row.id } }}>{value}</router-link>;
|
||||
|
@ -117,8 +122,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
editForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
show: true,
|
||||
helper: "复制该流水线配置作为模版来源",
|
||||
component: {
|
||||
valuesFormat: {
|
||||
labelFormatter: (item: any) => {
|
||||
|
|
|
@ -7,18 +7,33 @@
|
|||
</div>
|
||||
|
||||
<div class="more flex items-center flex-1 justify-end">
|
||||
<loading-button type="primary" @click="doSave">保存</loading-button>
|
||||
<loading-button type="primary" @click="doSave">保存模版</loading-button>
|
||||
<loading-button class="ml-10" type="primary" @click="useTemplateCreate">使用模版</loading-button>
|
||||
<loading-button class="ml-10" type="primary" danger @click="doDelete">删除模版</loading-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="page-template-edit">
|
||||
<div class="base"></div>
|
||||
<div class="props flex p-10">
|
||||
<div class="task-list w-50%">
|
||||
<div class="block-title">
|
||||
原始任务参数
|
||||
<div class="helper">点击加号,将字段作为模版变量</div>
|
||||
<div class="block-title flex flex-between">
|
||||
<div>
|
||||
模版流水线参数
|
||||
<div class="helper">点击加号,将字段作为模版变量</div>
|
||||
</div>
|
||||
<div class="more">
|
||||
<router-link
|
||||
v-if="detail?.template?.pipelineId > 0"
|
||||
:to="{
|
||||
path: '/certd/pipeline/detail',
|
||||
query: { id: detail?.template?.pipelineId, editMode: true },
|
||||
}"
|
||||
>
|
||||
修改模版流水线
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<a-collapse v-model:active-key="activeKey">
|
||||
<a-collapse v-if="detail?.template?.pipelineId > 0" v-model:active-key="activeKey">
|
||||
<a-collapse-panel v-for="(step, stepId) in steps" :key="stepId" class="step-item" :header="step.title">
|
||||
<div class="step-inputs flex flex-wrap">
|
||||
<div v-for="(input, key) of step.input" :key="key" class="hover:bg-gray-100 p-5 w-full xl:w-[50%]">
|
||||
|
@ -36,6 +51,17 @@
|
|||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
<div v-else-if="detail?.template?.pipelineId === 0">
|
||||
<div class="p-20 flex flex-col flex-center text-sm">
|
||||
<div class="mb-10">还未绑定模版流水线</div>
|
||||
<div>
|
||||
<a-button type="primary" @click="bindPipelineByCreate">创建新流水线作为模版</a-button>
|
||||
或
|
||||
<a-button type="primary" @click="bindPipelineByCopy">从已有流水线复制</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-props w-50%">
|
||||
|
@ -60,7 +86,9 @@ import { templateApi } from "./api";
|
|||
import { usePluginStore } from "/@/store/plugin";
|
||||
import { useStepHelper } from "./utils";
|
||||
import TemplateForm from "./form.vue";
|
||||
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { useTabbarStore } from "/@/vben/stores";
|
||||
import { useTemplate } from "./use";
|
||||
const route = useRoute();
|
||||
const templateId = route.query.templateId as string;
|
||||
|
||||
|
@ -79,6 +107,9 @@ const templateProps: Ref = ref({
|
|||
});
|
||||
const detail: Ref<TemplateDetail> = ref();
|
||||
async function getTemplateDetail() {
|
||||
if (!templateId) {
|
||||
return;
|
||||
}
|
||||
const res = await templateApi.GetDetail(parseInt(templateId));
|
||||
detail.value = res;
|
||||
templateProps.value = JSON.parse(res.template.content ?? "{input:{}}");
|
||||
|
@ -100,7 +131,7 @@ onMounted(async () => {
|
|||
|
||||
const { getStepsMap } = useStepHelper(pluginStore);
|
||||
const steps = computed(() => {
|
||||
if (!detail.value) {
|
||||
if (!detail.value || !detail.value.pipeline) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -126,5 +157,36 @@ async function doSave() {
|
|||
title: detail.value.template.title,
|
||||
content: JSON.stringify(templateProps.value),
|
||||
});
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
}
|
||||
|
||||
const tabbar = useTabbarStore();
|
||||
async function doDelete() {
|
||||
Modal.confirm({
|
||||
title: "确定删除模版?",
|
||||
content: "删除后,该模版流水线将不能再使用",
|
||||
onOk() {
|
||||
templateApi.DelObj(detail.value.template.id);
|
||||
notification.success({
|
||||
message: "删除成功",
|
||||
});
|
||||
tabbar.closeTab({ fullPath: route.fullPath } as any, router);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function bindPipelineByCreate() {
|
||||
//
|
||||
// openAddCertdPipelineDialog({ templateId: detail.value.template.id });
|
||||
}
|
||||
|
||||
async function bindPipelineByCopy() {}
|
||||
|
||||
const { openCreateFromTemplateDialog } = useTemplate();
|
||||
|
||||
async function useTemplateCreate() {
|
||||
openCreateFromTemplateDialog({ templateId: detail.value.template.id });
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-form ref="templateFormRef" class="template-form" :model="templateForm" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-form ref="templateFormRef" class="template-form w-full" :model="templateForm" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<template v-for="(item, key) in templateFormColumns" :key="key">
|
||||
<fs-form-item v-if="item.show !== false" :model-value="get(templateForm, key)" :item="item" :get-context-fn="getScopeFunc(key)" @update:model-value="set(templateForm, key, $event)" />
|
||||
</template>
|
||||
|
@ -28,7 +28,12 @@ const steps = computed(() => {
|
|||
return getStepsMap(props.pipeline);
|
||||
});
|
||||
|
||||
const labelCol = ref({ span: 6 });
|
||||
const labelCol = ref({
|
||||
span: null,
|
||||
style: {
|
||||
width: "145px",
|
||||
},
|
||||
});
|
||||
const wrapperCol = ref({ span: 16 });
|
||||
const templateForm: any = reactive({});
|
||||
const templateFormColumns = computed(() => {
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
import { dict, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { checkPipelineLimit } from "/@/views/certd/pipeline/utils";
|
||||
import { templateApi } from "/@/views/certd/pipeline/template/api";
|
||||
import TemplateForm from "./form.vue";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
export function useTemplate() {
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
async function openCreateFromTemplateDialog(req: { templateId?: number }) {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
const detail = await templateApi.GetDetail(req.templateId);
|
||||
if (!detail) {
|
||||
throw new Error("模板不存在");
|
||||
}
|
||||
if (!detail.template?.pipelineId) {
|
||||
throw new Error("还未绑定模版流水线");
|
||||
}
|
||||
const templateProps = JSON.parse(detail.template.content || "{}");
|
||||
const pipeline = detail.pipeline;
|
||||
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
|
||||
const wrapperRef = ref();
|
||||
function getFormData() {
|
||||
if (!wrapperRef.value) {
|
||||
return null;
|
||||
}
|
||||
return wrapperRef.value.getFormData();
|
||||
}
|
||||
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
const crudOptions = {
|
||||
form: {
|
||||
wrapper: {
|
||||
title: `从模版<${detail.template.title}>创建流水线`,
|
||||
width: 1100,
|
||||
slots: {
|
||||
"form-body-top": () => {
|
||||
return (
|
||||
<div class={"w-full flex"}>
|
||||
<TemplateForm input={templateProps.input} pipeline={pipeline} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
triggerCron: {
|
||||
title: "定时触发",
|
||||
type: "text",
|
||||
form: {
|
||||
value: `0 ${randomMin} ${randomHour} * * *`,
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: "失败通知",
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
on: {
|
||||
selectedChange(opts: any) {
|
||||
opts.form.notificationTarget = opts.$event;
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: "流水线分组",
|
||||
type: "dict-select",
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
order: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||
wrapperRef.value = wrapper;
|
||||
}
|
||||
|
||||
return {
|
||||
openCreateFromTemplateDialog,
|
||||
};
|
||||
}
|
|
@ -13,3 +13,6 @@ CREATE TABLE "pi_template"
|
|||
|
||||
CREATE INDEX "index_template_user_id" ON "pi_template" ("user_id");
|
||||
CREATE INDEX "index_template_pipeline_id" ON "pi_template" ("pipeline_id");
|
||||
|
||||
ALTER TABLE pi_pipeline ADD COLUMN "template_id" integer DEFAULT 0;
|
||||
CREATE INDEX "index_pipeline_template_id" ON "pi_pipeline" ("template_id");
|
||||
|
|
|
@ -59,8 +59,8 @@ export class TemplateController extends CrudController<TemplateService> {
|
|||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
return super.delete(id);
|
||||
await this.service.batchDelete([id], this.getUserId());
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/batchDelete', { summary: Constants.per.authOnly })
|
||||
|
|
|
@ -52,6 +52,7 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
|
|||
}
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
async delete(ids: any) {
|
||||
if (!ids) {
|
||||
return;
|
||||
|
|
|
@ -37,6 +37,9 @@ export class PipelineEntity {
|
|||
@Column({ comment: '来源', nullable: true, default: '' })
|
||||
from: string;
|
||||
|
||||
@Column({ name:"template_id", comment: '是否模版', nullable: true, default: '' })
|
||||
templateId: number;
|
||||
|
||||
@Column({
|
||||
name: 'last_history_time',
|
||||
comment: '最后一次执行时间',
|
||||
|
|
|
@ -46,6 +46,7 @@ import {UserSuiteEntity, UserSuiteService} from "@certd/commercial-core";
|
|||
import {CertInfoService} from "../../monitor/service/cert-info-service.js";
|
||||
import {TaskServiceBuilder} from "./task-service-getter.js";
|
||||
import {nanoid} from "nanoid";
|
||||
import {set} from "lodash-es";
|
||||
|
||||
const runningTasks: Map<string | number, Executor> = new Map();
|
||||
|
||||
|
@ -117,6 +118,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
}
|
||||
|
||||
async page(pageReq: PageReq<PipelineEntity>) {
|
||||
//模版流水线不要被查询出来
|
||||
set(pageReq,"query.templateId",0)
|
||||
const result = await super.page(pageReq);
|
||||
await this.fillLastVars(result.records);
|
||||
|
||||
|
@ -281,6 +284,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
},
|
||||
where: {
|
||||
disabled: false,
|
||||
templateId: 0,
|
||||
},
|
||||
});
|
||||
const ids = idEntityList.map(item => {
|
||||
|
@ -385,7 +389,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
}
|
||||
}
|
||||
|
||||
async delete(id: any) {
|
||||
//@ts-ignore
|
||||
async delete(id:any) {
|
||||
await this.clearTriggers(id);
|
||||
//TODO 删除storage
|
||||
// const storage = new DbStorage(pipeline.userId, this.storageService);
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {BaseService, SysSettingsService} from '@certd/lib-server';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import { TemplateEntity } from '../entity/template.js';
|
||||
import { PipelineService } from './pipeline-service.js';
|
||||
import {In, Repository} from 'typeorm';
|
||||
import {TemplateEntity} from '../entity/template.js';
|
||||
import {PipelineService} from './pipeline-service.js';
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {PipelineEntity} from "../entity/pipeline.js";
|
||||
import {Pipeline} from "@certd/pipeline";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
export class TemplateService extends BaseService<TemplateEntity> {
|
||||
@InjectEntityModel(TemplateEntity)
|
||||
repository: Repository<TemplateEntity>;
|
||||
|
@ -22,6 +25,47 @@ export class TemplateService extends BaseService<TemplateEntity> {
|
|||
return this.repository;
|
||||
}
|
||||
|
||||
async add(param: any) {
|
||||
const pipelineId = param.pipelineId;
|
||||
delete param.pipelineId;
|
||||
|
||||
const pipelineEntity = await this.pipelineService.info(pipelineId);
|
||||
if (!pipelineEntity) {
|
||||
throw new Error('pipeline not found');
|
||||
}
|
||||
if (pipelineEntity.userId !== param.userId) {
|
||||
throw new Error('permission denied');
|
||||
}
|
||||
|
||||
|
||||
let template = null
|
||||
await this.transaction(async (tx: any) => {
|
||||
|
||||
template = await tx.getRepository(TemplateEntity).save(param);
|
||||
let newPipeline = cloneDeep(pipelineEntity)
|
||||
//创建pipeline模版
|
||||
newPipeline.id = undefined;
|
||||
newPipeline.title = template.title+"模版流水线"
|
||||
newPipeline.templateId = template.id
|
||||
|
||||
const pipelineJson:Pipeline = JSON.parse(newPipeline.content)
|
||||
delete pipelineJson.triggers
|
||||
pipelineJson.id = template.id
|
||||
pipelineJson.userId = template.userId
|
||||
pipelineJson.title = newPipeline.title
|
||||
newPipeline.content = JSON.stringify(pipelineJson)
|
||||
newPipeline = await tx.getRepository(PipelineEntity).save(newPipeline)
|
||||
|
||||
const update :any= {}
|
||||
update.id = template.id
|
||||
update.pipelineId = newPipeline.id
|
||||
await tx.getRepository(TemplateEntity).save(update)
|
||||
})
|
||||
|
||||
return template
|
||||
|
||||
}
|
||||
|
||||
async detail(id: number, userId: number) {
|
||||
const info = await this.info(id)
|
||||
if (!info) {
|
||||
|
@ -30,12 +74,31 @@ export class TemplateService extends BaseService<TemplateEntity> {
|
|||
if (info.userId !== userId) {
|
||||
throw new Error('无权限');
|
||||
}
|
||||
const pipeline = await this.pipelineService.info(info.pipelineId);
|
||||
let pipeline = null
|
||||
if (info.pipelineId) {
|
||||
const pipelineEntity = await this.pipelineService.info(info.pipelineId);
|
||||
pipeline = JSON.parse(pipelineEntity.content)
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
template:info,
|
||||
pipeline: JSON.parse(pipeline.content),
|
||||
template: info,
|
||||
pipeline,
|
||||
}
|
||||
}
|
||||
async batchDelete(ids: number[], userId: number) {
|
||||
|
||||
const where :any= {
|
||||
id: In(ids),
|
||||
}
|
||||
if (userId > 0) {
|
||||
where.userId = userId
|
||||
}
|
||||
const list = await this.getRepository().find({where })
|
||||
ids = list.map(item => item.id)
|
||||
const pipelineIds = list.map(item => item.pipelineId)
|
||||
await this.delete(ids);
|
||||
await this.pipelineService.batchDelete(pipelineIds,userId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ export class RoleService extends BaseService<RoleEntity> {
|
|||
return permissionSet;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
async delete(id: any) {
|
||||
const idArr = this.resolveIdArr(id);
|
||||
//@ts-ignore
|
||||
|
|
|
@ -253,6 +253,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||
await this.update(param);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
async delete(ids: any) {
|
||||
if (typeof ids === 'string') {
|
||||
ids = ids.split(',');
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
createCertDomainGetterInputDefine,
|
||||
createRemoteSelectInputDefine
|
||||
} from "@certd/plugin-lib";
|
||||
import {PageReq} from "@certd/lib-server";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'AliyunDeployCertToWaf',
|
||||
|
@ -168,7 +169,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
async onGetCnameList(data: any) {
|
||||
async onGetCnameList(data: PageReq) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue