chore: 模版创建流水线

pull/453/head
xiaojunnuo 2025-06-25 18:18:57 +08:00
parent 9296ba7492
commit 29906ec057
11 changed files with 127 additions and 38 deletions

View File

@ -12,6 +12,25 @@ import { PluginGroup, usePluginStore } from "/@/store/plugin";
import { createNotificationApi } from "/@/views/certd/notification/api"; import { createNotificationApi } from "/@/views/certd/notification/api";
import GroupSelector from "../group/group-selector.vue"; import GroupSelector from "../group/group-selector.vue";
export function fillPipelineByDefaultForm(pipeline: any, form: any) {
const triggers = [];
if (form.triggerCron) {
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
}
const notifications = [];
if (form.notification != null) {
notifications.push({
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: form.notification,
title: form.notificationTarget?.name || "自定义通知",
});
}
pipeline.triggers = triggers;
pipeline.notifications = notifications;
return pipeline;
}
export function setRunnableIds(pipeline: any) { export function setRunnableIds(pipeline: any) {
const idMap: any = {}; const idMap: any = {};
function createId(oldId: any) { function createId(oldId: any) {
@ -244,21 +263,8 @@ export function useCertPipelineCreator() {
async function doSubmit({ form }: any) { async function doSubmit({ form }: any) {
// const certDetail = readCertDetail(form.cert.crt); // const certDetail = readCertDetail(form.cert.crt);
// 添加certd pipeline // 添加certd pipeline
const triggers = [];
if (form.triggerCron) {
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
}
const notifications = [];
if (form.notification != null) {
notifications.push({
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: form.notification,
title: form.notificationTarget?.name || "自定义通知",
});
}
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]); const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
let pipeline = { let pipeline: any = {
title: form.domains[0] + "证书自动化", title: form.domains[0] + "证书自动化",
runnableType: "pipeline", runnableType: "pipeline",
stages: [ stages: [
@ -288,17 +294,11 @@ export function useCertPipelineCreator() {
], ],
}, },
], ],
triggers,
notifications,
}; };
pipeline = setRunnableIds(pipeline);
/** pipeline = fillPipelineByDefaultForm(pipeline, form);
* // cert: 证书; backup: 备份; custom:自定义;
* type: string; pipeline = setRunnableIds(pipeline);
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const groupId = form.groupId; const groupId = form.groupId;
const id = await api.Save({ const id = await api.Save({
title: pipeline.title, title: pipeline.title,

View File

@ -56,4 +56,12 @@ export const templateApi = {
method: "post", method: "post",
}); });
}, },
async CreatePipelineByTemplate(data: any) {
return await request({
url: apiPrefix + "/createPipelineByTemplate",
method: "post",
data,
});
},
}; };

View File

@ -146,8 +146,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
toolbar: { toolbar: {
show: false, show: false,
}, },
tabs: { columns: {
name: "type", title: {
column: {
cellRender: null,
},
},
}, },
}, },
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<a-form ref="templateFormRef" class="template-form w-full" :model="templateForm" :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form ref="formRef" class="template-form w-full" :model="templateForm" :label-col="labelCol" :wrapper-col="wrapperCol">
<template v-for="(item, key) in templateFormColumns" :key="key"> <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)" /> <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> </template>
@ -14,7 +14,7 @@ import { usePluginStore } from "/@/store/plugin";
defineOptions({ defineOptions({
name: "TemplateForm", name: "TemplateForm",
}); });
const formRef = ref();
const props = defineProps<{ const props = defineProps<{
input: any; input: any;
pipeline: any; pipeline: any;
@ -61,9 +61,14 @@ function getScopeFunc(inputKey: string) {
}; };
} }
async function validate() {
return await formRef.value.validate();
}
defineExpose({ defineExpose({
getForm() { getForm() {
return templateForm; return templateForm;
}, },
validate,
}); });
</script> </script>

View File

@ -1,10 +1,12 @@
import { dict, useFormWrapper } from "@fast-crud/fast-crud"; import { dict, useFormWrapper } from "@fast-crud/fast-crud";
import { checkPipelineLimit } from "/@/views/certd/pipeline/utils"; import { checkPipelineLimit, eachSteps } from "/@/views/certd/pipeline/utils";
import { templateApi } from "/@/views/certd/pipeline/template/api"; import { templateApi } from "/@/views/certd/pipeline/template/api";
import TemplateForm from "./form.vue"; import TemplateForm from "./form.vue";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue"; import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue"; import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { ref } from "vue"; import { ref } from "vue";
import { fillPipelineByDefaultForm } from "/@/views/certd/pipeline/certd-form/use";
import { cloneDeep } from "lodash-es";
export function useTemplate() { export function useTemplate() {
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
@ -38,8 +40,43 @@ export function useTemplate() {
const randomHour = Math.floor(Math.random() * 6); const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60); const randomMin = Math.floor(Math.random() * 60);
const templateFormRef = ref();
async function onSubmit(opts: { form: any }) {
const form = opts.form;
await templateFormRef.value.validate();
const tempInputs = templateFormRef.value.getFormData();
let newPipeline = cloneDeep(pipeline);
newPipeline = fillPipelineByDefaultForm(newPipeline, form);
//填充模版参数
const steps: any = {};
eachSteps(newPipeline, (step: any) => {
steps[step.id] = step;
});
for (const inputKey in tempInputs) {
const [stepId, key] = inputKey.split(".");
const step = steps[stepId];
if (step) {
step.input[key] = tempInputs[inputKey];
}
}
const groupId = form.groupId;
const { id } = await templateApi.CreatePipelineByTemplate({
title: form.title,
content: JSON.stringify(newPipeline),
keepHistoryCount: 30,
groupId,
templateId: detail.template.id,
});
}
const crudOptions = { const crudOptions = {
form: { form: {
onSubmit,
wrapper: { wrapper: {
title: `从模版<${detail.template.title}>创建流水线`, title: `从模版<${detail.template.title}>创建流水线`,
width: 1100, width: 1100,
@ -47,7 +84,7 @@ export function useTemplate() {
"form-body-top": () => { "form-body-top": () => {
return ( return (
<div class={"w-full flex"}> <div class={"w-full flex"}>
<TemplateForm input={templateProps.input} pipeline={pipeline} /> <TemplateForm ref={templateFormRef} input={templateProps.input} pipeline={pipeline} />
</div> </div>
); );
}, },

View File

@ -6,6 +6,7 @@ CREATE TABLE "pi_template"
"title" varchar(1024), "title" varchar(1024),
"content" text, "content" text,
"order" integer, "order" integer,
"desc" varchar(1024),
"disabled" boolean NOT NULL DEFAULT (false), "disabled" boolean NOT NULL DEFAULT (false),
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
@ -14,5 +15,6 @@ CREATE TABLE "pi_template"
CREATE INDEX "index_template_user_id" ON "pi_template" ("user_id"); CREATE INDEX "index_template_user_id" ON "pi_template" ("user_id");
CREATE INDEX "index_template_pipeline_id" ON "pi_template" ("pipeline_id"); CREATE INDEX "index_template_pipeline_id" ON "pi_template" ("pipeline_id");
ALTER TABLE pi_pipeline ADD COLUMN "template_id" integer DEFAULT 0; ALTER TABLE pi_pipeline ADD COLUMN "template_id" integer DEFAULT (0);
ALTER TABLE pi_pipeline ADD COLUMN "is_template" boolean DEFAULT (0);
CREATE INDEX "index_pipeline_template_id" ON "pi_pipeline" ("template_id"); CREATE INDEX "index_pipeline_template_id" ON "pi_pipeline" ("template_id");

View File

@ -74,4 +74,10 @@ export class TemplateController extends CrudController<TemplateService> {
const detail = await this.service.detail(id, this.getUserId()); const detail = await this.service.detail(id, this.getUserId());
return this.ok(detail); return this.ok(detail);
} }
@Post('/createPipelineByTemplate', { summary: Constants.per.authOnly })
async createPipelineByTemplate(@Body(ALL) body: any) {
body.userId = this.getUserId();
const res = await this.service.createPipelineByTemplate(body);
return this.ok(res);
}
} }

View File

@ -37,9 +37,12 @@ export class PipelineEntity {
@Column({ comment: '来源', nullable: true, default: '' }) @Column({ comment: '来源', nullable: true, default: '' })
from: string; from: string;
@Column({ name:"template_id", comment: '是否模版', nullable: true, default: '' }) @Column({ name:"template_id", comment: '关联模版id', nullable: true, default: 0 })
templateId: number; templateId: number;
@Column({ name:"is_template", comment: '是否模版', nullable: true, default: false })
isTemplate: boolean;
@Column({ @Column({
name: 'last_history_time', name: 'last_history_time',
comment: '最后一次执行时间', comment: '最后一次执行时间',

View File

@ -23,6 +23,8 @@ export class TemplateEntity {
@Column({ name: 'title', comment: '标题' }) @Column({ name: 'title', comment: '标题' })
title: string; title: string;
@Column({ name: 'desc', comment: '说明' })
desc: string;
@Column({ comment: '配置', length: 40960 }) @Column({ comment: '配置', length: 40960 })
content: string; content: string;

View File

@ -119,7 +119,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
async page(pageReq: PageReq<PipelineEntity>) { async page(pageReq: PageReq<PipelineEntity>) {
//模版流水线不要被查询出来 //模版流水线不要被查询出来
set(pageReq,"query.templateId",0) set(pageReq,"query.isTemplate",false)
const result = await super.page(pageReq); const result = await super.page(pageReq);
await this.fillLastVars(result.records); await this.fillLastVars(result.records);

View File

@ -47,6 +47,7 @@ export class TemplateService extends BaseService<TemplateEntity> {
newPipeline.id = undefined; newPipeline.id = undefined;
newPipeline.title = template.title + "模版流水线" newPipeline.title = template.title + "模版流水线"
newPipeline.templateId = template.id newPipeline.templateId = template.id
newPipeline.isTemplate = true
const pipelineJson: Pipeline = JSON.parse(newPipeline.content) const pipelineJson: Pipeline = JSON.parse(newPipeline.content)
delete pipelineJson.triggers delete pipelineJson.triggers
@ -86,6 +87,7 @@ export class TemplateService extends BaseService<TemplateEntity> {
pipeline, pipeline,
} }
} }
async batchDelete(ids: number[], userId: number) { async batchDelete(ids: number[], userId: number) {
const where: any = { const where: any = {
@ -100,5 +102,25 @@ export class TemplateService extends BaseService<TemplateEntity> {
await this.delete(ids); await this.delete(ids);
await this.pipelineService.batchDelete(pipelineIds, userId) await this.pipelineService.batchDelete(pipelineIds, userId)
} }
async createPipelineByTemplate(body: PipelineEntity) {
const templateId = body.templateId;
const template = await this.info(templateId);
if (!template && template.userId !== body.userId) {
throw new Error('模板不存在');
}
const tempPipeline = await this.pipelineService.info(template.pipelineId)
const newPipeline = {
type: tempPipeline.type,
from : "template",
keepHistoryCount: tempPipeline.keepHistoryCount,
... body,
}
await this.pipelineService.save(newPipeline)
}
} }