mirror of https://github.com/certd/certd
chore: 支持手动上传证书并部署
parent
873f2b618b
commit
d1b61b6bf9
|
@ -11,11 +11,13 @@ function attachProperty(target: any, propertyKey: string | symbol) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClassProperties(target: any) {
|
function getClassProperties(target: any) {
|
||||||
//获取父类
|
//获取父类, 向上追溯三层
|
||||||
const parent = Object.getPrototypeOf(target);
|
const parent = Object.getPrototypeOf(target);
|
||||||
|
const pParent = Object.getPrototypeOf(parent);
|
||||||
|
const pParentMap = propertyMap[pParent] || {};
|
||||||
const parentMap = propertyMap[parent] || {};
|
const parentMap = propertyMap[parent] || {};
|
||||||
const current = propertyMap[target] || {};
|
const current = propertyMap[target] || {};
|
||||||
return _.merge({}, parentMap, current);
|
return _.merge({}, pParentMap, parentMap, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
function target(target: any, propertyKey?: string | symbol) {
|
function target(target: any, propertyKey?: string | symbol) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ export abstract class BaseService<T> {
|
||||||
|
|
||||||
async transaction(callback: (entityManager: EntityManager) => Promise<any>) {
|
async transaction(callback: (entityManager: EntityManager) => Promise<any>) {
|
||||||
const dataSource = this.dataSourceManager.getDataSource('default');
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
await dataSource.transaction(callback as any);
|
return await dataSource.transaction(callback as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,14 +31,14 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
|
||||||
domains!: string[];
|
domains!: string[];
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "证书密码",
|
title: "证书加密密码",
|
||||||
component: {
|
component: {
|
||||||
name: "input-password",
|
name: "input-password",
|
||||||
vModel: "value",
|
vModel: "value",
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
order: 100,
|
order: 100,
|
||||||
helper: "PFX、jks格式证书是否加密\njks必须设置密码,不传则默认123456\npfx不传则为空密码",
|
helper: "转换成PFX、jks格式证书是否需要加密\njks必须设置密码,不传则默认123456\npfx不传则为空密码",
|
||||||
})
|
})
|
||||||
pfxPassword!: string;
|
pfxPassword!: string;
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ export async function emitCertApplySuccess(emitter: TaskEmitter, cert: CertReade
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "邮箱",
|
title: "邮箱",
|
||||||
component: {
|
component: {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div class="file-input">
|
||||||
|
<a-button :type="type" @click="onClick">{{ text }}</a-button> {{ fileName }}
|
||||||
|
<div class="hidden">
|
||||||
|
<input ref="fileInputRef" type="file" @change="onFileChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, defineEmits, defineProps } from "vue";
|
||||||
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
text: string;
|
||||||
|
type: string;
|
||||||
|
}>();
|
||||||
|
const fileName = ref("");
|
||||||
|
const emit = defineEmits(["change"]);
|
||||||
|
function onClick() {
|
||||||
|
fileInputRef.value.click();
|
||||||
|
}
|
||||||
|
function onFileChange(e: any) {
|
||||||
|
fileName.value = e.target.files[0].name;
|
||||||
|
emit("change", e);
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -10,10 +10,12 @@ import Plugins from "./plugins/index";
|
||||||
import LoadingButton from "./loading-button.vue";
|
import LoadingButton from "./loading-button.vue";
|
||||||
import IconSelect from "./icon-select.vue";
|
import IconSelect from "./icon-select.vue";
|
||||||
import ExpiresTimeText from "./expires-time-text.vue";
|
import ExpiresTimeText from "./expires-time-text.vue";
|
||||||
|
import FileInput from "./file-input.vue";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
app.component("PiContainer", PiContainer);
|
app.component("PiContainer", PiContainer);
|
||||||
app.component("TextEditable", TextEditable);
|
app.component("TextEditable", TextEditable);
|
||||||
|
app.component("FileInput", FileInput);
|
||||||
|
|
||||||
app.component("CronLight", CronLight);
|
app.component("CronLight", CronLight);
|
||||||
app.component("CronEditor", CronEditor);
|
app.component("CronEditor", CronEditor);
|
||||||
|
@ -29,5 +31,5 @@ export default {
|
||||||
app.component("ExpiresTimeText", ExpiresTimeText);
|
app.component("ExpiresTimeText", ExpiresTimeText);
|
||||||
app.use(vip);
|
app.use(vip);
|
||||||
app.use(Plugins);
|
app.use(Plugins);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,8 @@ const apiPrefix = "/pi/plugin";
|
||||||
const defaultInputDefine = {
|
const defaultInputDefine = {
|
||||||
component: {
|
component: {
|
||||||
name: "a-input",
|
name: "a-input",
|
||||||
vModel: "modelValue"
|
vModel: "modelValue",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function initPlugins(plugins: any) {
|
function initPlugins(plugins: any) {
|
||||||
|
@ -35,7 +35,7 @@ export async function GetList(query: any) {
|
||||||
const plugins = await request({
|
const plugins = await request({
|
||||||
url: apiPrefix + "/list",
|
url: apiPrefix + "/list",
|
||||||
method: "post",
|
method: "post",
|
||||||
params: query
|
params: query,
|
||||||
});
|
});
|
||||||
initPlugins(plugins);
|
initPlugins(plugins);
|
||||||
return plugins;
|
return plugins;
|
||||||
|
@ -45,7 +45,7 @@ export async function GetGroups(query: any) {
|
||||||
const groups = await request({
|
const groups = await request({
|
||||||
url: apiPrefix + "/groups",
|
url: apiPrefix + "/groups",
|
||||||
method: "post",
|
method: "post",
|
||||||
params: query
|
params: query,
|
||||||
});
|
});
|
||||||
const plugins: any = [];
|
const plugins: any = [];
|
||||||
for (const groupKey in groups) {
|
for (const groupKey in groups) {
|
||||||
|
@ -60,8 +60,8 @@ export async function GetPluginDefine(type: string) {
|
||||||
url: apiPrefix + "/getDefineByType",
|
url: apiPrefix + "/getDefineByType",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
type
|
type,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
initPlugins([define]);
|
initPlugins([define]);
|
||||||
return define;
|
return define;
|
||||||
|
@ -71,6 +71,6 @@ export async function GetPluginConfig(req: { id?: number; name: string; type: st
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/config",
|
url: apiPrefix + "/config",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: req
|
data: req,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
const apiPrefix = "/monitor/cert";
|
||||||
|
export async function UploadCert(body: { id?: number; cert: { crt: string; key: string }; pipeline?: any }) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/upload",
|
||||||
|
method: "post",
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
import { compute, useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
|
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||||
|
import * as api from "./api";
|
||||||
|
import { omit, cloneDeep, set } from "lodash-es";
|
||||||
|
import { useReference } from "/@/use/use-refrence";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import * as pluginApi from "../api.plugin";
|
||||||
|
import { checkPipelineLimit } from "/@/views/certd/pipeline/utils";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
export function useCertUpload() {
|
||||||
|
const { openCrudFormDialog } = useFormWrapper();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function buildUploadCertPluginInputs(getFormData: any) {
|
||||||
|
const plugin: any = await pluginApi.GetPluginDefine("CertApplyUpload");
|
||||||
|
const inputs: any = {};
|
||||||
|
for (const inputKey in plugin.input) {
|
||||||
|
if (inputKey === "certInfoId" || inputKey === "domains") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const inputDefine = cloneDeep(plugin.input[inputKey]);
|
||||||
|
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 inputDefineShow;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
function topRender({ form, key }: any) {
|
||||||
|
function onChange(e: any) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const size = file.size;
|
||||||
|
if (size > 100 * 1024) {
|
||||||
|
notification.error({
|
||||||
|
message: "文件超过100k,请选择正确的证书文件",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = function (e: any) {
|
||||||
|
const value = e.target.result;
|
||||||
|
set(form, key, value);
|
||||||
|
};
|
||||||
|
fileReader.readAsText(file); // 以文本形式读取文件
|
||||||
|
}
|
||||||
|
return <file-input class="mb-5" type="primary" text={"选择文件"} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openUploadCreateDialog() {
|
||||||
|
//检查是否流水线数量超出限制
|
||||||
|
await checkPipelineLimit();
|
||||||
|
|
||||||
|
const wrapperRef = ref();
|
||||||
|
function getFormData() {
|
||||||
|
if (!wrapperRef.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return wrapperRef.value.getFormData();
|
||||||
|
}
|
||||||
|
const inputs = await buildUploadCertPluginInputs(getFormData);
|
||||||
|
|
||||||
|
function createCrudOptions() {
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
columns: {
|
||||||
|
"cert.crt": {
|
||||||
|
title: "证书",
|
||||||
|
type: "textarea",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
rows: 4,
|
||||||
|
placeholder: "-----BEGIN CERTIFICATE-----\n...\n...\n-----END CERTIFICATE-----",
|
||||||
|
},
|
||||||
|
helper: "选择pem格式证书文件,或者粘贴到此",
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
col: { span: 24 },
|
||||||
|
order: -9999,
|
||||||
|
topRender,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cert.key": {
|
||||||
|
title: "证书私钥",
|
||||||
|
type: "textarea",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
rows: 4,
|
||||||
|
placeholder: "-----BEGIN PRIVATE KEY-----\n...\n...\n-----END PRIVATE KEY----- ",
|
||||||
|
},
|
||||||
|
helper: "选择pem格式证书私钥文件,或者粘贴到此",
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
col: { span: 24 },
|
||||||
|
order: -9999,
|
||||||
|
topRender,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...inputs,
|
||||||
|
notification: {
|
||||||
|
title: "失败通知",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
value: 0,
|
||||||
|
component: {
|
||||||
|
name: NotificationSelector,
|
||||||
|
vModel: "modelValue",
|
||||||
|
on: {
|
||||||
|
selectedChange({ $event, form }: any) {
|
||||||
|
form.notificationTarget = $event;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: 101,
|
||||||
|
helper: "任务执行失败实时提醒",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
wrapper: {
|
||||||
|
title: "上传证书&创建部署流水线",
|
||||||
|
saveRemind: false,
|
||||||
|
},
|
||||||
|
async doSubmit({ form }: any) {
|
||||||
|
const notifications = [];
|
||||||
|
if (form.notification != null) {
|
||||||
|
notifications.push({
|
||||||
|
type: "custom",
|
||||||
|
when: ["error", "turnToSuccess", "success"],
|
||||||
|
notificationId: form.notification,
|
||||||
|
title: form.notificationTarget?.name || "自定义通知",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
id: form.id,
|
||||||
|
cert: form.cert,
|
||||||
|
pipeline: {
|
||||||
|
input: omit(form, ["id", "cert", "notification", "notificationTarget"]),
|
||||||
|
notifications,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = await api.UploadCert(req);
|
||||||
|
router.push({
|
||||||
|
path: "/certd/pipeline/detail",
|
||||||
|
query: { id: res.pipelineId, editMode: "true" },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { crudOptions } = createCrudOptions();
|
||||||
|
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||||
|
wrapperRef.value = wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openUpdateCertDialog(id: any) {
|
||||||
|
function createCrudOptions() {
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
columns: {
|
||||||
|
"cert.crt": {
|
||||||
|
title: "证书",
|
||||||
|
type: "textarea",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
col: { span: 24 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"cert.key": {
|
||||||
|
title: "私钥",
|
||||||
|
type: "textarea",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
col: { span: 24 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
wrapper: {
|
||||||
|
title: "更新证书",
|
||||||
|
saveRemind: false,
|
||||||
|
},
|
||||||
|
async doSubmit({ form }: any) {
|
||||||
|
const req = {
|
||||||
|
id: id,
|
||||||
|
cert: form.cert,
|
||||||
|
};
|
||||||
|
return await api.UploadCert(form);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { crudOptions } = createCrudOptions();
|
||||||
|
await openCrudFormDialog({ crudOptions });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openUploadCreateDialog,
|
||||||
|
openUpdateCertDialog,
|
||||||
|
};
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||||
for (const plugin of certPlugins) {
|
for (const plugin of certPlugins) {
|
||||||
for (const inputKey in plugin.input) {
|
for (const inputKey in plugin.input) {
|
||||||
if (inputs[inputKey]) {
|
if (inputs[inputKey]) {
|
||||||
inputs[inputKey].form.show = true;
|
// inputs[inputKey].form.show = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const inputDefine = _.cloneDeep(plugin.input[inputKey]);
|
const inputDefine = _.cloneDeep(plugin.input[inputKey]);
|
||||||
|
|
|
@ -4,11 +4,11 @@ export const Dicts = {
|
||||||
sslProviderDict: dict({
|
sslProviderDict: dict({
|
||||||
data: [
|
data: [
|
||||||
{ value: "letsencrypt", label: "Let‘s Encrypt" },
|
{ value: "letsencrypt", label: "Let‘s Encrypt" },
|
||||||
{ value: "zerossl", label: "ZeroSSL" }
|
{ value: "zerossl", label: "ZeroSSL" },
|
||||||
]
|
],
|
||||||
}),
|
}),
|
||||||
challengeTypeDict: dict({ data: [{ value: "dns", label: "DNS校验" }] }),
|
challengeTypeDict: dict({ data: [{ value: "dns", label: "DNS校验" }] }),
|
||||||
dnsProviderTypeDict: dict({
|
dnsProviderTypeDict: dict({
|
||||||
url: "pi/dnsProvider/dnsProviderTypeDict"
|
url: "pi/dnsProvider/dnsProviderTypeDict",
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,8 +44,8 @@ export default {
|
||||||
const notificationApi = createNotificationApi();
|
const notificationApi = createNotificationApi();
|
||||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}) as any
|
}) as any
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -60,9 +60,9 @@ export default {
|
||||||
return {
|
return {
|
||||||
formWrapperRef,
|
formWrapperRef,
|
||||||
open,
|
open,
|
||||||
formWrapperOptions
|
formWrapperOptions,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { checkPipelineLimit } 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) => {
|
||||||
|
// 添加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",
|
||||||
|
from: "custom",
|
||||||
|
});
|
||||||
|
message.success("创建成功,请添加证书部署任务");
|
||||||
|
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openAddCertdPipelineDialog,
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,62 +4,26 @@ 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 { 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 { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||||
import { nanoid } from "nanoid";
|
import { Modal, notification } from "ant-design-vue";
|
||||||
import { message, Modal, notification } from "ant-design-vue";
|
|
||||||
import { env } from "/@/utils/util.env";
|
import { env } from "/@/utils/util.env";
|
||||||
import { useUserStore } from "/@/store/modules/user";
|
import { useUserStore } from "/@/store/modules/user";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import * as _ from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import { useModal } from "/@/use/use-modal";
|
import { useModal } from "/@/use/use-modal";
|
||||||
import CertView from "./cert-view.vue";
|
import CertView from "./cert-view.vue";
|
||||||
import { eachStages } from "./utils";
|
import { eachStages } from "./utils";
|
||||||
import { createNotificationApi as createNotificationApi } from "../notification/api";
|
import { setRunnableIds, useCertd } from "/@/views/certd/pipeline/certd-form/use";
|
||||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
||||||
|
|
||||||
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const lastResRef = ref();
|
const lastResRef = ref();
|
||||||
|
|
||||||
function setRunnableIds(pipeline: any) {
|
const { openAddCertdPipelineDialog } = useCertd(certdFormRef);
|
||||||
const idMap: any = {};
|
const { openUploadCreateDialog } = useCertUpload();
|
||||||
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);
|
|
||||||
}
|
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
|
@ -96,90 +60,6 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const settingsStore = useSettingStore();
|
|
||||||
async function addCertdPipeline() {
|
|
||||||
//检查是否流水线数量超出限制
|
|
||||||
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
|
|
||||||
//检查数量是否超限
|
|
||||||
|
|
||||||
const suiteDetail = await mySuiteApi.SuiteDetailGet();
|
|
||||||
const max = suiteDetail.pipelineCount.max;
|
|
||||||
if (max != -1 && max <= suiteDetail.pipelineCount.used) {
|
|
||||||
notification.error({
|
|
||||||
message: `对不起,您最多只能创建${max}条流水线,请购买或升级套餐`,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
certdFormRef.value.open(async ({ form }: any) => {
|
|
||||||
// 添加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 || "自定义通知",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
...form,
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
from: "custom",
|
|
||||||
});
|
|
||||||
message.success("创建成功,请添加证书部署任务");
|
|
||||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = useModal();
|
const model = useModal();
|
||||||
const viewCert = async (row: any) => {
|
const viewCert = async (row: any) => {
|
||||||
const cert = await api.GetCert(row.id);
|
const cert = await api.GetCert(row.id);
|
||||||
|
@ -268,14 +148,25 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
icon: "ion:ios-add-circle-outline",
|
||||||
text: "自定义流水线",
|
text: "自定义流水线",
|
||||||
},
|
},
|
||||||
addCertd: {
|
addCertd: {
|
||||||
order: 1,
|
order: 1,
|
||||||
text: "创建证书流水线",
|
text: "创建证书流水线",
|
||||||
type: "primary",
|
type: "primary",
|
||||||
|
icon: "ion:ios-add-circle-outline",
|
||||||
click() {
|
click() {
|
||||||
addCertdPipeline();
|
openAddCertdPipelineDialog();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uploadCert: {
|
||||||
|
order: 2,
|
||||||
|
text: "上传证书部署",
|
||||||
|
type: "primary",
|
||||||
|
icon: "ion:cloud-upload-outline",
|
||||||
|
click() {
|
||||||
|
openUploadCreateDialog();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -329,7 +220,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||||
const { ui } = useUi();
|
const { ui } = useUi();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let row = context[ui.tableColumn.row];
|
let row = context[ui.tableColumn.row];
|
||||||
row = _.cloneDeep(row);
|
row = cloneDeep(row);
|
||||||
row.title = row.title + "_copy";
|
row.title = row.title + "_copy";
|
||||||
await crudExpose.openCopy({
|
await crudExpose.openCopy({
|
||||||
row: row,
|
row: row,
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { forEach } from "lodash-es";
|
import { forEach } from "lodash-es";
|
||||||
|
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
|
|
||||||
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
|
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
|
||||||
if (!list || list.length <= 0) {
|
if (!list || list.length <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
forEach(list, (item) => {
|
forEach(list, item => {
|
||||||
exec(item, runnableType);
|
exec(item, runnableType);
|
||||||
if (runnableType === "stage") {
|
if (runnableType === "stage") {
|
||||||
eachStages(item.tasks, exec, "task");
|
eachStages(item.tasks, exec, "task");
|
||||||
|
@ -13,3 +16,19 @@ export function eachStages(list: any[], exec: (item: any, runnableType: string)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkPipelineLimit() {
|
||||||
|
const settingsStore = useSettingStore();
|
||||||
|
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
|
||||||
|
//检查数量是否超限
|
||||||
|
|
||||||
|
const suiteDetail = await mySuiteApi.SuiteDetailGet();
|
||||||
|
const max = suiteDetail.pipelineCount.max;
|
||||||
|
if (max != -1 && max <= suiteDetail.pipelineCount.used) {
|
||||||
|
notification.error({
|
||||||
|
message: `对不起,您最多只能创建${max}条流水线,请购买或升级套餐`,
|
||||||
|
});
|
||||||
|
throw new Error("流水线数量超限");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -121,6 +121,16 @@ export class CertInfoController extends CrudController<CertInfoService> {
|
||||||
return this.ok(list);
|
return this.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Post('/getCert', { summary: Constants.per.authOnly })
|
||||||
|
async getCert(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
const certInfoEntity = await this.service.info(id);
|
||||||
|
const certInfo = JSON.parse(certInfoEntity.certInfo);
|
||||||
|
return this.ok(certInfo);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/upload', { summary: Constants.per.authOnly })
|
@Post('/upload', { summary: Constants.per.authOnly })
|
||||||
async upload(@Body(ALL) body: {cert: CertInfo, pipeline: any, id?: number}) {
|
async upload(@Body(ALL) body: {cert: CertInfo, pipeline: any, id?: number}) {
|
||||||
if (body.id) {
|
if (body.id) {
|
||||||
|
@ -131,23 +141,16 @@ export class CertInfoController extends CrudController<CertInfoService> {
|
||||||
userId: this.getUserId(),
|
userId: this.getUserId(),
|
||||||
cert: body.cert,
|
cert: body.cert,
|
||||||
});
|
});
|
||||||
|
return this.ok();
|
||||||
}else{
|
}else{
|
||||||
//添加
|
//添加
|
||||||
await this.certUploadService.createUploadCertPipeline({
|
const res = await this.certUploadService.createUploadCertPipeline({
|
||||||
userId: this.getUserId(),
|
userId: this.getUserId(),
|
||||||
cert: body.cert,
|
cert: body.cert,
|
||||||
|
pipeline: body.pipeline,
|
||||||
});
|
});
|
||||||
|
return this.ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/getCert', { summary: Constants.per.authOnly })
|
|
||||||
async getCert(@Query('id') id: number) {
|
|
||||||
await this.service.checkUserId(id, this.getUserId());
|
|
||||||
const certInfoEntity = await this.service.info(id);
|
|
||||||
const certInfo = JSON.parse(certInfoEntity.certInfo);
|
|
||||||
return this.ok(certInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ export type UpdateCertReq = {
|
||||||
export type CreateUploadPipelineReq = {
|
export type CreateUploadPipelineReq = {
|
||||||
cert: CertInfo;
|
cert: CertInfo;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
pipeline?:{
|
||||||
|
input?:any;
|
||||||
|
notifications?:any[]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Provide("CertUploadService")
|
@Provide("CertUploadService")
|
||||||
|
@ -86,13 +90,16 @@ export class CertUploadService extends BaseService<CertInfoEntity> {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pipelineTitle = certReader.getAllDomains()[0] +"上传证书自动部署";
|
const pipelineTitle = certReader.getAllDomains()[0] +"上传证书自动部署";
|
||||||
const notifications = [];
|
const notifications = body.pipeline?.notifications ||[];
|
||||||
|
if(notifications.length === 0){
|
||||||
notifications.push({
|
notifications.push({
|
||||||
type: "custom",
|
type: "custom",
|
||||||
when: ["error", "turnToSuccess", "success"],
|
when: ["error", "turnToSuccess", "success"],
|
||||||
notificationId: 0,
|
notificationId: 0,
|
||||||
title: "默认通知",
|
title: "默认通知",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let pipeline = {
|
let pipeline = {
|
||||||
title: pipelineTitle,
|
title: pipelineTitle,
|
||||||
runnableType: "pipeline",
|
runnableType: "pipeline",
|
||||||
|
@ -115,6 +122,7 @@ export class CertUploadService extends BaseService<CertInfoEntity> {
|
||||||
input: {
|
input: {
|
||||||
certInfoId: newCertInfo.id,
|
certInfoId: newCertInfo.id,
|
||||||
domains: newCertInfo.domains.split(','),
|
domains: newCertInfo.domains.split(','),
|
||||||
|
...body.pipeline?.input
|
||||||
},
|
},
|
||||||
strategy: {
|
strategy: {
|
||||||
runStrategy: 0, // 正常执行
|
runStrategy: 0, // 正常执行
|
||||||
|
@ -144,7 +152,10 @@ export class CertUploadService extends BaseService<CertInfoEntity> {
|
||||||
pipelineId: newPipeline.id
|
pipelineId: newPipeline.id
|
||||||
});
|
});
|
||||||
|
|
||||||
return newCertInfo.id
|
return {
|
||||||
|
id:newCertInfo.id,
|
||||||
|
pipelineId: newPipeline.id
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue