perf: 优化证书流水线创建,支持选择分组

pull/409/head
xiaojunnuo 2025-04-19 16:05:24 +08:00
parent 5750bb7067
commit d613aa8f3e
11 changed files with 400 additions and 298 deletions

View File

@ -7,6 +7,7 @@ import { routerUtils } from "./util.router";
import { treeUtils } from "./util.tree";
import { hashUtils } from "./util.hash";
import { amountUtils } from "./util.amount";
import { cache } from "./util.cache";
export const util = {
...envs,
...sites,
@ -17,5 +18,6 @@ export const util = {
tree: treeUtils,
hash: hashUtils,
amount: amountUtils,
cache,
};
export const utils = util;

View File

@ -0,0 +1,17 @@
export class Cache {
bucket: Record<string, any> = {};
async get(key: string) {
return this.bucket[key];
}
async set(key: string, value: any, ttl: number) {
this.bucket[key] = value;
}
async del(key: string) {
delete this.bucket[key];
}
}
export const cache = new Cache();

View File

@ -7,7 +7,7 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
data: query,
});
},
@ -15,7 +15,7 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
data: obj,
});
},
@ -23,7 +23,7 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
data: obj,
});
},
@ -31,7 +31,7 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
params: { id },
});
},
@ -39,14 +39,14 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
params: { id },
});
},
async GetOptions(id: number) {
return await request({
url: apiPrefix + "/options",
method: "post"
method: "post",
});
},
@ -54,14 +54,14 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/setDefault",
method: "post",
params: { id }
params: { id },
});
},
async GetDefaultId() {
return await request({
url: apiPrefix + "/getDefaultId",
method: "post"
method: "post",
});
},
@ -69,14 +69,14 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/simpleInfo",
method: "post",
params: { id }
params: { id },
});
},
async GetDefineTypes() {
return await request({
url: apiPrefix + "/getTypeDict",
method: "post"
method: "post",
});
},
@ -84,7 +84,7 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/define",
method: "post",
params: { type }
params: { type },
});
},
@ -92,15 +92,15 @@ export function createNotificationApi() {
return await request({
url: apiPrefix + "/defineByType",
method: "post",
params: { type }
params: { type },
});
},
async GetOrCreateDefault(param: { email: any }) {
return await request({
url: apiPrefix + "/getOrCreateDefault",
method: "post",
data: param
data: param,
});
}
},
};
}

View File

