diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts index a8c4e484..e83fcbe9 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts @@ -8,7 +8,7 @@ export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success"; export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { @TaskInput({ - title: "域名", + title: "证书域名", component: { name: "a-select", vModel: "value", diff --git a/packages/ui/certd-client/src/components/plugins/common/api-test.vue b/packages/ui/certd-client/src/components/plugins/common/api-test.vue index f7d44cb6..f82727da 100644 --- a/packages/ui/certd-client/src/components/plugins/common/api-test.vue +++ b/packages/ui/certd-client/src/components/plugins/common/api-test.vue @@ -19,7 +19,9 @@ defineOptions({ }); const getScope: any = inject("get:scope"); -const getPluginType: any = inject("get:plugin:type"); +const getPluginType: any = inject("get:plugin:type", () => { + return "access"; +}); const formItemContext = Form.useInjectFormItemContext(); const props = defineProps<{} & ComponentPropsType>(); diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-input.vue b/packages/ui/certd-client/src/components/plugins/common/remote-input.vue index 6a1e1c48..ccc2e415 100644 --- a/packages/ui/certd-client/src/components/plugins/common/remote-input.vue +++ b/packages/ui/certd-client/src/components/plugins/common/remote-input.vue @@ -27,7 +27,9 @@ const emit = defineEmits<{ }>(); const getScope: any = inject("get:scope"); -const getPluginType: any = inject("get:plugin:type"); +const getPluginType: any = inject("get:plugin:type", () => { + return "plugin"; +}); const attrs = useAttrs(); diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue index 377a2162..a8e55d97 100644 --- a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue +++ b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue @@ -69,9 +69,15 @@ const emit = defineEmits<{ const attrs = useAttrs(); -const getCurrentPluginDefine: any = inject("getCurrentPluginDefine"); -const getScope: any = inject("get:scope"); -const getPluginType: any = inject("get:plugin:type"); +const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => { + return {}; +}); +const getScope: any = inject("get:scope", () => { + return {}; +}); +const getPluginType: any = inject("get:plugin:type", () => { + return "plugin"; +}); const searchKeyRef = ref(""); const optionsRef = ref([]); @@ -96,7 +102,7 @@ const getOptions = async () => { } const pluginType = getPluginType(); const { form } = getScope(); - const input = pluginType === "plugin" ? form.input : form; + const input = (pluginType === "plugin" ? form?.input : form) || {}; for (let key in define.input) { const inWatches = props.watches.includes(key); @@ -186,7 +192,7 @@ watch( () => { const pluginType = getPluginType(); const { form, key } = getScope(); - const input = pluginType === "plugin" ? form.input : form; + const input = (pluginType === "plugin" ? form?.input : form) || {}; const watches = {}; for (const key of props.watches) { watches[key] = input[key]; @@ -198,10 +204,11 @@ watch( }, async (value, oldValue) => { const { form } = value; - const oldForm = oldValue.form; + const oldForm: any = oldValue?.form; let changed = oldForm == null || optionsRef.value.length == 0; for (const key of props.watches) { - if (form[key] != oldForm[key]) { + //@ts-ignore + if (oldForm && form[key] != oldForm[key]) { changed = true; break; } diff --git a/packages/ui/certd-client/src/components/plugins/synology/device-id-getter.vue b/packages/ui/certd-client/src/components/plugins/synology/device-id-getter.vue index b81ac2d9..c59412b6 100644 --- a/packages/ui/certd-client/src/components/plugins/synology/device-id-getter.vue +++ b/packages/ui/certd-client/src/components/plugins/synology/device-id-getter.vue @@ -27,7 +27,9 @@ const attrs = useAttrs(); const otpCodeRef = ref(""); const getScope: any = inject("get:scope"); -const getPluginType: any = inject("get:plugin:type"); +const getPluginType: any = inject("get:plugin:type", () => { + return "access"; +}); async function loginWithOTPCode(otpCode: string) { const { form } = getScope(); diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index cc00711a..46b42361 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -61,6 +61,15 @@ export const certdResources = [ isMenu: false, }, }, + { + title: "流水线模版批量创建", + name: "PipelineTemplateImport", + path: "/certd/pipeline/template/import", + component: "/certd/pipeline/template/import/index.vue", + meta: { + isMenu: false, + }, + }, { title: "证书仓库", name: "CertStore", diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/template/crud.tsx index 96a4990f..4b682b4c 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/template/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/crud.tsx @@ -4,6 +4,7 @@ import { useRouter } from "vue-router"; import { useModal } from "/@/use/use-modal"; import createCrudOptionsPipeline from "../crud"; import * as pipelineApi from "../api"; +import { useTemplate } from "/@/views/certd/pipeline/template/use"; export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { const api = templateApi; const pageRequest = async (query: UserPageQuery): Promise => { @@ -29,6 +30,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const router = useRouter(); const model = useModal(); + + const { openCreateFromTemplateDialog } = useTemplate(); + return { crudOptions: { request: { @@ -73,6 +77,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat buttons: { edit: { show: false }, copy: { show: false }, + use: { + text: null, + title: "使用此模版创建流水线", + icon: "ion:duplicate-outline", + click({ row }) { + openCreateFromTemplateDialog({ + templateId: row.id, + onCreated: ({ id }) => { + router.push({ path: "/certd/pipeline/detail", query: { id, editMode: true } }); + }, + }); + }, + }, + import: { + text: null, + title: "批量导入创建", + icon: "ion:duplicate", + click({ row }) { + router.push({ path: "/certd/pipeline/template/import", query: { templateId: row.id } }); + }, + }, }, }, columns: { diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/import/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/template/import/crud.tsx new file mode 100644 index 00000000..8eb3cb63 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/import/crud.tsx @@ -0,0 +1,80 @@ +import { CreateCrudOptionsProps, CreateCrudOptionsRet, importTable } from "@fast-crud/fast-crud"; +import { Modal, notification } from "ant-design-vue"; + +export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { + return { + crudOptions: { + mode: { + name: "local", + isMergeWhenUpdate: true, + isAppendWhenAdd: true, + }, + //启用addRow按钮 + actionbar: { + buttons: { + //禁用弹框添加 + add: { show: false }, + //启用添加行 + addRow: { show: true }, + //导入按钮 + import: { + show: false, + text: "批量导入", + type: "primary", + click() { + const modal = Modal.info({ + title: "批量导入", + okText: "关闭", + content() { + async function onChange(e: any) { + const file = e.target.files[0]; + await importTable(crudExpose, { file, append: true }); + modal.destroy(); + notification.success({ + message: "导入成功", + }); + } + return ( +
+

+ 1、下载导入模板 +

+

+ 2、模板填充数据 +

+

+ 3、导入: + +

+
+ ); + }, + }); + }, + }, + }, + }, + table: { + remove: { + //删除数据后不请求后台 + refreshTable: false, + }, + editable: { + enabled: true, + mode: "row", + activeTrigger: false, + }, + }, + search: { + show: false, + }, + toolbar: { + show: false, + }, + pagination: { + show: false, + }, + columns: {}, + }, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/import/form.tsx b/packages/ui/certd-client/src/views/certd/pipeline/template/import/form.tsx new file mode 100644 index 00000000..fbcc18bc --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/import/form.tsx @@ -0,0 +1,30 @@ +import { useColumns } from "@fast-crud/fast-crud"; +import { createExtraColumns } from "/@/views/certd/pipeline/template/use"; +import TemplateImportTable from "/@/views/certd/pipeline/template/import/table.vue"; +import { Ref } from "vue"; + +export function createFormOptions(detail: Ref): any { + const { buildFormOptions } = useColumns(); + + const crudOptions = { + columns: { + ...createExtraColumns(), + templateProps: { + title: "流水线导入", + type: "text", + form: { + order: 1, + component: { + name: TemplateImportTable, + detail: detail, + }, + col: { + span: 24, + }, + }, + }, + }, + }; + + return buildFormOptions(crudOptions); +} diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/import/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/template/import/index.vue new file mode 100644 index 00000000..b58cb55d --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/import/index.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/import/table.vue b/packages/ui/certd-client/src/views/certd/pipeline/template/import/table.vue new file mode 100644 index 00000000..32bfc4f6 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/import/table.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/certd/pipeline/template/use.tsx b/packages/ui/certd-client/src/views/certd/pipeline/template/use.tsx index b210319b..ab8fe4a9 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/template/use.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/template/use.tsx @@ -8,10 +8,69 @@ import { ref } from "vue"; import { fillPipelineByDefaultForm } from "/@/views/certd/pipeline/certd-form/use"; import { cloneDeep } from "lodash-es"; +export function createExtraColumns() { + const groupDictRef = dict({ + url: "/pi/pipeline/group/all", + value: "id", + label: "name", + }); + const randomHour = Math.floor(Math.random() * 6); + const randomMin = Math.floor(Math.random() * 60); + return { + triggerCron: { + title: "定时触发", + type: "text", + form: { + value: `0 ${randomMin} ${randomHour} * * *`, + component: { + name: "cron-editor", + vModel: "modelValue", + placeholder: "0 0 4 * * *", + }, + col: { + span: 24, + }, + 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: 999, + }, + }, + }; +} + export function useTemplate() { const { openCrudFormDialog } = useFormWrapper(); - async function openCreateFromTemplateDialog(req: { templateId?: number }) { + async function openCreateFromTemplateDialog(req: { templateId?: number; onCreated?: (ctx: any) => void }) { //检查是否流水线数量超出限制 await checkPipelineLimit(); const detail = await templateApi.GetDetail(req.templateId); @@ -24,12 +83,6 @@ export function useTemplate() { 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) { @@ -38,8 +91,6 @@ export function useTemplate() { return wrapperRef.value.getFormData(); } - const randomHour = Math.floor(Math.random() * 6); - const randomMin = Math.floor(Math.random() * 60); const templateFormRef = ref(); async function doSubmit(opts: { form: any }) { @@ -67,13 +118,16 @@ export function useTemplate() { const title = form.title; newPipeline.title = title; const groupId = form.groupId; - await templateApi.CreatePipelineByTemplate({ + const { id } = await templateApi.CreatePipelineByTemplate({ title, content: JSON.stringify(newPipeline), keepHistoryCount: 30, groupId, templateId: detail.template.id, }); + if (req.onCreated) { + req.onCreated({ id }); + } } const crudOptions = { @@ -104,50 +158,7 @@ export function useTemplate() { rules: [{ required: true, message: "请输入流水线标题" }], }, }, - 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: 99, - }, - }, + ...createExtraColumns(), }, }; diff --git a/packages/ui/certd-server/src/modules/pipeline/service/template-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/template-service.ts index 2e80e016..cbf97fb0 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/template-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/template-service.ts @@ -48,10 +48,10 @@ export class TemplateService extends BaseService { newPipeline.title = template.title + "模版流水线" newPipeline.templateId = template.id newPipeline.isTemplate = true + newPipeline.userId = template.userId 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) @@ -121,6 +121,8 @@ export class TemplateService extends BaseService { } await this.pipelineService.save(newPipeline) + + return newPipeline } }