diff --git a/README.md b/README.md index 42b6f4d1..0abdf877 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系 * 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持40+部署插件) * 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式 * 邮件通知、webhook通知 -* 私有化部署,数据保存本地,镜像由Github Actions构建,过程公开透明 +* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明 * 支持SQLite,PostgreSQL、MySQL数据库 diff --git a/docker/run/docker-compose.yaml b/docker/run/docker-compose.yaml index 81a46e89..e9ce4db9 100644 --- a/docker/run/docker-compose.yaml +++ b/docker/run/docker-compose.yaml @@ -33,6 +33,7 @@ services: # 配置规则: certd_ + 配置项, 点号用_代替 # #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,重启之后,管理员密码将改成123456,然后请及时修改回false - certd_system_resetAdminPasswd=false +# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量 # #↓↓↓↓ ----------------------------- 使用postgresql数据库,需要提前创建数据库 # - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录 # - certd_typeorm_dataSource_default_type=postgres # 数据库类型 diff --git a/docs/index.md b/docs/index.md index 878e44fe..3652167c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ features: - title: 多证书格式支持 details: 支持pem、pfx、der、jks等多种证书格式,支持Google、Letsencrypt、ZeroSSL证书颁发机构 - title: 支持私有化部署 - details: 保障数据安全 + details: 授权数据加密存储,保障数据安全 - title: 多数据库支持 details: 支持SQLite、Postgresql、MySQL数据库 --- diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts index 96e8501f..cf52be99 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -233,37 +233,36 @@ cert.jks:jks格式证书文件,java服务器使用 // return null; // } - let inputChanged = this.ctx.inputChanged; - if (inputChanged) { - this.logger.info("input hash 有变更,检查是否需要重新申请证书"); - //判断域名有没有变更 - /** - * "renewDays": 35, - * "certApplyPlugin": "CertApply", - * "sslProvider": "letsencrypt", - * "privateKeyType": "rsa_2048_pkcs1", - * "dnsProviderType": "aliyun", - * "domains": [ - * "*.handsfree.work" - * ], - * "email": "xiaojunnuo@qq.com", - * "dnsProviderAccess": 3, - * "useProxy": false, - * "skipLocalVerify": false, - * "successNotify": true, - * "pfxPassword": "123456" - */ - const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"]; - const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges)); - const thisInput = JSON.stringify(pick(this, checkInputChanges)); - inputChanged = oldInput !== thisInput; + let inputChanged = false; + //判断域名有没有变更 + /** + * "renewDays": 35, + * "certApplyPlugin": "CertApply", + * "sslProvider": "letsencrypt", + * "privateKeyType": "rsa_2048_pkcs1", + * "dnsProviderType": "aliyun", + * "domains": [ + * "*.handsfree.work" + * ], + * "email": "xiaojunnuo@qq.com", + * "dnsProviderAccess": 3, + * "useProxy": false, + * "skipLocalVerify": false, + * "successNotify": true, + * "pfxPassword": "123456" + */ + const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"]; + const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges)); + const thisInput = JSON.stringify(pick(this, checkInputChanges)); + inputChanged = oldInput !== thisInput; - if (inputChanged) { - this.logger.info(`旧参数:${oldInput}`); - this.logger.info(`新参数:${thisInput}`); - this.logger.info("输入参数变更,准备申请新证书"); - return null; - } + this.logger.info(`旧参数:${oldInput}`); + this.logger.info(`新参数:${thisInput}`); + if (inputChanged) { + this.logger.info("输入参数变更,准备申请新证书"); + return null; + } else { + this.logger.info("输入参数未变更,不需要更新证书"); } let oldCert: CertReader | undefined = undefined; diff --git a/packages/ui/certd-client/src/components/expires-time-text.vue b/packages/ui/certd-client/src/components/expires-time-text.vue index 1fd2bdee..f7f00b16 100644 --- a/packages/ui/certd-client/src/components/expires-time-text.vue +++ b/packages/ui/certd-client/src/components/expires-time-text.vue @@ -5,7 +5,7 @@ {{ label }} @@ -35,12 +35,14 @@ const color = computed(() => { if (props.value == null) { return ""; } - if (props.value === -1) { + //距离今天多少天 + const days = dayjs(props.value).diff(dayjs(), "day"); + if (props.value === -1 || days > 365) { return "green"; } //小于3天 红色 - if (dayjs().add(3, "day").valueOf() > props.value) { + if (days <= 6) { return "red"; } diff --git a/packages/ui/certd-client/src/layout/components/menu/index.vue b/packages/ui/certd-client/src/layout/components/menu/index.vue new file mode 100644 index 00000000..46b297ab --- /dev/null +++ b/packages/ui/certd-client/src/layout/components/menu/index.vue @@ -0,0 +1,118 @@ + + + + diff --git a/packages/ui/certd-client/src/layout/components/menu/index.tsx b/packages/ui/certd-client/src/layout/components/menu/index1.tsx similarity index 73% rename from packages/ui/certd-client/src/layout/components/menu/index.tsx rename to packages/ui/certd-client/src/layout/components/menu/index1.tsx index 428e443c..48a8dd88 100644 --- a/packages/ui/certd-client/src/layout/components/menu/index.tsx +++ b/packages/ui/certd-client/src/layout/components/menu/index1.tsx @@ -6,60 +6,8 @@ import "./index.less"; import { utils } from "@fast-crud/fast-crud"; import { routerUtils } from "/@/utils/util.router"; -function useBetterScroll(enabled = true) { - const bsRef = ref(null); - const asideMenuRef = ref(); +defineOptions() - let onOpenChange = () => {}; - if (enabled) { - function bsInit() { - if (asideMenuRef.value == null) { - return; - } - bsRef.value = new BScroll(asideMenuRef.value, { - mouseWheel: true, - click: true, - momentum: false, - // 如果你愿意可以打开显示滚动条 - scrollbar: { - fade: true, - interactive: false - }, - bounce: false - }); - } - - function bsDestroy() { - if (bsRef.value != null && bsRef.value.destroy) { - try { - bsRef.value.destroy(); - } catch (e) { - // console.error(e); - } finally { - bsRef.value = null; - } - } - } - - onMounted(() => { - bsInit(); - }); - - onUnmounted(() => { - bsDestroy(); - }); - onOpenChange = async () => { - console.log("onOpenChange"); - setTimeout(() => { - bsRef.value?.refresh(); - }, 300); - }; - } - return { - onOpenChange, - asideMenuRef - }; -} export default defineComponent({ name: "FsMenu", inheritAttrs: true, @@ -75,6 +23,31 @@ export default defineComponent({ await routerUtils.open(item.key); } + const items = ref([]); + + function buildItemMenus(menus: any) { + if (menus == null) { + return; + } + const list: any = []; + for (const sub of menus) { + const item: any = { + key: sub.path, + label: sub.title, + title: sub.title, + icon: () => { + return ; + } + }; + list.push(item); + if (sub.children && sub.children.length > 0) { + item.children = buildItemMenus(sub.children); + } + } + return list; + } + items.value = buildItemMenus(props.menus); + console.log("items", items.value); const fsIcon = resolveComponent("FsIcon"); const buildMenus = (children: any) => { @@ -114,7 +87,7 @@ export default defineComponent({ open(sub.path); } } - slots.push(); + slots.push(); } else { slots.push( @@ -132,6 +105,7 @@ export default defineComponent({ }; const selectedKeys = ref([]); const openKeys = ref([]); + const route = useRoute(); const router = useRouter(); @@ -168,7 +142,7 @@ export default defineComponent({ return changed; } - const { asideMenuRef, onOpenChange } = useBetterScroll(props.scroll as any); + // const { asideMenuRef, onOpenChange } = useBetterScroll(props.scroll as any); watch( () => { @@ -179,7 +153,7 @@ export default defineComponent({ selectedKeys.value = [path]; const changed = openSelectedParents(path); if (changed) { - onOpenChange(); + // onOpenChange(); } }, { @@ -191,22 +165,19 @@ export default defineComponent({ ); const classNames = { "fs-menu-wrapper": true, "fs-menu-better-scroll": props.scroll }; - return ( -
- {menu} -
- ); + return
{menu}
; }; } }); diff --git a/packages/ui/certd-client/src/layout/layout-framework.vue b/packages/ui/certd-client/src/layout/layout-framework.vue index 223387c9..847e6360 100644 --- a/packages/ui/certd-client/src/layout/layout-framework.vue +++ b/packages/ui/certd-client/src/layout/layout-framework.vue @@ -106,7 +106,7 @@ diff --git a/packages/ui/certd-client/src/views/certd/suite/product-info.vue b/packages/ui/certd-client/src/views/certd/suite/product-info.vue index a3b0262f..0fbbc1bb 100644 --- a/packages/ui/certd-client/src/views/certd/suite/product-info.vue +++ b/packages/ui/certd-client/src/views/certd/suite/product-info.vue @@ -16,7 +16,13 @@
-
部署次数:
+
+ + 部署次数: + + + +
@@ -43,7 +49,7 @@
价格
- + / {{ durationDict.dataMap[selected.duration]?.label }}
@@ -58,7 +64,7 @@ import { durationDict } from "/@/views/certd/suite/api"; import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import PriceInput from "/@/views/sys/suite/product/price-input.vue"; import { ref } from "vue"; -import { dict } from "@fast-crud/fast-crud"; +import { dict, FsIcon } from "@fast-crud/fast-crud"; const props = defineProps<{ product: any; diff --git a/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue b/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue index c7358315..489ab34a 100644 --- a/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue +++ b/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue @@ -9,11 +9,17 @@
-
域名数量:
+
+ + 域名数量: +
-
部署次数:
+
+ + 部署次数: +
@@ -41,6 +47,7 @@ import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import { ref } from "vue"; import ExpiresTimeText from "/@/components/expires-time-text.vue"; import api, { SuiteDetail } from "/@/views/certd/suite/mine/api"; +import { FsIcon } from "@fast-crud/fast-crud"; defineOptions({ name: "SuiteCard" diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx index d1909e5b..db494247 100644 --- a/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx +++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx @@ -1,4 +1,4 @@ -import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { pipelineGroupApi } from "./api"; import { useRouter } from "vue-router"; import SuiteValueEdit from "/@/views/sys/suite/product/suite-value-edit.vue"; @@ -6,6 +6,7 @@ import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import DurationValue from "/@/views/sys/suite/product/duration-value.vue"; import dayjs from "dayjs"; import createCrudOptionsUser from "/@/views/sys/authority/user/crud"; +import UserSuiteStatus from "/@/views/certd/suite/mine/user-suite-status.vue"; export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { const api = pipelineGroupApi; @@ -68,6 +69,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat // } } }, + toolbar: { show: false }, rowHandle: { width: 200, fixed: "right", @@ -234,7 +236,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat component: { name: SuiteValue, vModel: "modelValue", - unit: "次" + unit: "次", + used: compute(({ row }) => { + return row.deployCountUsed; + }) }, align: "center" } @@ -281,29 +286,16 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat column: { width: 100, align: "center", + component: { + name: UserSuiteStatus, + userSuite: compute(({ row }) => { + return row; + }) + }, conditionalRender: { match() { return false; } - }, - cellRender({ row }) { - if (row.activeTime == null) { - return 未使用; - } - const now = dayjs().valueOf(); - //已过期 - const isExpired = row.expiresTime != -1 && now > row.expiresTime; - if (isExpired) { - return 已过期; - } - //如果在激活时间之前 - if (now < row.activeTime) { - return 待生效; - } - // 是否在激活时间和过期时间之间 - if (now > row.activeTime && (row.expiresTime == -1 || now < row.expiresTime)) { - return 生效中; - } } } }, @@ -342,6 +334,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat width: 100, align: "center" } + }, + createTime: { + title: "创建时间", + type: "datetime", + form: { + show: false + }, + column: { + sorter: true, + width: 160, + align: "center" + } + }, + updateTime: { + title: "更新时间", + type: "datetime", + form: { + show: false + }, + column: { + show: true, + width: 160 + } } } } diff --git a/packages/ui/certd-server/src/config/config.default.ts b/packages/ui/certd-server/src/config/config.default.ts index f1e489ee..bd760f3b 100644 --- a/packages/ui/certd-server/src/config/config.default.ts +++ b/packages/ui/certd-server/src/config/config.default.ts @@ -72,7 +72,7 @@ const development = { type: 'better-sqlite3', database: './data/db.sqlite', synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true - logging: true, + logging: false, highlightSql: false, // 配置实体模型 或者 entities: '/entity', diff --git a/packages/ui/certd-server/src/middleware/global-exception.ts b/packages/ui/certd-server/src/middleware/global-exception.ts index c36b1aa0..5a6ec009 100644 --- a/packages/ui/certd-server/src/middleware/global-exception.ts +++ b/packages/ui/certd-server/src/middleware/global-exception.ts @@ -1,7 +1,7 @@ import { Provide } from '@midwayjs/core'; import { IWebMiddleware, IMidwayKoaContext, NextFunction } from '@midwayjs/koa'; import { logger } from '@certd/basic'; -import { Result } from '@certd/lib-server'; +import { BaseException, Result } from '@certd/lib-server'; @Provide() export class GlobalExceptionMiddleware implements IWebMiddleware { @@ -14,7 +14,11 @@ export class GlobalExceptionMiddleware implements IWebMiddleware { await next(); logger.info('请求完成:', url, Date.now() - startTime + 'ms'); } catch (err) { - logger.error('请求异常:', url, Date.now() - startTime + 'ms', err); + logger.error('请求异常:', url, Date.now() - startTime + 'ms', err.message); + if (!(err instanceof BaseException)) { + logger.error(err); + } + ctx.status = 200; if (err.code == null || typeof err.code !== 'number') { err.code = 1; diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index 0620ea18..dac7f98e 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -148,7 +148,7 @@ export class PipelineService extends BaseService { return new PipelineDetail(pipeline); } - async update(bean: PipelineEntity) { + async update(bean: Partial) { //更新非trigger部分 await super.update(bean); } @@ -304,13 +304,17 @@ export class PipelineService extends BaseService { } async trigger(id: any, stepId?: string) { + const entity: PipelineEntity = await this.info(id); + if (isComm()) { + await this.checkHasDeployCount(id, entity.userId); + } this.cron.register({ name: `pipeline.${id}.trigger.once`, cron: null, job: async () => { logger.info('用户手动启动job'); try { - await this.run(id, null, stepId); + await this.doRun(entity, null, stepId); } catch (e) { logger.error('手动job执行失败:', e); } @@ -318,6 +322,21 @@ export class PipelineService extends BaseService { }); } + async checkHasDeployCount(pipelineId: number, userId: number) { + try { + return await this.userSuiteService.checkHasDeployCount(userId); + } catch (e) { + if (e instanceof NeedSuiteException) { + logger.error(e.message); + await this.update({ + id: pipelineId, + status: 'no_deploy_count', + }); + } + throw e; + } + } + async delete(id: any) { await this.clearTriggers(id); //TODO 删除storage @@ -390,10 +409,14 @@ export class PipelineService extends BaseService { async run(id: number, triggerId: string, stepId?: string) { const entity: PipelineEntity = await this.info(id); + await this.doRun(entity, triggerId, stepId); + } + async doRun(entity: PipelineEntity, triggerId: string, stepId?: string) { + const id = entity.id; let suite: UserSuiteEntity = null; if (isComm()) { - suite = await this.userSuiteService.checkHasDeployCount(entity.userId); + suite = await this.checkHasDeployCount(id, entity.userId); } const pipeline = JSON.parse(entity.content);