@ -1,4 +1,4 @@
import { compute, useFormWrapper } from "@fast-crud/fast-crud";
import { compute, dict, useFormWrapper } from "@fast-crud/fast-crud";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { cloneDeep, omit } from "lodash-es";
import { useReference } from "/@/use/use-refrence";
@ -90,7 +90,7 @@ export function useCertUpload() {
return inputs;
}
async function openUploadCreateDialog() {
async function openUploadCreateDialog(req: { defaultGroupId?: number }) {
//检查是否流水线数量超出限制
await checkPipelineLimit();
@ -102,7 +102,11 @@ export function useCertUpload() {
return wrapperRef.value.getFormData();
}
const inputs = await buildUploadCertPluginInputs(getFormData);
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
label: "name",
});
function createCrudOptions() {
return {
crudOptions: {
@ -127,6 +131,15 @@ export function useCertUpload() {
helper: "任务执行失败实时提醒",
},
},
groupId: {
title: "流水线分组",
type: "dict-select",
dict: groupDictRef,
form: {
value: req.defaultGroupId || undefined,
order: 9999,
},
},
},
form: {
wrapper: {
@ -191,6 +204,7 @@ export function useCertUpload() {
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert_upload",
groupId: form.groupId,
});
router.push({
path: "/certd/pipeline/detail",

View File

@ -1,143 +0,0 @@
import { compute, CreateCrudOptionsRet, dict } from "@fast-crud/fast-crud";
import { useReference } from "/@/use/use-refrence";
import { merge, cloneDeep } from "lodash-es";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { usePluginStore } from "/@/store/plugin";
export default function (certPlugins: any[], formWrapperRef: any): 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 = formWrapperRef.value.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 pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
return {
crudOptions: {
form: {
wrapper: {
width: 1350,
saveRemind: false,
title: "创建证书流水线",
},
group: {
groups: {
more: {
header: "更多参数",
columns: moreParams,
collapsed: true,
},
},
},
},
columns: {
certApplyPlugin: {
title: "证书申请插件",
type: "dict-select",
dict: dict({
data: [
{ value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" },
],
}),
form: {
order: 0,
value: "CertApply",
helper: {
render: () => {
return (
<ul>
<li>JS-ACME使便</li>
<li>Lego-ACMELegoDNSLEGO使</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: "定时触发",
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({ $event, form }) {
form.notificationTarget = $event;
},
},
},
order: 101,
helper: "任务执行失败实时提醒",
},
},
},
},
};
}

View File

@ -18,7 +18,7 @@ const formWrapperRef = ref();
const formWrapperOptions = ref();
const doSubmitRef = ref();
const pluginStore = usePluginStore();
async function buildFormOptions() {
async function buildFormOptions(req: { defaultGroupId?: number }) {
const pluginGroup = await pluginStore.getGroups();
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
const certPluginGroup = pluginGroups.cert;
@ -51,10 +51,13 @@ async function buildFormOptions() {
}) as any
);
formOptions.columns.groupId.value = req.defaultGroupId;
formWrapperOptions.value = formOptions;
}
buildFormOptions();
function open(doSubmit: any) {
function open(doSubmit: any, req: { defaultGroupId?: number }) {
buildFormOptions(req);
doSubmitRef.value = doSubmit;
formWrapperRef.value.open(formWrapperOptions.value);
}

View File

@ -1,126 +0,0 @@
import { checkPipelineLimit, readCertDetail } from "/@/views/certd/pipeline/utils";
import { omit } from "lodash-es";
import * as api from "/@/views/certd/pipeline/api";
import { message } from "ant-design-vue";
import { nanoid } from "nanoid";
import { useRouter } from "vue-router";
export function setRunnableIds(pipeline: any) {
const idMap: any = {};
function createId(oldId: any) {
if (oldId == null) {
return nanoid();
}
const newId = nanoid();
idMap[oldId] = newId;
return newId;
}
if (pipeline.stages) {
for (const stage of pipeline.stages) {
stage.id = createId(stage.id);
if (stage.tasks) {
for (const task of stage.tasks) {
task.id = createId(task.id);
if (task.steps) {
for (const step of task.steps) {
step.id = createId(step.id);
}
}
}
}
}
}
for (const trigger of pipeline.triggers) {
trigger.id = nanoid();
}
for (const notification of pipeline.notifications) {
notification.id = nanoid();
}
let content = JSON.stringify(pipeline);
for (const key in idMap) {
content = content.replaceAll(key, idMap[key]);
}
return JSON.parse(content);
}
export function useCertd(certdFormRef: any) {
const router = useRouter();
async function openAddCertdPipelineDialog() {
//检查是否流水线数量超出限制
await checkPipelineLimit();
certdFormRef.value.open(async ({ form }: any) => {
// const certDetail = readCertDetail(form.cert.crt);
// 添加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"]);
let pipeline = {
title: form.domains[0] + "证书自动化",
runnableType: "pipeline",
stages: [
{
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
title: "证书申请任务",
runnableType: "task",
steps: [
{
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
...pluginInput,
},
strategy: {
runStrategy: 0, // 正常执行
},
type: form.certApplyPlugin,
},
],
},
],
},
],
triggers,
notifications,
};
pipeline = setRunnableIds(pipeline);
/**
* // cert: 证书; backup: 备份; custom:自定义;
* type: string;
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const id = await api.Save({
title: pipeline.title,
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert",
});
message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
});
}
return {
openAddCertdPipelineDialog,
};
}

View File

@ -0,0 +1,328 @@
import { checkPipelineLimit } from "/@/views/certd/pipeline/utils";
import { cloneDeep, merge, omit } from "lodash-es";
import { message } from "ant-design-vue";
import { nanoid } from "nanoid";
import { useRouter } from "vue-router";
import { compute, CreateCrudOptionsRet, dict, useFormWrapper } from "@fast-crud/fast-crud";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useReference } from "/@/use/use-refrence";
import { ref } from "vue";
import * as api from "../api";
import { PluginGroup, usePluginStore } from "/@/store/plugin";
import { createNotificationApi } from "/@/views/certd/notification/api";
export function setRunnableIds(pipeline: any) {
const idMap: any = {};
function createId(oldId: any) {
if (oldId == null) {
return nanoid();
}
const newId = nanoid();
idMap[oldId] = newId;
return newId;
}
if (pipeline.stages) {
for (const stage of pipeline.stages) {
stage.id = createId(stage.id);
if (stage.tasks) {
for (const task of stage.tasks) {
task.id = createId(task.id);
if (task.steps) {
for (const step of task.steps) {
step.id = createId(step.id);
}
}
}
}
}
}
for (const trigger of pipeline.triggers) {
trigger.id = nanoid();
}
for (const notification of pipeline.notifications) {
notification.id = nanoid();
}
let content = JSON.stringify(pipeline);
for (const key in idMap) {
content = content.replaceAll(key, idMap[key]);
}
return JSON.parse(content);
}
export function useCertPipelineCreator() {
const { openCrudFormDialog } = useFormWrapper();
const pluginStore = usePluginStore();
const router = useRouter();
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): 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 pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
label: "name",
});
return {
crudOptions: {
form: {
doSubmit,
wrapper: {
width: 1350,
saveRemind: false,
title: "创建证书流水线",
},
group: {
groups: {
more: {
header: "更多参数",
columns: moreParams,
collapsed: true,
},
},
},
},
columns: {
certApplyPlugin: {
title: "证书申请插件",
type: "dict-select",
dict: dict({
data: [
{ value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" },
],
}),
form: {
order: 0,
value: "CertApply",
helper: {
render: () => {
return (
<ul>
<li>JS-ACME使便</li>
<li>Lego-ACMELegoDNSLEGO使</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: "定时触发",
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({ $event, form }) {
form.notificationTarget = $event;
},
},
},
order: 101,
helper: "任务执行失败实时提醒",
},
},
groupId: {
title: "流水线分组",
type: "dict-select",
dict: groupDictRef,
form: {
order: 9999,
},
},
},
},
};
}
async function getCertPlugins() {
const pluginGroup = await pluginStore.getGroups();
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
const certPluginGroup = pluginGroups.cert;
const certPlugins = [];
for (const plugin of certPluginGroup.plugins) {
const detail: any = await pluginStore.getPluginDefine(plugin.name);
certPlugins.push(detail);
}
return certPlugins;
}
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
//检查是否流水线数量超出限制
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
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"]);
let pipeline = {
title: form.domains[0] + "证书自动化",
runnableType: "pipeline",
stages: [
{
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
title: "证书申请任务",
runnableType: "task",
steps: [
{
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
...pluginInput,
},
strategy: {
runStrategy: 0, // 正常执行
},
type: form.certApplyPlugin,
},
],
},
],
},
],
triggers,
notifications,
};
pipeline = setRunnableIds(pipeline);
/**
* // cert: 证书; backup: 备份; custom:自定义;
* type: string;
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const groupId = form.groupId;
const id = await api.Save({
title: pipeline.title,
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert",
groupId,
});
if (form.email) {
try {
//创建一个默认的邮件通知
const notificationApi = createNotificationApi();
await notificationApi.GetOrCreateDefault({ email: form.email });
} catch (e) {
console.error(e);
}
}
message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
}
const certPlugins = await getCertPlugins();
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
//@ts-ignore
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
const wrapper = await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
}
return {
openAddCertdPipelineDialog,
};
}

View File

@ -13,15 +13,15 @@ import { cloneDeep } from "lodash-es";
import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue";
import { eachStages } from "./utils";
import { setRunnableIds, useCertd } from "/@/views/certd/pipeline/certd-form/use";
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const lastResRef = ref();
const { openAddCertdPipelineDialog } = useCertd(certdFormRef);
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
const { openUploadCreateDialog } = useCertUpload();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@ -157,7 +157,9 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
type: "primary",
icon: "ion:ios-add-circle-outline",
click() {
openAddCertdPipelineDialog();
const searchForm = crudExpose.getSearchValidatedFormData();
const defaultGroupId = searchForm.groupId;
openAddCertdPipelineDialog({ defaultGroupId });
},
},
uploadCert: {
@ -179,7 +181,8 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
},
icon: "ion:cloud-upload-outline",
click() {
openUploadCreateDialog();
const searchForm = crudExpose.getSearchValidatedFormData();
openUploadCreateDialog({ defaultGroupId: searchForm.groupId });
},
},
},

View File

@ -15,7 +15,6 @@
<template #form-bottom>
<div>申请证书</div>
</template>
<pi-certd-form ref="certdFormRef"></pi-certd-form>
</fs-crud>
</fs-page>
</template>
@ -33,7 +32,6 @@ defineOptions({
name: "PipelineManager",
});
const certdFormRef = ref();
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
@ -41,7 +39,6 @@ const groupDictRef = dict({
});
const selectedRowKeys = ref([]);
const context: any = {
certdFormRef,
groupDictRef,
selectedRowKeys,
};

View File

@ -3,6 +3,7 @@ import { mySuiteApi } from "/@/views/certd/suite/mine/api";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/settings";
import { ReadCertDetail } from "./api";
import { util } from "/@/utils";
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
if (!list || list.length <= 0) {
return;
@ -70,7 +71,13 @@ export async function checkPipelineLimit() {
}
export async function readCertDetail(crt: string) {
return await ReadCertDetail(crt);
const cached = await util.cache.get(crt);
if (cached) {
return cached;
}
const res = await ReadCertDetail(crt);
await util.cache.set(crt, res);
return res;
}
export async function getAllDomainsFromCrt(crt: string) {