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 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 moreParams = [];
for (const plugin of certPlugins) {
for (const inputKey in plugin.input) {
if (inputs[inputKey]) {
//如果两个插件有的字段,直接显示
inputs[inputKey].form.show = true;
continue;
}
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;
}),
},
};
const doSubmit = req.doSubmit;
for (const inputKey in req.certPlugin.input) {
// inputs[inputKey].form.show = true;
const inputDefine = cloneDeep(req.certPlugin.input[inputKey]);
if (inputDefine.maybeNeed) {
moreParams.push(inputKey);
}
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
},
};
}
const pluginStore = usePluginStore();
@@ -146,7 +123,7 @@ export function useCertPipelineCreator() {
wrapClassName: "cert_pipeline_create_form",
width: 1350,
saveRemind: false,
title: t("certd.pipelineForm.createTitle"),
title: req.title || t("certd.pipelineForm.createTitle"),
},
group: {
groups: {
@@ -159,44 +136,44 @@ export function useCertPipelineCreator() {
},
},
columns: {
certApplyPlugin: {
title: t("certd.plugin.selectTitle"),
type: "dict-select",
dict: dict({
data: [
{ value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" },
{ value: "CertApplyGetFormAliyun", label: "Aliyun-Order" },
],
}),
form: {
order: 0,
value: "CertApply",
helper: {
render: () => {
return (
<ul>
<li>{t("certd.plugin.jsAcme")}</li>
<li>{t("certd.plugin.legoAcme")}</li>
<li>{t("certd.plugin.aliyunOrder")}</li>
</ul>
);
},
},
valueChange: {
handle: async ({ form, value }) => {
const config = await pluginStore.getPluginConfig({
name: value,
type: "builtIn",
});
if (config.sysSetting?.input) {
merge(form, config.sysSetting.input);
}
},
immediate: true,
},
},
},
// certApplyPlugin: {
// title: t("certd.plugin.selectTitle"),
// type: "dict-select",
// dict: dict({
// data: [
// { value: "CertApply", label: "JS-ACME" },
// { value: "CertApplyLego", label: "Lego-ACME" },
// { value: "CertApplyGetFormAliyun", label: "Aliyun-Order" },
// ],
// }),
// form: {
// order: 0,
// value: "CertApply",
// helper: {
// render: () => {
// return (
// <ul>
// <li>{t("certd.plugin.jsAcme")}</li>
// <li>{t("certd.plugin.legoAcme")}</li>
// <li>{t("certd.plugin.aliyunOrder")}</li>
// </ul>
// );
// },
// },
// valueChange: {
// handle: async ({ form, value }) => {
// const config = await pluginStore.getPluginConfig({
// name: value,
// type: "builtIn",
// });
// if (config.sysSetting?.input) {
// merge(form, config.sysSetting.input);
// }
// },
// immediate: true,
// },
// },
// },
...inputs,
triggerCron: {
title: t("certd.pipelineForm.triggerCronTitle"),
@@ -336,18 +313,10 @@ export function useCertPipelineCreator() {
return certPlugins;
}
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
async function openAddCertdPipelineDialog(req: { pluginName: string; defaultGroupId?: number; title?: string }) {
//检查是否流水线数量超出限制
await checkPipelineLimit();
const wrapperRef = ref();
function getFormData() {
if (!wrapperRef.value) {
return null;
}
return wrapperRef.value.getFormData();
}
async function doSubmit({ form }: any) {
// const certDetail = readCertDetail(form.cert.crt);
// 添加certd pipeline
@@ -375,7 +344,7 @@ export function useCertPipelineCreator() {
strategy: {
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" } });
}
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
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
const wrapper = await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
await openCrudFormDialog({ crudOptions });
}
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 { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { Modal, notification } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
import dayjs from "dayjs";
import * as api from "./api";
import { GetDetail } from "./api";
import { groupDictRef } from "./group/dicts";
import { useSettingStore } from "/@/store/settings";
import { cloneDeep } from "lodash-es";
import { eachStages } from "./utils";
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
import { useUserStore } from "/@/store/user";
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 { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { useCertViewer } from "/@/views/certd/pipeline/use";
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 lastResRef = ref();
const { t } = useI18n();
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
const { openUploadCreateDialog } = useCertUpload();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@@ -108,7 +105,8 @@ export default function ({ crudExpose, context: { selectedRowKeys } }: CreateCru
actionbar: {
buttons: {
add: {
order: 5,
order: 99,
show: false,
icon: "ion:ios-add-circle-outline",
text: t("certd.customPipeline"),
},
@@ -118,9 +116,7 @@ export default function ({ crudExpose, context: { selectedRowKeys } }: CreateCru
type: "primary",
icon: "ion:ios-add-circle-outline",
click() {
const searchForm = crudExpose.getSearchValidatedFormData();
const defaultGroupId = searchForm.groupId;
openAddCertdPipelineDialog({ defaultGroupId });
openCertApplyDialog({ key: "CertApply" });
},
},
uploadCert: {
@@ -9,6 +9,40 @@
</template>
</a-alert> -->
<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 class="batch-actions-inner">
<span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span>
@@ -19,7 +53,6 @@
<change-trigger :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger>
</div>
</div>
<template #actionbar-right> </template>
<template #form-bottom>
<div>{{ t("certd.applyCertificate") }}</div>
</template>
@@ -28,7 +61,7 @@
</template>
<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 createCrudOptions from "./crud";
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 { useSettingStore } from "/@/store/settings";
import { groupDictRef } from "./group/dicts";
import { useCertPipelineCreator } from "./certd-form/use";
defineOptions({
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>
<style lang="less">
.batch-actions {
+1 -1
View File
@@ -1,2 +1,2 @@
LEGO_VERSION=4.30.1
certd_plugin_loadmode=metadata
certd_plugin_loadmode=dev
@@ -123,25 +123,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
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({
title: "DNS解析服务商",
@@ -227,6 +209,27 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
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({
title: "Google公共EAB授权",
isSys: true,
@@ -319,6 +322,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
],
},
helper: "如无特殊需求,默认即可\n选择RSA 2048 pkcs1可以获得旧版RSA证书",
maybeNeed: false,
required: true,
})
privateKeyType!: PrivateKeyType;
@@ -337,6 +341,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
},
helper: "如无特殊需求,默认即可",
required: false,
maybeNeed: true,
mergeScript: `
return {
show: ctx.compute(({form})=>{
@@ -356,6 +361,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
},
helper: preferredChainConfigs.letsencrypt.helper,
required: false,
maybeNeed: true,
mergeScript: preferredChainMergeScript,
})
preferredChain!: string;
@@ -367,6 +373,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-switch",
vModel: "checked",
},
maybeNeed: true,
helper: "如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项\n默认情况会进行测试,如果无法访问,将会自动使用代理",
})
useProxy = false;
@@ -376,6 +383,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
component: {
placeholder: "google.yourproxy.com",
},
maybeNeed: true,
helper: "填写你的自定义反代地址,不要带http://\nletsencrypt反代目标:acme-v02.api.letsencrypt.org\ngoogle反代目标:dv.acme-v02.api.pki.goog",
})
reverseProxy = "";
@@ -387,6 +395,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-switch",
vModel: "checked",
},
maybeNeed: true,
helper: "跳过本地校验可以加快申请速度,同时也会增加失败概率。",
})
skipLocalVerify = false;
@@ -398,6 +407,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-input-number",
vModel: "value",
},
maybeNeed: true,
helper: "检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值",
})
maxCheckRetryCount = 20;
@@ -409,6 +419,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-input-number",
vModel: "value",
},
maybeNeed: true,
helper: "等待解析生效时长(秒),如果使用CNAME方式校验,本地验证失败,可以尝试延长此时间(比如5-10分钟)",
})
waitDnsDiffuseTime = 30;
@@ -39,7 +39,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
},
required: false,
order: 100,
helper: "转换成PFX、jks格式证书是否需要加密\njks必须设置密码,不传则默认123456\npfx不传则为空密码",
helper: "转换成PFX、jks格式证书是否需要加密\n不传则pfx格式默认空密码,jks格式默认123456",
})
pfxPassword!: string;
@@ -57,6 +57,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
},
required: false,
order: 100,
maybeNeed: true,
helper: "兼容Windows Server各个版本",
})
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
@@ -39,6 +39,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
vModel: "checked",
},
order: 100,
maybeNeed: true,
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
})
successNotify = false;
@@ -71,7 +71,6 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
name: "access-selector",
type: "eab",
},
maybeNeed: true,
helper: "如果需要提供EAB授权",
})
legoEabAccessId!: number;