perf: 支持手动上传证书并部署

This commit is contained in:
xiaojunnuo
2025-03-17 00:19:01 +08:00
parent 0069c0e399
commit a9fffa5180
14 changed files with 245 additions and 90 deletions
@@ -4,6 +4,10 @@ export function createAccessApi(from = "user") {
const apiPrefix = from === "sys" ? "/sys/access" : "/pi/access";
return {
async GetList(query: any) {
if (query?.query) {
delete query.query.access;
}
return await request({
url: apiPrefix + "/page",
method: "post",
@@ -77,7 +77,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
type: "dict-select",
dict: AccessTypeDictRef,
search: {
show: false
show: true
},
column: {
width: 200,
@@ -1,12 +1,10 @@
// @ts-ignore
import { useI18n } from "vue-i18n";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { certInfoApi } from "./api";
import dayjs from "dayjs";
import { useUserStore } from "/@/store/modules/user";
import { useRouter } from "vue-router";
import { useModal } from "/@/use/use-modal";
import * as api from "/@/views/certd/pipeline/api";
import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue";
@@ -54,6 +52,69 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
};
async function openUpload(id?: any) {
function createCrudOptions() {
return {
crudOptions: {
request: {
// addRequest: async (form: any) => {
// return await api.Upload(form);
// },
// editRequest: async (form: any) => {
// return await api.Upload(form);
// }
},
columns: {
id: {
title: "ID",
type: "number",
form: {
show: false
}
},
"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: "上传自定义证书"
},
async doSubmit({ form }: any) {
if (!id) {
delete form.id;
} else {
form.id = id;
}
return await api.Upload(form);
}
}
}
};
}
const { crudOptions } = createCrudOptions();
const wrapperRef = await openCrudFormDialog({ crudOptions });
}
return {
crudOptions: {
request: {
@@ -83,64 +144,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
add: {
text: "上传自定义证书",
type: "primary",
show: false,
show: true,
async click() {
function createCrudOptions() {
return {
crudOptions: {
request: {
addRequest: async (form: any) => {
return await api.Upload(form);
},
editRequest: async (form: any) => {
return await api.Upload(form);
}
},
columns: {
id: {
title: "ID",
type: "number",
form: {
show: false
}
},
"cert.crt": {
title: "证书",
type: "textarea",
form: {
component: {
rows: 4
},
rules: [{ required: true, message: "此项必填" }]
}
},
"cert.key": {
title: "私钥",
type: "textarea",
form: {
component: {
rows: 4
},
rules: [{ required: true, message: "此项必填" }]
}
}
},
form: {
wrapper: {
title: "上传自定义证书"
}
}
}
};
}
const { crudOptions } = createCrudOptions();
const wrapperRef = await openCrudFormDialog({ crudOptions });
await openUpload();
}
}
}
},
tabs: {
name: "fromType",
show: true
},
rowHandle: {
width: 100,
width: 140,
fixed: "right",
buttons: {
view: { show: false },
@@ -155,7 +171,24 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
copy: { show: false },
edit: { show: false },
remove: { show: false }
upload: {
show: compute(({ row }) => {
return row.fromType === "upload";
}),
order: 4,
title: "更新证书",
type: "link",
icon: "ph:upload",
async click({ row }) {
await openUpload(row.id);
}
},
remove: {
order: 10,
show: compute(({ row }) => {
return row.fromType === "upload";
})
}
}
},
columns: {
@@ -176,25 +209,37 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false
}
},
// domain: {
// title: "主域名",
// search: {
// show: true
// },
// type: "text",
// form: {
// show: false
// },
// column: {
// width: 180,
// sorter: true,
// component: {
// name: "fs-values-format"
// }
// }
// },
fromType: {
title: "来源",
search: {
show: true
},
type: "dict-select",
dict: dict({
data: [
{ label: "流水线", value: "pipeline" },
{ label: "手动上传", value: "upload" }
]
}),
form: {
show: false
},
column: {
width: 100,
sorter: true,
component: {
color: "auto"
},
conditionalRender: false
},
valueBuilder({ value, row, key }) {
if (!value) {
row[key] = "pipeline";
}
}
},
domains: {
title: "全部域名",
title: "域名",
search: {
show: true
},
@@ -177,13 +177,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
column: {
align: "center",
width: 100
width: 110
}
},
certDomains: {
title: "证书域名",
search: {
show: false
show: true
},
type: "text",
form: {
@@ -294,7 +294,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false
},
column: {
width: 100,
width: 110,
align: "center",
sorter: true
}
@@ -358,7 +358,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
value: false
},
column: {
width: 90,
width: 110,
sorter: true
}
}
@@ -4,7 +4,7 @@
<div class="title flex items-center">
站点证书监控
<div class="sub flex-1">
<div>每天0点检查网站证书的过期时间并发出提醒;</div>
<div>每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道;</div>
<div class="flex items-center">基础版限制1条专业版以上无限制当前<vip-button class="ml-5" mode="nav"></vip-button></div>
</div>
</div>
@@ -504,6 +504,20 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
width: 150
}
},
"lastVars.certExpiresTime": {
title: "过期时间",
search: {
show: false
},
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
align: "center"
}
},
status: {
title: "状态",
type: "dict-select",
@@ -24,11 +24,17 @@ export class CertInfoController extends CrudController<CertInfoService> {
async page(@Body(ALL) body: any) {
body.query = body.query ?? {};
body.query.userId = this.getUserId();
const domains = body.query?.domains;
delete body.query.domains;
const res = await this.service.page({
query: body.query,
page: body.page,
sort: body.sort,
buildQuery: (bq) => {
if (domains) {
bq.andWhere('domains like :domains', { domains: `%${domains}%` });
}
}
});
const records = res.records;
@@ -88,7 +94,11 @@ export class CertInfoController extends CrudController<CertInfoService> {
@Post('/upload', { summary: Constants.per.authOnly })
async upload(@Body(ALL) body: any) {
if (body.id) {
//修改
await this.service.checkUserId(body.id, this.getUserId());
}else{
//添加
body.userId = this.getUserId();
}
const res = await this.service.upload(body);
@@ -10,9 +10,10 @@ export type UploadCertReq = {
id?: number;
certReader: CertReader;
fromType?: string;
userId?: number;
};
@Provide()
@Provide("CertInfoService")
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CertInfoService extends BaseService<CertInfoEntity> {
@InjectEntityModel(CertInfoEntity)
@@ -147,7 +148,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
private async updateCert(req: UploadCertReq) {
const bean = new CertInfoEntity();
const { id, fromType, certReader } = req;
const { id, fromType,userId, certReader } = req;
if (id) {
bean.id = id;
} else {
@@ -162,13 +163,13 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
bean.domainCount = domains.length;
bean.expiresTime = certReader.expires;
bean.certProvider = certReader.detail.issuer.commonName;
bean.userId = userId
await this.addOrUpdate(bean);
return bean;
}
async upload(body: { id?: number; cert: CertInfo }) {
const { id, cert } = body;
async upload(body: { id?: number; userId?:number ;cert: CertInfo }) {
const { id, userId, cert } = body;
if (!cert) {
throw new CommonException("cert can't be empty");
}
@@ -176,6 +177,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
id,
certReader: new CertReader(cert),
fromType: 'upload',
userId
});
return res.id;
}
@@ -182,6 +182,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
);
}
async sendExpiresNotify(site: SiteInfoEntity) {
const tipDays = 10
const expires = site.certExpiresTime;
const validDays = dayjs(expires).diff(dayjs(), 'day');
const url = await this.notificationService.getBindUrl('#/monitor/site');
@@ -190,7 +193,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
证书域名: ${site.certDomains} \n
证书颁发者: ${site.certProvider} \n
过期时间: ${dayjs(site.certExpiresTime).format('YYYY-MM-DD')} \n`;
if (validDays >= 0 && validDays < 10) {
if (validDays >= 0 && validDays < tipDays) {
// 发通知
await this.notificationService.send(
{
@@ -475,6 +475,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
sysInfo.title = siteInfo.title;
}
const serviceContainer = {
CertInfoService: this.certInfoService
}
const serviceGetter = (name: string) => {
return serviceContainer[name]
}
const executor = new Executor({
user,
pipeline,
@@ -488,6 +494,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
notificationService: notificationGetter,
fileRootDir: this.certdConfig.fileRootDir,
sysInfo,
serviceGetter
});
try {
runningTasks.set(historyId, executor);