perf: 优化流水线创建入口,各种证书申请任务类型拆分成多个按钮

This commit is contained in:
xiaojunnuo
2026-01-21 13:27:14 +08:00
parent 418bcddc95
commit f75c73d739
8 changed files with 168 additions and 130 deletions
@@ -85,46 +85,23 @@ export function useCertPipelineCreator() {
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const router = useRouter(); const router = useRouter();
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet { function createCrudOptions(req: { certPlugin: any; doSubmit: any; title?: string }): CreateCrudOptionsRet {
const inputs: any = {}; const inputs: any = {};
const moreParams = []; const moreParams = [];
for (const plugin of certPlugins) { const doSubmit = req.doSubmit;
for (const inputKey in plugin.input) { for (const inputKey in req.certPlugin.input) {
if (inputs[inputKey]) { // inputs[inputKey].form.show = true;
//如果两个插件有的字段,直接显示 const inputDefine = cloneDeep(req.certPlugin.input[inputKey]);
inputs[inputKey].form.show = true; if (inputDefine.maybeNeed) {
continue; moreParams.push(inputKey);
}
const inputDefine = cloneDeep(plugin.input[inputKey]);
if (!inputDefine.required && !inputDefine.maybeNeed) {
moreParams.push(inputKey);
// continue;
}
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
show: compute(ctx => {
const form = getFormData();
if (!form) {
return false;
}
let inputDefineShow = true;
if (inputDefine.show != null) {
const computeShow = inputDefine.show as any;
if (computeShow === false) {
inputDefineShow = false;
} else if (computeShow && computeShow.computeFn) {
inputDefineShow = computeShow.computeFn({ form });
}
}
return form?.certApplyPlugin === plugin.name && inputDefineShow;
}),
},
};
} }
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
},
};
} }
const pluginStore = usePluginStore(); const pluginStore = usePluginStore();
@@ -146,7 +123,7 @@ export function useCertPipelineCreator() {
wrapClassName: "cert_pipeline_create_form", wrapClassName: "cert_pipeline_create_form",
width: 1350, width: 1350,
saveRemind: false, saveRemind: false,
title: t("certd.pipelineForm.createTitle"), title: req.title || t("certd.pipelineForm.createTitle"),
}, },
group: { group: {
groups: { groups: {
@@ -159,44 +136,44 @@ export function useCertPipelineCreator() {
}, },
}, },
columns: { columns: {
certApplyPlugin: { // certApplyPlugin: {
title: t("certd.plugin.selectTitle"), // title: t("certd.plugin.selectTitle"),
type: "dict-select", // type: "dict-select",
dict: dict({ // dict: dict({
data: [ // data: [
{ value: "CertApply", label: "JS-ACME" }, // { value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" }, // { value: "CertApplyLego", label: "Lego-ACME" },
{ value: "CertApplyGetFormAliyun", label: "Aliyun-Order" }, // { value: "CertApplyGetFormAliyun", label: "Aliyun-Order" },
], // ],
}), // }),
form: { // form: {
order: 0, // order: 0,
value: "CertApply", // value: "CertApply",
helper: { // helper: {
render: () => { // render: () => {
return ( // return (
<ul> // <ul>
<li>{t("certd.plugin.jsAcme")}</li> // <li>{t("certd.plugin.jsAcme")}</li>
<li>{t("certd.plugin.legoAcme")}</li> // <li>{t("certd.plugin.legoAcme")}</li>
<li>{t("certd.plugin.aliyunOrder")}</li> // <li>{t("certd.plugin.aliyunOrder")}</li>
</ul> // </ul>
); // );
}, // },
}, // },
valueChange: { // valueChange: {
handle: async ({ form, value }) => { // handle: async ({ form, value }) => {
const config = await pluginStore.getPluginConfig({ // const config = await pluginStore.getPluginConfig({
name: value, // name: value,
type: "builtIn", // type: "builtIn",
}); // });
if (config.sysSetting?.input) { // if (config.sysSetting?.input) {
merge(form, config.sysSetting.input); // merge(form, config.sysSetting.input);
} // }
}, // },
immediate: true, // immediate: true,
}, // },
}, // },
}, // },
...inputs, ...inputs,
triggerCron: { triggerCron: {
title: t("certd.pipelineForm.triggerCronTitle"), title: t("certd.pipelineForm.triggerCronTitle"),
@@ -336,18 +313,10 @@ export function useCertPipelineCreator() {
return certPlugins; return certPlugins;
} }
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) { async function openAddCertdPipelineDialog(req: { pluginName: string; defaultGroupId?: number; title?: string }) {
//检查是否流水线数量超出限制 //检查是否流水线数量超出限制
await checkPipelineLimit(); await checkPipelineLimit();
const wrapperRef = ref();
function getFormData() {
if (!wrapperRef.value) {
return null;
}
return wrapperRef.value.getFormData();
}
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
@@ -375,7 +344,7 @@ export function useCertPipelineCreator() {
strategy: { strategy: {
runStrategy: 0, // 正常执行 runStrategy: 0, // 正常执行
}, },
type: form.certApplyPlugin, type: req.pluginName,
}, },
], ],
}, },
@@ -410,11 +379,19 @@ export function useCertPipelineCreator() {
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } }); router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
} }
const certPlugins = await getCertPlugins(); const certPlugins = await getCertPlugins();
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit); const certPlugin = certPlugins.find(plugin => plugin.name === req.pluginName);
if (!certPlugin) {
message.error("该证书申请插件不存在");
return;
}
const { crudOptions } = createCrudOptions({
certPlugin,
doSubmit,
title: req.title,
});
//@ts-ignore //@ts-ignore
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined; crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
const wrapper = await openCrudFormDialog({ crudOptions }); await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
} }
return { return {
@@ -1,29 +1,26 @@
import * as api from "./api"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
import { Modal, notification } from "ant-design-vue";
import dayjs from "dayjs";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud"; import * as api from "./api";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status"; import { GetDetail } from "./api";
import { Modal, notification } from "ant-design-vue"; import { groupDictRef } from "./group/dicts";
import { useUserStore } from "/@/store/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { cloneDeep } from "lodash-es"; import { useUserStore } from "/@/store/user";
import { eachStages } from "./utils";
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use"; import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import { setRunnableIds } from "/@/views/certd/pipeline/certd-form/use";
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue"; import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { useCertViewer } from "/@/views/certd/pipeline/use"; import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { GetDetail, GetObj } from "./api";
import { groupDictRef } from "./group/dicts";
export default function ({ crudExpose, context: { selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context: { selectedRowKeys, openCertApplyDialog } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
const lastResRef = ref(); const lastResRef = ref();
const { t } = useI18n(); const { t } = useI18n();
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
const { openUploadCreateDialog } = useCertUpload(); const { openUploadCreateDialog } = useCertUpload();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@@ -108,7 +105,8 @@ export default function ({ crudExpose, context: { selectedRowKeys } }: CreateCru
actionbar: { actionbar: {
buttons: { buttons: {
add: { add: {
order: 5, order: 99,
show: false,
icon: "ion:ios-add-circle-outline", icon: "ion:ios-add-circle-outline",
text: t("certd.customPipeline"), text: t("certd.customPipeline"),
}, },
@@ -118,9 +116,7 @@ export default function ({ crudExpose, context: { selectedRowKeys } }: CreateCru
type: "primary", type: "primary",
icon: "ion:ios-add-circle-outline", icon: "ion:ios-add-circle-outline",
click() { click() {
const searchForm = crudExpose.getSearchValidatedFormData(); openCertApplyDialog({ key: "CertApply" });
const defaultGroupId = searchForm.groupId;
openAddCertdPipelineDialog({ defaultGroupId });
}, },
}, },
uploadCert: { uploadCert: {
@@ -9,6 +9,40 @@
</template> </template>
</a-alert> --> </a-alert> -->
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right="scope">
<a-dropdown class="ml-1">
<a-button type="primary" class="ant-dropdown-link" @click.prevent>
更多流水线
<DownOutlined />
</a-button>
<template #overlay>
<a-menu @click="openCertApplyDialog">
<!-- <a-menu-item key="CertApplyUpload" class="flex items-center">
<fs-icon icon="ion:business-outline" />
商用证书托管流水线
</a-menu-item> -->
<a-menu-item key="CertApplyGetFormAliyun">
<div class="flex items-center">
<fs-icon icon="svg:icon-aliyun" />
<span class="ml-2">阿里云订阅证书流水线</span>
</div>
</a-menu-item>
<a-menu-item key="CertApplyLego">
<div class="flex items-center">
<fs-icon icon="cbi:lego" />
<span class="ml-2">Lego申请证书流水线</span>
</div>
</a-menu-item>
<a-menu-item key="AddPipeline">
<div class="flex items-center">
<fs-icon icon="ion:add-circle-outline" />
<span class="ml-2">自定义流水线</span>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<div v-if="selectedRowKeys.length > 0" class="batch-actions"> <div v-if="selectedRowKeys.length > 0" class="batch-actions">
<div class="batch-actions-inner"> <div class="batch-actions-inner">
<span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span> <span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span>
@@ -19,7 +53,6 @@
<change-trigger :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger> <change-trigger :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger>
</div> </div>
</div> </div>
<template #actionbar-right> </template>
<template #form-bottom> <template #form-bottom>
<div>{{ t("certd.applyCertificate") }}</div> <div>{{ t("certd.applyCertificate") }}</div>
</template> </template>
@@ -28,7 +61,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onActivated, onMounted, ref } from "vue"; import { computed, onActivated, onMounted, ref } from "vue";
import { dict, useFs } from "@fast-crud/fast-crud"; import { dict, useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import ChangeGroup from "./components/change-group.vue"; import ChangeGroup from "./components/change-group.vue";
@@ -42,6 +75,7 @@ const { t } = useI18n();
import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue"; import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { groupDictRef } from "./group/dicts"; import { groupDictRef } from "./group/dicts";
import { useCertPipelineCreator } from "./certd-form/use";
defineOptions({ defineOptions({
name: "PipelineManager", name: "PipelineManager",
@@ -81,6 +115,25 @@ function batchDelete() {
}, },
}); });
} }
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
const addMorePipelineBtns = computed(() => {
return [
{ key: "CertApplyGetFormAliyun", title: t("certd.aliyunSubscriptionPipeline"), icon: "svg:icon-aliyun" },
{ key: "CertApplyLego", title: t("certd.legoApplicationPipeline"), icon: "cbi:lego" },
{ key: "AddPipeline", title: t("certd.customPipeline"), icon: "ion:add-circle-outline" },
];
});
function openCertApplyDialog(req: { key: string; title: string }) {
if (req.key === "AddPipeline") {
crudExpose.openAdd({});
return;
}
const searchForm = crudExpose.getSearchValidatedFormData();
const defaultGroupId = searchForm.groupId;
openAddCertdPipelineDialog({ pluginName: req.key, defaultGroupId, title: req.title });
}
context.openCertApplyDialog = openCertApplyDialog;
</script> </script>
<style lang="less"> <style lang="less">
.batch-actions { .batch-actions {
+1 -1
View File
@@ -1,2 +1,2 @@
LEGO_VERSION=4.30.1 LEGO_VERSION=4.30.1
certd_plugin_loadmode=metadata certd_plugin_loadmode=dev
@@ -123,25 +123,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}) })
challengeType!: string; challengeType!: string;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt(免费,新手推荐,支持IP证书)", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google(免费)", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL(免费)", icon: "emojione:digit-zero" },
{ value: "litessl", label: "litessl(免费)", icon: "roentgen:free" },
{ value: "sslcom", label: "SSL.com(仅主域名和www免费)", icon: "la:expeditedssl" },
{ value: "letsencrypt_staging", label: "Let's Encrypt测试环境(仅供测试)", icon: "simple-icons:letsencrypt" },
],
},
helper: "Let's Encrypt:申请最简单\nGoogle:大厂光环,兼容性好,仅首次需要翻墙获取EAB授权\nZeroSSL:需要EAB授权,无需翻墙\nSSL.com:仅主域名和www免费,必须设置CAA记录",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({ @TaskInput({
title: "DNS解析服务商", title: "DNS解析服务商",
@@ -227,6 +209,27 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}) })
domainsVerifyPlan!: DomainsVerifyPlanInput; domainsVerifyPlan!: DomainsVerifyPlanInput;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt(免费,新手推荐,支持IP证书)", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google(免费)", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL(免费)", icon: "emojione:digit-zero" },
{ value: "litessl", label: "litessl(免费)", icon: "roentgen:free" },
{ value: "sslcom", label: "SSL.com(仅主域名和www免费)", icon: "la:expeditedssl" },
{ value: "letsencrypt_staging", label: "Let's Encrypt测试环境(仅供测试)", icon: "simple-icons:letsencrypt" },
],
},
helper: "Let's Encrypt:申请最简单\nGoogle:大厂光环,兼容性好,仅首次需要翻墙获取EAB授权\nZeroSSL:需要EAB授权,无需翻墙\nSSL.com:仅主域名和www免费,必须设置CAA记录",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({ @TaskInput({
title: "Google公共EAB授权", title: "Google公共EAB授权",
isSys: true, isSys: true,
@@ -319,6 +322,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
], ],
}, },
helper: "如无特殊需求,默认即可\n选择RSA 2048 pkcs1可以获得旧版RSA证书", helper: "如无特殊需求,默认即可\n选择RSA 2048 pkcs1可以获得旧版RSA证书",
maybeNeed: false,
required: true, required: true,
}) })
privateKeyType!: PrivateKeyType; privateKeyType!: PrivateKeyType;
@@ -337,6 +341,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}, },
helper: "如无特殊需求,默认即可", helper: "如无特殊需求,默认即可",
required: false, required: false,
maybeNeed: true,
mergeScript: ` mergeScript: `
return { return {
show: ctx.compute(({form})=>{ show: ctx.compute(({form})=>{
@@ -356,6 +361,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}, },
helper: preferredChainConfigs.letsencrypt.helper, helper: preferredChainConfigs.letsencrypt.helper,
required: false, required: false,
maybeNeed: true,
mergeScript: preferredChainMergeScript, mergeScript: preferredChainMergeScript,
}) })
preferredChain!: string; preferredChain!: string;
@@ -367,6 +373,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-switch", name: "a-switch",
vModel: "checked", vModel: "checked",
}, },
maybeNeed: true,
helper: "如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项\n默认情况会进行测试,如果无法访问,将会自动使用代理", helper: "如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项\n默认情况会进行测试,如果无法访问,将会自动使用代理",
}) })
useProxy = false; useProxy = false;
@@ -376,6 +383,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
component: { component: {
placeholder: "google.yourproxy.com", placeholder: "google.yourproxy.com",
}, },
maybeNeed: true,
helper: "填写你的自定义反代地址,不要带http://\nletsencrypt反代目标:acme-v02.api.letsencrypt.org\ngoogle反代目标:dv.acme-v02.api.pki.goog", helper: "填写你的自定义反代地址,不要带http://\nletsencrypt反代目标:acme-v02.api.letsencrypt.org\ngoogle反代目标:dv.acme-v02.api.pki.goog",
}) })
reverseProxy = ""; reverseProxy = "";
@@ -387,6 +395,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-switch", name: "a-switch",
vModel: "checked", vModel: "checked",
}, },
maybeNeed: true,
helper: "跳过本地校验可以加快申请速度,同时也会增加失败概率。", helper: "跳过本地校验可以加快申请速度,同时也会增加失败概率。",
}) })
skipLocalVerify = false; skipLocalVerify = false;
@@ -398,6 +407,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-input-number", name: "a-input-number",
vModel: "value", vModel: "value",
}, },
maybeNeed: true,
helper: "检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值", helper: "检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值",
}) })
maxCheckRetryCount = 20; maxCheckRetryCount = 20;
@@ -409,6 +419,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-input-number", name: "a-input-number",
vModel: "value", vModel: "value",
}, },
maybeNeed: true,
helper: "等待解析生效时长(秒),如果使用CNAME方式校验,本地验证失败,可以尝试延长此时间(比如5-10分钟)", helper: "等待解析生效时长(秒),如果使用CNAME方式校验,本地验证失败,可以尝试延长此时间(比如5-10分钟)",
}) })
waitDnsDiffuseTime = 30; waitDnsDiffuseTime = 30;
@@ -39,7 +39,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
}, },
required: false, required: false,
order: 100, order: 100,
helper: "转换成PFX、jks格式证书是否需要加密\njks必须设置密码,不传则默认123456\npfx不传则为空密码", helper: "转换成PFX、jks格式证书是否需要加密\n不传则pfx格式默认空密码,jks格式默认123456",
}) })
pfxPassword!: string; pfxPassword!: string;
@@ -57,6 +57,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
}, },
required: false, required: false,
order: 100, order: 100,
maybeNeed: true,
helper: "兼容Windows Server各个版本", helper: "兼容Windows Server各个版本",
}) })
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES"; pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
@@ -39,6 +39,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
vModel: "checked", vModel: "checked",
}, },
order: 100, order: 100,
maybeNeed: true,
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道", helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
}) })
successNotify = false; successNotify = false;
@@ -71,7 +71,6 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
name: "access-selector", name: "access-selector",
type: "eab", type: "eab",
}, },
maybeNeed: true,
helper: "如果需要提供EAB授权", helper: "如果需要提供EAB授权",
}) })
legoEabAccessId!: number; legoEabAccessId!: number;