Compare commits

...

52 Commits

Author SHA1 Message Date
xiaojunnuo
36993cb6f8 v1.29.0 2024-12-25 01:04:12 +08:00
xiaojunnuo
c854415319 build: prepare to build 2024-12-25 01:02:01 +08:00
xiaojunnuo
aecc1cd979 build: prepare to build 2024-12-25 00:59:27 +08:00
xiaojunnuo
b2f3b0b584 build: prepare to build 2024-12-25 00:53:02 +08:00
xiaojunnuo
c937f5afc7 chore: 兼容数据库 2024-12-25 00:52:39 +08:00
xiaojunnuo
2d580a26af chore:menu.meta.show参数支持 2024-12-24 23:55:50 +08:00
xiaojunnuo
4a00a3cc1b chore: 2024-12-24 23:23:02 +08:00
xiaojunnuo
d3935219f2 perf: 调整创建证书表单字段的顺序 2024-12-24 23:14:12 +08:00
xiaojunnuo
040788c793 fix: 修复手机模式下,查询框被文字遮盖的bug 2024-12-24 17:52:03 +08:00
xiaojunnuo
005622307e fix: 修复左侧菜单收起时无法展开子菜单的bug 2024-12-24 17:09:06 +08:00
xiaojunnuo
8ebf95a222 perf: 同一时间只允许一个套餐生效 2024-12-24 10:39:54 +08:00
xiaojunnuo
7f596ed315 chore: 2024-12-24 01:16:27 +08:00
xiaojunnuo
ffa4de6911 chore: 2024-12-24 01:12:12 +08:00
xiaojunnuo
cb27d4b490 feat: 基础版不再限制流水线数量 2024-12-23 23:33:13 +08:00
xiaojunnuo
bb4910f4e5 perf: 站点证书监控通知发送,每天定时检查 2024-12-23 18:11:06 +08:00
xiaojunnuo
89c7f07034 perf: 用户名支持修改 2024-12-23 14:47:27 +08:00
xiaojunnuo
b150b2f034 chore: 2024-12-23 13:28:25 +08:00
xiaojunnuo
45d6347f5b feat: 支持微信支付 2024-12-23 13:27:04 +08:00
xiaojunnuo
67d762b6a5 perf: 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 2024-12-23 00:49:56 +08:00
xiaojunnuo
faa28f88f9 feat: 套餐购买支持易支付、支付宝支付 2024-12-23 00:24:31 +08:00
xiaojunnuo
9c8c7a7812 feat: 站点证书监控 2024-12-22 14:01:10 +08:00
xiaojunnuo
a019956698 feat: 用户套餐,用户支付功能 2024-12-22 14:00:46 +08:00
xiaojunnuo
d70e2b66a3 chore: 2024-12-20 18:04:32 +08:00
xiaojunnuo
5d568efac3 chore: suite 2024-12-20 01:00:13 +08:00
xiaojunnuo
08111f1418 chore: 2024-12-19 22:37:27 +08:00
xiaojunnuo
45839f227a chore: suite 2024-12-19 01:21:55 +08:00
xiaojunnuo
8814ffeda6 Merge branch 'v2-dev' into v2-dev-suite 2024-12-18 21:28:38 +08:00
xiaojunnuo
d224c4c124 chore: 2024-12-18 21:25:39 +08:00
xiaojunnuo
549525fb37 chore: plesk ok 2024-12-18 10:22:22 +08:00
xiaojunnuo
1c8e25beb3 chore: suite 2024-12-18 09:07:52 +08:00
xiaojunnuo
eda45c1528 perf: 支持plesk网站证书部署 2024-12-18 00:38:27 +08:00
xiaojunnuo
53c38cf714 perf: 支持一体证书 2024-12-17 22:50:18 +08:00
xiaojunnuo
0e7578043e chore: 2024-12-17 22:45:14 +08:00
xiaojunnuo
21f50e0b38 Merge branch 'v2' into v2-dev 2024-12-17 22:22:19 +08:00
greper
515f00c7cd docs: 自动更新方法(@coolxitech)
Update README.md
2024-12-17 10:52:30 +08:00
xiaojunnuo
8057586dc1 chore: suite first 2024-12-17 10:27:35 +08:00
酷曦科技
b101ac7c7f Update README.md
Include the Docker compose configuration file content for automatic version updates.
2024-12-17 00:06:21 +08:00
xiaojunnuo
64319937a1 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2024-12-13 09:51:13 +08:00
xiaojunnuo
1c0cfd6769 build: publish 2024-12-13 00:19:43 +08:00
xiaojunnuo
f8e17d5285 build: trigger build image 2024-12-13 00:19:23 +08:00
xiaojunnuo
d4385ad8a5 v1.28.4 2024-12-13 00:17:10 +08:00
xiaojunnuo
da07ce419f build: prepare to build 2024-12-13 00:08:18 +08:00
xiaojunnuo
714e0206c4 build: prepare to build 2024-12-13 00:07:32 +08:00
xiaojunnuo
40da82666a chore: 2024-12-12 18:06:07 +08:00
xiaojunnuo
79f7ec4672 perf: 群晖支持6.x 2024-12-12 17:55:54 +08:00
xiaojunnuo
0f5c69040b fix: 修复证书成功通知发送失败的bug 2024-12-12 17:28:33 +08:00
xiaojunnuo
c9d1c45d97 docs: 证书成功同志 2024-12-12 16:49:40 +08:00
xiaojunnuo
ea8fdb120c docs: 证书说明 2024-12-12 16:45:40 +08:00
xiaojunnuo
f6fa830ffe docs: 2024-12-12 12:37:38 +08:00
xiaojunnuo
992e50c014 docs: 2024-12-12 12:30:26 +08:00
xiaojunnuo
bd705d91ba build: publish 2024-12-12 12:08:12 +08:00
xiaojunnuo
2656394195 build: trigger build image 2024-12-12 12:07:54 +08:00
206 changed files with 6842 additions and 551 deletions

View File

@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复手机模式下查询框被文字遮盖的bug ([040788c](https://github.com/certd/certd/commit/040788c793642c3bb2a3ede87fe30fcf3be471bd))
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 站点证书监控 ([9c8c7a7](https://github.com/certd/certd/commit/9c8c7a781223f4217f45510db1e89495600e3cd5))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 用户名支持修改 ([89c7f07](https://github.com/certd/certd/commit/89c7f070343e86453c84677ebe1669f9b266d871))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持一体证书 ([53c38cf](https://github.com/certd/certd/commit/53c38cf714a6f7486abbf1d71c9f48f56a790100))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
### Performance Improvements
* 群晖支持6.x ([79f7ec4](https://github.com/certd/certd/commit/79f7ec4672f4fd5744cc45e4a6f104da943f4026))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes

View File

@@ -12,11 +12,13 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
* 全自动部署更新证书目前支持部署到主机、阿里云、腾讯云等目前已支持40+部署插件)
* 支持通配符域名/泛域名支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式
* 邮件通知、webhook通知
* 私有化部署数据保存本地镜像由Github Actions构建过程公开透明
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建过程公开透明
* 支持SQLitePostgreSQL、MySQL数据库
>
> 流水线数量现已调整为无限制,欢迎大家使用
>
## 二、在线体验
@@ -90,7 +92,7 @@ https://certd.handfree.work/
1. 修改`docker-compose.yaml`中的镜像版本号
2. 运行`docker compose up -d` 即可
如果使用`latest`版本
如果需要使用最新版本
```shell
#重新拉取镜像
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
@@ -98,7 +100,54 @@ docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
docker compose down
docker compose up -d
```
关于自动升级(仅限尝鲜建议非生产使用)
```yaml
version: '3.3'
services:
certd:
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
container_name: certd
restart: unless-stopped
volumes:
- /data/certd:/app/data
ports:
- "7001:7001"
- "7002:7002"
# 如果需要修改系统配置,可以通过环境变量传递;初次运行请保持默认配置
environment:
- certd_system_resetAdminPasswd=false
# 如果需要切换数据库类型可以在此处设置为mysql或postgres
# - certd_typeorm_dataSource_default_type=mysql
# - certd_typeorm_dataSource_default_host=localhost
# - certd_typeorm_dataSource_default_port=3306
# - certd_typeorm_dataSource_default_username=root
# - certd_typeorm_dataSource_default_password=123456
# - certd_typeorm_dataSource_default_database=certd
labels:
com.centurylinklabs.watchtower.enable: "true"
certd-updater: # 添加 Watchtower 服务
image: containrrr/watchtower:latest
container_name: certd-updater
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# 配置 自动更新
environment:
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
# 如果需要支持 IPv6请取消以下注释
# networks:
# ip6net:
# enable_ipv6: true
# ipam:
# config:
# - subnet: 2001:db8::/64
```
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
@@ -155,12 +204,14 @@ https://afdian.com/a/greper
专业版特权对比
| 功能 | 免费版 | 专业版 |
|---------|-------------------|-----------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 自动部署插件 | 阿里云、腾讯云、七牛云、主机部署等 | 支持群晖、宝塔、1Panel等持续开发中 |
| 发邮件功能 | 需要配置 | 免配置 |
| 证书流水线条数 | 10条 | 无限制 |
| 功能 | 基础版 | 专业版 |
|------|-----------------|-------------------|
| 免费证书申请 | 免费无限制 | 无限制 |
| 域名数量 | 免费无限制 | 无限制 |
| 证书流水线条数 | 免费无限制 | 无限制 |
| 站点证书监控 | 1 | 无限制 |
| 自动部署插件 | 阿里云、腾讯云、七牛云、SSH | 支持群晖、宝塔、1Panel等持续开发中 |
| 通知 | 邮件、webhook | server酱、企微、anpush等 |
************************

View File

@@ -1 +1 @@
22:52
00:19

View File

@@ -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 # 数据库类型

View File

@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
### Performance Improvements
* 群晖支持6.x ([79f7ec4](https://github.com/certd/certd/commit/79f7ec4672f4fd5744cc45e4a6f104da943f4026))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes
* 修复没有配置eab时报order无法读取的问题 ([657a2ae](https://github.com/certd/certd/commit/657a2ae032e6f61ac27fbdd26c7bf169c041219e))
* 修复授权被删除后无法清空的bug ([b45977c](https://github.com/certd/certd/commit/b45977c29a29084c11e496bec3415eaaebafdd74))
* mysql下access.setting字段改成text ([b7f5740](https://github.com/certd/certd/commit/b7f5740c57743914f754f3b4fdd94b59a2e8338c))
### Performance Improvements
* 点击版本红点按钮,跳转到升级帮助页面 ([454fbda](https://github.com/certd/certd/commit/454fbda581bbe22abca5b91e5086ea9d9d58a020))
* 通知标题优化 ([ff083ce](https://github.com/certd/certd/commit/ff083ce6848a8bee3c8248e4b881086ae1517c28))
* 支持腾讯虚拟机开关机([@wujingke](https://github.com/wujingke)) ([8039e8b](https://github.com/certd/certd/commit/8039e8baf83c82d03f1a6198cf61c372026b962b))
* 支持aws cloudfront ([0ae39f1](https://github.com/certd/certd/commit/0ae39f160a7c6b6696b3bf513d68aa28905810ad))
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
### Bug Fixes

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -31,15 +31,12 @@ admin/123456
## 三、升级
1. 找到容器,点击编辑
![](./images/edit1.png)
1. 找到容器,点击更多->升级
![](./images/upgrade-1.png)
2. 将latest修改为最新版本号
![](https://img.shields.io/npm/v/%40certd%2Fpipeline)
2. 选择强制拉取镜像,点击确认即可
![img.png](./images/upgrade-2.png)
![img.png](./images/edit2.png)
3. 点击确定,重启容器
## 四、数据备份

View File

@@ -38,28 +38,12 @@ admin/123456
登录后请及时修改密码
## 三、如何升级
宝塔升级certd非常简单
### 1. 应用商店安装,直接更新镜像即可
`docker`->`容器编排`->`左侧选择Certd-xxxx`->`更新镜像`
`docker`->`容器编排`->`左侧选择Certd`->`更新镜像`
![img.png](./images/upgrade.png)
### 2. latest更新方式
在主机上拉取最新镜像,然后面板上重启容器
```shell
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
```
### 3. 固定版本号方式
修改容器编排模版中的镜像版本号,然后面板上重启容器
```shell
services:
certd:
# 镜像 # 修改最新版本号 ---- ↓↓↓↓↓
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:v1.xx.x
```
## 四、数据备份
### 4.1 应用商店部署方式

View File

@@ -3,10 +3,10 @@
## 升级方法
根据不同部署方式查看升级方法
1. [Docker方式部署升级](./install/docker/#二、升级)
2. [宝塔面板方式部署升级](./install/baota/#三、如何升级)
3. [1Panel面板方式部署升级](./install/1panel/#三、升级)
4. [源码方式部署](./install/source/#二、升级)
1. [Docker方式部署升级](./docker/#二、升级)
2. [宝塔面板方式部署升级](./baota/#三、如何升级)
3. [1Panel面板方式部署升级](./1panel/#三、升级)
4. [源码方式部署](./source/#二、升级)
## 升级日志
[CHANGELOG](../changelogs/CHANGELOG.md)

View File

@@ -0,0 +1,10 @@
# 证书申请失败情况
## DNS记录问题
1. DNS 不要设置CAA记录删除即可
2. DNSSEC相关报错DNSSEC管理中删除即可
3. DNS 有其他平台申请过的_acme-challenge记录删除即可

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,8 +1,12 @@
# 群晖部署和证书更新
支持群晖`6.x``7.x`
## 一、群晖部署Certd
以下是群晖`7.x`的部署`certd`步骤。
群晖`6.x`请参考[docker部署](./../../install/docker/)
### 1. 打开Container Manager
![](./images/1.png)
@@ -32,6 +36,8 @@
## 二、更新群晖证书
证书部署插件支持群晖`6.x``7.x`
## 1. 前提条件
* 已经部署了certd
* 群晖上已经设置好了证书(证书建议设置好描述,插件需要根据描述查找证书)

View File

@@ -30,7 +30,7 @@ features:
- title: 多证书格式支持
details: 支持pem、pfx、der、jks等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构
- title: 支持私有化部署
details: 保障数据安全
details: 授权数据加密存储,保障数据安全
- title: 多数据库支持
details: 支持SQLite、Postgresql、MySQL数据库
---

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.28.3"
"version": "1.29.0"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/publishlab/node-acme-client/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/acme-client
## [1.28.4](https://github.com/publishlab/node-acme-client/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/acme-client
## [1.28.3](https://github.com/publishlab/node-acme-client/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.28.3",
"@certd/basic": "^1.29.0",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -65,5 +65,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/basic
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
12:05
01:02

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -23,6 +23,7 @@
"lodash-es": "^4.17.21",
"log4js": "^6.9.1",
"lru-cache": "^10.0.0",
"mitt": "^3.0.1",
"nanoid": "^5.0.7",
"node-forge": "^1.3.1",
"nodemailer": "^6.9.3"
@@ -43,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -8,6 +8,7 @@ export * from './util.hash.js';
export * from './util.merge.js';
export * from './util.cache.js';
export * from './util.string.js';
export * from './util.lock.js';
import { stringUtils } from './util.string.js';
import sleep from './util.sleep.js';
import { http, download } from './util.request.js';
@@ -24,6 +25,8 @@ import { domainUtils } from './util.domain.js';
import { optionsUtils } from './util.options.js';
import { nanoid } from 'nanoid';
import * as id from './util.id.js';
import { locker } from './util.lock.js';
import { mitter } from './util.mitter.js';
export const utils = {
sleep,
http,
@@ -41,4 +44,6 @@ export const utils = {
domain: domainUtils,
options: optionsUtils,
string: stringUtils,
locker,
mitter,
};

View File

@@ -1,4 +1,4 @@
export function isDev() {
const nodeEnv = process.env.NODE_ENV || '';
return nodeEnv === 'development' || nodeEnv.indexOf('local') >= 0;
return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev');
}

View File

@@ -3,7 +3,10 @@ import crypto from 'crypto';
function md5(data: string) {
return crypto.createHash('md5').update(data).digest('hex');
}
function sha256(data: string) {
return crypto.createHash('sha256').update(data).digest('hex');
}
export const hashUtils = {
md5,
sha256,
};

View File

@@ -0,0 +1,47 @@
import { logger, utils } from './index.js';
export class Locker {
locked: Record<string, any> = {};
async execute(lockStr: string, callback: any) {
await this.lock(lockStr);
const timeoutId = setTimeout(() => {
logger.warn('Lock timeout,自动解锁', lockStr);
this.unlock(lockStr);
}, 20000);
try {
return await callback();
} finally {
clearTimeout(timeoutId);
this.unlock(lockStr);
}
}
async lock(str: string) {
const isLocked = this.isLocked(str);
if (isLocked) {
let count = 0;
while (true) {
await utils.sleep(100);
if (!this.isLocked(str)) {
break;
}
count++;
if (count > 20) {
throw new Error('Lock timeout');
}
}
}
this.locked[str] = true;
}
unlock(str: string) {
delete this.locked[str];
}
isLocked(str: string) {
return this.locked[str] ?? false;
}
}
export const locker = new Locker();

View File

@@ -0,0 +1,2 @@
import mitt from 'mitt';
export const mitter = mitt();

View File

@@ -37,6 +37,8 @@ function buildGroupOptions(options: any[], inDomains: string[]) {
}
export const optionsUtils = {
//获取分组
groupByDomain,
//构建分组后的选项列表,常用
buildGroupOptions,
};

View File

@@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios';
import axios, { AxiosHeaders, AxiosRequestConfig } from 'axios';
import { ILogger, logger } from './util.log.js';
import { Logger } from 'log4js';
import { HttpProxyAgent } from 'http-proxy-agent';
@@ -13,7 +13,7 @@ export class HttpError extends Error {
statusText?: string;
code?: string;
request?: { baseURL: string; url: string; method: string; params?: any; data?: any };
response?: { data: any };
response?: { data: any; headers: AxiosHeaders };
cause?: any;
constructor(error: any) {
if (!error) {
@@ -55,6 +55,7 @@ export class HttpError extends Error {
this.response = {
data: error.response?.data,
headers: error.response?.headers,
};
const { stack, cause } = error;
@@ -156,13 +157,13 @@ export function createAxiosService({ logger }: { logger: Logger }) {
error.message = '请求错误';
break;
case 401:
error.message = '未授权,请登录';
error.message = '认证/登录失败';
break;
case 403:
error.message = '拒绝访问';
break;
case 404:
error.message = `请求地址出错: ${error.response.config.url}`;
error.message = `请求地址出错`;
break;
case 408:
error.message = '请求超时';
@@ -216,6 +217,7 @@ export type HttpRequestConfig<D = any> = {
logParams?: boolean;
logRes?: boolean;
httpProxy?: string;
returnResponse?: boolean;
} & AxiosRequestConfig<D>;
export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -16,8 +16,8 @@
"test": "mocha --loader=ts-node/esm"
},
"dependencies": {
"@certd/basic": "^1.28.3",
"@certd/plus-core": "^1.28.3",
"@certd/basic": "^1.29.0",
"@certd/plus-core": "^1.29.0",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -92,13 +92,17 @@ export class Executor {
await this.onChanged(this.runtime);
}, 5000);
await this.runWithHistory(this.pipeline, "pipeline", async () => {
const result = await this.runWithHistory(this.pipeline, "pipeline", async () => {
return await this.runStages(this.pipeline);
});
if (this.lastRuntime && this.lastRuntime.pipeline.status?.status === ResultType.error) {
await this.notification("turnToSuccess");
if (result === ResultType.success) {
if (this.lastRuntime && this.lastRuntime.pipeline.status?.status === ResultType.error) {
await this.notification("turnToSuccess");
} else {
await this.notification("success");
}
}
await this.notification("success");
return result;
} catch (e: any) {
await this.notification("error", e);
this.logger.error("pipeline 执行失败", e);
@@ -336,7 +340,7 @@ export class Executor {
instance.setCtx(taskCtx);
await instance.onInstance();
await instance.execute();
const result = await instance.execute();
//执行结果处理
if (instance._result.clearLastStatus) {
//是否需要清除所有状态
@@ -364,6 +368,8 @@ export class Executor {
merge(vars, instance._result.pipelinePrivateVars);
await this.pipelineContext.setObj("privateVars", vars);
}
return result;
}
async notification(when: NotificationWhen, error?: any) {
@@ -385,7 +391,7 @@ export class Executor {
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`;
} else if (when === "error") {
subject = `执行失败,${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}\n错误详情:${error.message}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
} else {
return;
}

View File

@@ -134,6 +134,7 @@ export class RunHistory {
export class RunnableCollection {
private collection: RunnableMap = {};
private pipeline!: Pipeline;
currentStep!: Step;
constructor(pipeline?: Pipeline) {
if (!pipeline) {
return;
@@ -143,6 +144,19 @@ export class RunnableCollection {
this.collection = map;
}
static initPipelineRunnableType(pipeline: Pipeline) {
pipeline.runnableType = "pipeline";
pipeline.stages.forEach((stage) => {
stage.runnableType = "stage";
stage.tasks.forEach((task) => {
task.runnableType = "task";
task.steps.forEach((step) => {
step.runnableType = "step";
});
});
});
}
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
list.forEach((item) => {
exec(item);
@@ -193,5 +207,8 @@ export class RunnableCollection {
add(runnable: Runnable) {
this.collection[runnable.id] = runnable;
if (runnable.runnableType === "step") {
this.currentStep = runnable as Step;
}
}
}

View File

@@ -53,7 +53,7 @@ export type NotificationSendReq = {
useDefault?: boolean;
useEmail?: boolean;
emailAddress?: string;
logger?: ILogger;
logger: ILogger;
body: NotificationBody;
};
export interface INotificationService {

View File

@@ -59,7 +59,7 @@ export type PluginDefine = Registrable & {
export type ITaskPlugin = {
onInstance(): Promise<void>;
execute(): Promise<void>;
execute(): Promise<void | string>;
onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
[key: string]: any;
};
@@ -184,7 +184,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
return;
}
abstract execute(): Promise<void>;
abstract execute(): Promise<void | string>;
appendTimeSuffix(name?: string) {
if (name == null) {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-huawei
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-huawei
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -21,5 +21,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-iframe
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-iframe
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -30,5 +30,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-k8s
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-k8s
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -16,7 +16,7 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/basic": "^1.28.3",
"@certd/basic": "^1.29.0",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
### Performance Improvements
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-server
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.28.3",
"version": "1.29.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.28.3",
"@certd/basic": "^1.28.3",
"@certd/pipeline": "^1.28.3",
"@certd/plus-core": "^1.28.3",
"@certd/acme-client": "^1.29.0",
"@certd/basic": "^1.29.0",
"@certd/pipeline": "^1.29.0",
"@certd/plus-core": "^1.29.0",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.17.1",
"@midwayjs/i18n": "~3.17.3",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -25,7 +25,7 @@ export abstract class BaseController {
* @param msg
* @param code
*/
fail(msg: string, code: any) {
fail(msg: string, code?: any) {
return {
code: code ? code : Constants.res.error.code,
msg: msg ? msg : Constants.res.error.code,
@@ -39,4 +39,12 @@ export abstract class BaseController {
}
return userId;
}
getLoginUser() {
const user = this.ctx.user;
if (user == null) {
throw new Error('Token已过期');
}
return user;
}
}

View File

@@ -3,6 +3,7 @@ import { In, Repository, SelectQueryBuilder } from 'typeorm';
import { Inject } from '@midwayjs/core';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { EntityManager } from 'typeorm/entity-manager/EntityManager.js';
import { FindManyOptions } from 'typeorm';
export type PageReq<T = any> = {
page?: { offset: number; limit: number };
@@ -15,6 +16,7 @@ export type ListReq<T = any> = {
asc: boolean;
};
buildQuery?: (bq: SelectQueryBuilder<any>) => void;
select?: any;
};
/**
@@ -53,7 +55,7 @@ export abstract class BaseService<T> {
* 非分页查询
* @param options
*/
async find(options) {
async find(options: FindManyOptions<T>) {
return await this.getRepository().find(options);
}
@@ -99,7 +101,7 @@ export abstract class BaseService<T> {
* 新增|修改
* @param param 数据
*/
async addOrUpdate(param) {
async addOrUpdate(param: any) {
await this.getRepository().save(param);
}
@@ -107,7 +109,7 @@ export abstract class BaseService<T> {
* 新增
* @param param 数据
*/
async add(param) {
async add(param: any) {
const now = new Date();
param.createTime = now;
param.updateTime = now;
@@ -122,7 +124,7 @@ export abstract class BaseService<T> {
* 修改
* @param param 数据
*/
async update(param) {
async update(param: any) {
if (!param.id) throw new ValidateException('id 不能为空');
param.updateTime = new Date();
await this.addOrUpdate(param);
@@ -148,6 +150,7 @@ export abstract class BaseService<T> {
page.limit = 20;
}
const qb = this.buildListQuery(pageReq);
qb.offset(page.offset).limit(page.limit);
const list = await qb.getMany();
const total = await qb.getCount();

View File

@@ -36,6 +36,10 @@ export const Constants = {
code: 88,
message: '需要VIP',
},
needsuite: {
code: 89,
message: '需要购买或升级套餐',
},
loginError: {
code: 2,
message: '登录失败',

View File

@@ -49,4 +49,10 @@ export abstract class CrudController<T> extends BaseController {
await this.getService().delete([id]);
return this.ok(null);
}
@Post('/deleteByIds')
async deleteByIds(@Body('ids') ids: number[]) {
await this.getService().delete(ids);
return this.ok(null);
}
}

View File

@@ -8,3 +8,9 @@ export class NeedVIPException extends BaseException {
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
}
}
export class NeedSuiteException extends BaseException {
constructor(message) {
super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
}
}

View File

@@ -1,7 +1,8 @@
import { SysSettingsEntity } from './system/index.js';
import { AccessEntity } from './user/access/entity/access.js';
export * from './basic/index.js';
export * from './system/index.js';
export * from './user/index.js';
export { LibServerConfiguration as Configuration } from './configuration.js';
export const libServerEntities = [SysSettingsEntity];
export const libServerEntities = [SysSettingsEntity, AccessEntity];

View File

@@ -14,7 +14,7 @@ export const uploadTmpFileCacheKey = 'tmpfile_key_';
/**
*/
@Provide()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class FileService {
async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') {
if (tmpCacheKey.startsWith(`/${permission}`)) {

View File

@@ -5,7 +5,7 @@ import { SysInstallInfo, SysLicenseInfo, SysSettingsService } from '../../settin
import { merge } from 'lodash-es';
@Provide()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PlusService {
@Inject()
sysSettingsService: SysSettingsService;

View File

@@ -136,3 +136,35 @@ export class SysHeaderMenus extends BaseSettings {
menus: MenuItem[];
}
export type PaymentItem = {
enabled: boolean;
accessId?: number;
};
export class SysPaymentSetting extends BaseSettings {
static __title__ = '支付设置';
static __key__ = 'sys.payment';
static __access__ = 'private';
yizhifu?: PaymentItem = { enabled: false };
alipay?: PaymentItem = { enabled: false };
wxpay?: PaymentItem = { enabled: false };
}
export class SysSuiteSetting extends BaseSettings {
static __title__ = '套餐设置';
static __key__ = 'sys.suite';
static __access__ = 'private';
enabled = false;
registerGift?: {
productId: number;
duration: number;
};
intro?: string;
}

View File

@@ -12,7 +12,7 @@ import * as dns from 'node:dns';
* 设置
*/
@Provide()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class SysSettingsService extends BaseService<SysSettingsEntity> {
@InjectEntityModel(SysSettingsEntity)
repository: Repository<SysSettingsEntity>;

View File

@@ -0,0 +1,5 @@
export * from './entity/access.js';
export * from './service/access-service.js';
export * from './service/access-sys-getter.js';
export * from './service/access-getter.js';
export * from './service/encrypt-service.js';

View File

@@ -1,7 +1,7 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, PageReq, PermissionException, ValidateException } from '@certd/lib-server';
import { BaseService, PageReq, PermissionException, ValidateException } from '../../../index.js';
import { AccessEntity } from '../entity/access.js';
import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline';
import { EncryptService } from './encrypt-service.js';
@@ -10,7 +10,7 @@ import { EncryptService } from './encrypt-service.js';
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AccessService extends BaseService<AccessEntity> {
@InjectEntityModel(AccessEntity)
repository: Repository<AccessEntity>;
@@ -18,6 +18,7 @@ export class AccessService extends BaseService<AccessEntity> {
@Inject()
encryptService: EncryptService;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
getRepository() {
return this.repository;

View File

@@ -1,7 +1,6 @@
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import crypto from 'crypto';
import { SysSettingsService } from '@certd/lib-server';
import { SysPrivateSettings } from '@certd/lib-server';
import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js';
/**
*

View File

@@ -0,0 +1 @@
export * from './access/index.js';

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.28.3",
"version": "1.29.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持一体证书 ([53c38cf](https://github.com/certd/certd/commit/53c38cf714a6f7486abbf1d71c9f48f56a790100))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-cert
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,9 +15,9 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^1.28.3",
"@certd/basic": "^1.28.3",
"@certd/pipeline": "^1.28.3",
"@certd/acme-client": "^1.29.0",
"@certd/basic": "^1.29.0",
"@certd/pipeline": "^1.29.0",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -40,5 +40,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -24,13 +24,15 @@ export type DomainsVerifyPlan = {
};
export type CertInfo = {
crt: string;
key: string;
csr: string;
ic?: string;
crt: string; //fullchain证书
key: string; //私钥
csr: string; //csr
oc?: string; //仅证书非fullchain证书
ic?: string; //中间证书
pfx?: string;
der?: string;
jks?: string;
one?: string;
};
export type SSLProvider = "letsencrypt" | "google" | "zerossl";
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";

View File

@@ -120,10 +120,11 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
abstract doCertApply(): Promise<any>;
async execute(): Promise<void> {
async execute(): Promise<string | void> {
const oldCert = await this.condition();
if (oldCert != null) {
return await this.output(oldCert, false);
await this.output(oldCert, false);
return "skip";
}
const cert = await this.doCertApply();
if (cert != null) {
@@ -191,7 +192,8 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
zip.file("cert.crt", cert.crt);
zip.file("cert.key", cert.key);
zip.file("intermediate.crt", cert.ic);
zip.file("origin.crt", cert.oc);
zip.file("one.pem", cert.one);
if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
}
@@ -201,6 +203,21 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
if (cert.jks) {
zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
}
zip.file(
"说明.txt",
`证书文件说明
cert.crt证书文件包含证书链pem格式
cert.key私钥文件pem格式
intermediate.crt中间证书文件pem格式
origin.crt原始证书文件不含证书链pem格式
one.pem 证书和私钥简单合并成一个文件pem格式crt正文+key正文
cert.pfxpfx格式证书文件iis服务器使用
cert.derder格式证书文件
cert.jksjks格式证书文件java服务器使用
`
);
const content = await zip.generateAsync({ type: "nodebuffer" });
this.saveFile(filename, content);
this.logger.info(`已保存文件:${filename}`);
@@ -216,35 +233,36 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
// 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("输入参数变更,准备申请新证书");
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;
@@ -320,6 +338,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
useDefault: true,
useEmail: true,
emailAddress: this.email,
logger: this.logger,
body,
});
} catch (e) {

View File

@@ -10,19 +10,23 @@ export type CertReaderHandleContext = {
reader: CertReader;
tmpCrtPath: string;
tmpKeyPath: string;
tmpOcPath?: string;
tmpPfxPath?: string;
tmpDerPath?: string;
tmpIcPath?: string;
tmpJksPath?: string;
tmpOnePath?: string;
};
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
export class CertReader {
cert: CertInfo;
oc: string; //仅证书非fullchain证书
crt: string;
key: string;
csr: string;
ic: string; //中间证书
one: string; //crt + key 合成一个pem文件
detail: any;
expires: number;
@@ -38,6 +42,18 @@ export class CertReader {
this.cert.ic = this.ic;
}
this.oc = certInfo.oc;
if (!this.oc) {
this.oc = this.getOc();
this.cert.oc = this.oc;
}
this.one = certInfo.one;
if (!this.one) {
this.one = this.crt + "\n" + this.key;
this.cert.one = this.one;
}
const { detail, expires } = this.getCrtDetail(this.cert.crt);
this.detail = detail;
this.expires = expires.getTime();
@@ -56,11 +72,22 @@ export class CertReader {
return ic.trim();
}
getOc() {
//原始证书 就是crt的第一个 -----END CERTIFICATE----- 之前的内容
const endStr = "-----END CERTIFICATE-----";
const arr = this.crt.split(endStr);
return arr[0] + endStr;
}
toCertInfo(): CertInfo {
return this.cert;
}
getCrtDetail(crt: string = this.cert.crt) {
return CertReader.readCertDetail(crt);
}
static readCertDetail(crt: string) {
const detail = crypto.readCertificateInfo(crt.toString());
const expires = detail.notAfter;
return { detail, expires };
@@ -73,7 +100,7 @@ export class CertReader {
return domains;
}
saveToFile(type: "crt" | "key" | "pfx" | "der" | "ic" | "jks", filepath?: string) {
saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks", filepath?: string) {
if (!this.cert[type]) {
return;
}
@@ -87,7 +114,7 @@ export class CertReader {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (type === "crt" || type === "key" || type === "ic") {
if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one") {
fs.writeFileSync(filepath, this.cert[type]);
} else {
fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64"));
@@ -102,9 +129,11 @@ export class CertReader {
const tmpKeyPath = this.saveToFile("key");
const tmpPfxPath = this.saveToFile("pfx");
const tmpIcPath = this.saveToFile("ic");
logger.info("本地文件写入成功");
const tmpOcPath = this.saveToFile("oc");
const tmpDerPath = this.saveToFile("der");
const tmpJksPath = this.saveToFile("jks");
const tmpOnePath = this.saveToFile("one");
logger.info("本地文件写入成功");
try {
return await opts.handle({
reader: this,
@@ -114,6 +143,8 @@ export class CertReader {
tmpDerPath: tmpDerPath,
tmpIcPath: tmpIcPath,
tmpJksPath: tmpJksPath,
tmpOcPath: tmpOcPath,
tmpOnePath,
});
} catch (err) {
throw err;
@@ -128,9 +159,11 @@ export class CertReader {
removeFile(tmpCrtPath);
removeFile(tmpKeyPath);
removeFile(tmpPfxPath);
removeFile(tmpOcPath);
removeFile(tmpDerPath);
removeFile(tmpIcPath);
removeFile(tmpJksPath);
removeFile(tmpOnePath);
}
}

View File

@@ -62,6 +62,23 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
challengeType!: string;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
],
},
helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好仅首次需要翻墙获取EAB授权\nZeroSSL需要EAB授权无需翻墙",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({
title: "DNS解析服务商",
component: {
@@ -125,23 +142,6 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
domainsVerifyPlan!: DomainsVerifyPlanInput;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
],
},
helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好仅首次需要翻墙获取EAB授权\nZeroSSL需要EAB授权无需翻墙",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({
title: "Google公共EAB授权",
isSys: true,

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/plugin-lib
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-lib
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.28.3",
"version": "1.29.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,9 +16,9 @@
},
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/basic": "^1.28.3",
"@certd/pipeline": "^1.28.3",
"@certd/plugin-cert": "^1.28.3",
"@certd/basic": "^1.29.0",
"@certd/pipeline": "^1.29.0",
"@certd/plugin-cert": "^1.29.0",
"@kubernetes/client-node": "0.21.0",
"dayjs": "^1.11.7",
"iconv-lite": "^0.6.3",
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "e5c164065cef75a6813a4ff48b2167a966dce112"
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
}

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复手机模式下查询框被文字遮盖的bug ([040788c](https://github.com/certd/certd/commit/040788c793642c3bb2a3ede87fe30fcf3be471bd))
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 用户名支持修改 ([89c7f07](https://github.com/certd/certd/commit/89c7f070343e86453c84677ebe1669f9b266d871))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/ui-client
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.28.3",
"version": "1.29.0",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -56,6 +56,7 @@
"pinia": "2.1.7",
"psl": "^1.9.0",
"qiniu-js": "^3.4.2",
"qrcode": "^1.5.4",
"sortablejs": "^1.15.2",
"vue": "^3.4.21",
"vue-cropperjs": "^5.0.0",
@@ -65,8 +66,8 @@
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@certd/lib-iframe": "^1.28.3",
"@certd/pipeline": "^1.28.3",
"@certd/lib-iframe": "^1.29.0",
"@certd/pipeline": "^1.29.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -35,7 +35,9 @@ export type SysPublicSetting = {
managerOtherUserPipeline?: boolean;
icpNo?: string;
};
export type SuiteSetting = {
enabled?: boolean;
};
export type SysPrivateSetting = {
httpProxy?: string;
httpsProxy?: string;
@@ -67,6 +69,7 @@ export type AllSettings = {
siteInfo: SiteInfo;
siteEnv: SiteEnv;
headerMenus: HeaderMenus;
suiteSetting: SuiteSetting;
};
export async function loadAllSettings(): Promise<AllSettings> {

View File

@@ -25,6 +25,10 @@ function createService() {
if (response.config.responseType === "blob") {
return response;
}
//@ts-ignore
if (response.config.returnResponse) {
return response;
}
// dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data;

View File

@@ -0,0 +1,61 @@
<template>
<span class="cd-expires-time-text">
<component :is="wrapperComp" :color="color">
<template v-if="label != null">
{{ label }}
</template>
<template v-else>
<FsTimeHumanize :model-value="value" :use-format-greater="1000000000000000" :options="{ units: ['y', 'd'] }"></FsTimeHumanize>
</template>
</component>
</span>
</template>
<script lang="ts" setup>
import dayjs from "dayjs";
import { computed } from "vue";
defineOptions({
name: "ExpiresTimeText"
});
const props = defineProps<{
value?: number;
mode?: "tag" | "text";
}>();
const wrapperComp = computed(() => {
if (props.mode === "tag") {
return "a-tag";
}
return "span";
});
const color = computed(() => {
if (props.value == null) {
return "";
}
//距离今天多少天
const days = dayjs(props.value).diff(dayjs(), "day");
if (props.value === -1 || days > 365) {
return "green";
}
//小于3天 红色
if (days <= 6) {
return "red";
}
return "blue";
});
const label = computed(() => {
if (props.value == null) {
return "";
}
if (props.value === -1) {
return "永久";
}
return null;
});
</script>

View File

@@ -9,6 +9,7 @@ import "@vue-js-cron/light/dist/light.css";
import Plugins from "./plugins/index";
import LoadingButton from "./loading-button.vue";
import IconSelect from "./icon-select.vue";
import ExpiresTimeText from "./expires-time-text.vue";
export default {
install(app: any) {
app.component("PiContainer", PiContainer);
@@ -25,7 +26,7 @@ export default {
app.component("LoadingButton", LoadingButton);
app.component("IconSelect", IconSelect);
app.component("ExpiresTimeText", ExpiresTimeText);
app.use(vip);
app.use(Plugins);
}

View File

@@ -229,27 +229,44 @@ function openUpgrade() {
const vipTypeDefine = {
free: {
title: "基础版",
desc: "免费使用",
desc: "社区免费版",
type: "free",
privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"]
icon: "lucide:package-open",
privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件", "邮件、webhook通知方式"]
},
plus: {
title: "专业版",
desc: "功能增强,适用于个人企业内部使用",
desc: "开源需要您的赞助支持",
type: "plus",
privilege: ["可加VIP群需求优先实现", "证书流水线数量无限制", "免配置发邮件功能", "支持宝塔、易盾、群晖、1Panel、cdnfly等部署插件"],
privilege: ["可加VIP群需求优先实现", "站点证书监控无限制", "更多通知方式", "更多强大部署插件宝塔、群晖、1Panel等"],
trial: {
title: "7天试用",
title: "点击获取7天试用",
click: () => {
openStarModal();
}
},
icon: "stash:thumb-up",
price: 29.9,
get() {
return (
<a-tooltip title="爱发电赞助“VIP会员”后获取一年期专业版激活码开源需要您的支持">
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
爱发电赞助后获取
</a-button>
</a-tooltip>
);
}
},
comm: {
title: "商业版",
desc: "商业授权,可对外运营",
type: "comm",
privilege: ["拥有专业版所有特权", "允许商用可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付(敬请期待)"]
icon: "vaadin:handshake",
privilege: ["拥有专业版所有特权", "允许商用可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付"],
price: 399,
get() {
return <a-button size="small">请联系作者获取</a-button>;
}
}
};
@@ -260,28 +277,16 @@ function openUpgrade() {
},
maskClosable: true,
okText: "激活",
width: 900,
width: 1000,
content: () => {
let activationCodeGetWay: any = null;
if (settingStore.siteEnv.agent.enabled != null) {
const agent = settingStore.siteEnv.agent;
if (agent.enabled === false) {
activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
爱发电赞助VIP会员¥29.9后获取一年期专业版激活码
</a>
<span> 商业版请直接联系作者</span>
</span>
);
} else {
activationCodeGetWay = (
<a href={agent.contactLink} target="_blank">
{agent.contactText}
</a>
);
}
}
let activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
爱发电赞助VIP会员后获取一年期专业版激活码
</a>
<span> 商业版请直接联系作者</span>
</span>
);
const vipLabel = settingStore.vipLabel;
const slots = [];
for (const key in vipTypeDefine) {
@@ -291,8 +296,8 @@ function openUpgrade() {
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header">
<span>{item.title}</span>
<h3 class="block-header ">
<span class="flex-o">{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
@@ -301,15 +306,34 @@ function openUpgrade() {
</span>
)}
</h3>
<div>{item.desc}</div>
<ul>
<div style="color:green" class="flex-o">
<fs-icon icon={item.icon} class="fs-16 flex-o" />
{item.desc}
</div>
<ul class="flex-1 privilege">
{item.privilege.map((p: string) => (
<li>
<li class="flex-baseline">
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
{p}
</li>
))}
</ul>
<div class="footer flex-between flex-vc">
<div class="price-show">
{item.price && (
<span>
<span class="price-text">¥{item.price}</span>
/
</span>
)}
{!item.price && (
<span>
<span class="price-text">免费</span>
</span>
)}
</div>
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
</div>
</div>
</a-col>
);
@@ -372,10 +396,12 @@ onMounted(() => {
.vip-active-modal {
.vip-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #eee;
border-radius: 5px;
height: 195px;
height: 250px;
//background-color: rgba(250, 237, 167, 0.79);
&.current {
border-color: green;
@@ -389,6 +415,16 @@ onMounted(() => {
font-wight: 400;
}
}
.footer {
padding-top: 5px;
margin-top: 0px;
border-top: 1px solid #eee;
.price-text {
font-size: 18px;
color: red;
}
}
}
ul {
@@ -400,9 +436,13 @@ onMounted(() => {
color: green;
}
.vip-type-vs {
.privilege {
.fs-icon {
color: green;
}
}
.fs-icon {
margin-right: 5px;
color: green;
}
}
}

View File

@@ -0,0 +1,124 @@
<template>
<a-menu v-model:open-keys="openKeys" v-model:selected-keys="selectedKeys" class="fs-menu" mode="inline" theme="light" :items="items" @click="onClick" />
</template>
<script lang="tsx" setup>
import { ref, watch, defineOptions } from "vue";
import { routerUtils } from "/@/utils/util.router";
import { useRoute } from "vue-router";
import { utils } from "@fast-crud/fast-crud";
import * as _ from "lodash-es";
defineOptions({
name: "FsMenu"
});
const props = defineProps<{
menus: any[];
expandSelected: boolean;
}>();
const items = ref([]);
function buildItemMenus(menus: any) {
if (menus == null) {
return;
}
const list: any = [];
for (const sub of menus) {
if (sub.meta?.show != null) {
if (sub.meta.show === false || (typeof sub.meta.show === "function" && !sub.meta.show())) {
continue;
}
}
const item: any = {
key: sub.path,
label: sub.title,
title: sub.title,
icon: () => {
return <fsIcon icon={sub.icon ?? sub.meta?.icon} />;
}
};
list.push(item);
if (sub.children && sub.children.length > 0) {
item.children = buildItemMenus(sub.children);
}
}
return list;
}
watch(
() => props.menus,
(menus) => {
items.value = buildItemMenus(menus);
},
{ immediate: true }
);
async function onClick(item: any) {
await routerUtils.open(item.key);
}
const route = useRoute();
const selectedKeys = ref([]);
const openKeys = ref([]);
function openSelectedParents(fullPath: any) {
if (!props.expandSelected) {
return;
}
if (props.menus == null) {
return;
}
const keys: any = [];
let changed = false;
utils.deepdash.forEachDeep(props.menus, (value: any, key: any, parent: any, context: any) => {
if (value == null) {
return;
}
if (value.path === fullPath) {
_.forEach(context.parents, (item) => {
if (item.value instanceof Array) {
return;
}
keys.push(item.value.path);
});
}
});
if (keys.length > 0) {
for (const key of keys) {
if (openKeys.value.indexOf(key) === -1) {
openKeys.value.push(key);
changed = true;
}
}
}
return changed;
}
watch(
() => {
return route.fullPath;
},
(path) => {
// path = route.fullPath;
selectedKeys.value = [path];
const changed = openSelectedParents(path);
if (changed) {
// onOpenChange();
}
},
{
immediate: true
}
);
</script>
<style lang="less">
.fs-menu {
height: 100%;
overflow-y: auto;
.fs-icon {
font-size: 16px !important;
min-width: 16px !important;
}
}
</style>

View File

@@ -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 <fsIcon icon={sub.icon ?? sub.meta?.icon} />;
}
};
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(<a-sub-menu key={sub.path} v-slots={subSlots} onTitleClick={onTitleClick} />);
slots.push(<a-sub-menu key={sub.path} v-slots={subSlots} />);
} else {
slots.push(
<a-menu-item key={sub.path} title={sub.title}>
@@ -132,6 +105,7 @@ export default defineComponent({
};
const selectedKeys = ref([]);
const openKeys = ref([]);
const route = useRoute();
const router = useRouter();
@@ -153,7 +127,7 @@ export default defineComponent({
if (item.value instanceof Array) {
return;
}
keys.push(item.value.index);
keys.push(item.value.path);
});
}
});
@@ -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({
<a-menu
mode={"inline"}
theme={"light"}
v-slots={slots}
onClick={onSelect}
onOpenChange={onOpenChange}
// v-slots={slots}
// onClick={onSelect}
// onOpenChange={onOpenChange}
v-models={[
[openKeys.value, "openKeys"],
[selectedKeys.value, "selectedKeys"]
]}
{...ctx.attrs}
items={items.value}
inlineCollapsed={!props.expandSelected}
/>
);
const classNames = { "fs-menu-wrapper": true, "fs-menu-better-scroll": props.scroll };
return (
<div ref={asideMenuRef} class={classNames}>
{menu}
</div>
);
return <div>{menu}</div>;
};
}
});

View File

@@ -1,6 +1,6 @@
<template>
<a-layout class="fs-framework">
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible>
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible :width="210">
<div class="header-logo">
<img :src="siteInfo.logo" />
<span v-if="!asideCollapsed" class="title">{{ siteInfo.title }}</span>
@@ -106,7 +106,7 @@
<script lang="ts" setup>
import { computed, onErrorCaptured, onMounted, ref } from "vue";
import FsMenu from "./components/menu/index.jsx";
import FsMenu from "./components/menu/index.vue";
import FsLocale from "./components/locale/index.vue";
import FsUserInfo from "./components/user-info/index.vue";
import FsTabs from "./components/tabs/index.vue";

View File

@@ -128,6 +128,15 @@ function install(app: App, options: any = {}) {
return { currentPage, pageSize, records: res.records, total: res.total, ...res };
}
},
search: {
formItem: {
wrapperCol: {
style: {
width: "50%"
}
}
}
},
form: {
display: "flex",
labelCol: {

View File

@@ -1,7 +1,7 @@
import Validator from "async-validator";
// 自定义验证器函数
export function isDomain(rule: any, value: any) {
if (value == null) {
if (value == null || value == "") {
return true;
}
let domains: string[] = value;

View File

@@ -1,3 +1,5 @@
import { useSettingStore } from "/@/store/modules/settings";
export const certdResources = [
{
title: "证书自动化",
@@ -39,47 +41,165 @@ export const certdResources = [
}
},
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
title: "站点证书监控",
name: "SiteCertMonitor",
path: "/certd/monitor/site",
component: "/certd/monitor/site/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{
title: "通知设置",
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
cache: true
}
},
{
title: "CNAME记录管理",
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
icon: "ion:videocam-outline",
auth: true
}
},
{
title: "分组管理",
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
title: "设置",
name: "MineSetting",
path: "/certd/mine",
meta: {
icon: "mdi:format-list-group",
auth: true
}
icon: "ion:settings-outline",
auth: true,
cache: true
},
children: [
{
title: "CNAME记录管理",
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true
}
},
{
title: "流水线分组管理",
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true
}
},
{
title: "证书仓库",
name: "CertStore",
path: "/certd/monitor/cert",
component: "/certd/monitor/cert/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isPlus;
},
icon: "ion:shield-checkmark-outline",
auth: true,
isMenu: false
}
},
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{
title: "通知设置",
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
cache: true
}
},
{
title: "账号信息",
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false
}
}
]
},
{
title: "套餐",
name: "SuiteProduct",
path: "/certd/suite",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm && settingStore.isSuiteEnabled;
},
icon: "ion:cart-outline",
auth: true
},
children: [
{
title: "我的套餐",
name: "MySuite",
path: "/certd/suite/mine",
component: "/certd/suite/mine/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true
}
},
{
title: "套餐购买",
name: "SuiteProductBuy",
path: "/certd/suite/buy",
component: "/certd/suite/buy.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart-outline",
auth: true
}
},
{
title: "我的订单",
name: "MyTrade",
path: "/certd/trade",
component: "/certd/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check-outline",
auth: true
}
},
{
title: "支付返回",
name: "PaymentReturn",
path: "/certd/payment/return/:type",
component: "/certd/payment/return.vue",
meta: {
icon: "ant-design:pay-circle-outlined",
auth: false,
isMenu: false
}
}
]
}
// {
// title: "邮箱设置",
// name: "EmailSetting",
@@ -90,17 +210,6 @@ export const certdResources = [
// auth: true
// }
// },
{
title: "账号信息",
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false
}
}
]
}
];

View File

@@ -169,35 +169,65 @@ export const sysResources = [
icon: "ion:person-outline",
permission: "sys:auth:user:view"
}
}
},
// {
// title: "商业版设置",
// name: "SysCommercial",
// meta: {
// icon: "ion:document-text-outline",
// permission: "sys:settings:view",
// show: () => {
// const settingStore = useSettingStore();
// return settingStore.isComm;
// }
// },
// children: [
// {
// title: "套餐设置",
// name: "suite",
// path: "/sys/commercial/suite",
// meta: {
// icon: "ion:document-text-outline",
// permission: "sys:settings:view",
// show: () => {
// const settingStore = useSettingStore();
// return settingStore.isComm;
// }
// }
// }
// ]
// }
{
title: "套餐管理",
name: "SuiteManager",
path: "/sys/suite",
meta: {
icon: "ion:cart-outline",
permission: "sys:settings:edit",
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
}
},
children: [
{
title: "套餐设置",
name: "SuiteSetting",
path: "/sys/suite/setting",
component: "/sys/suite/setting/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart",
permission: "sys:settings:edit"
}
},
{
title: "订单管理",
name: "OrderManager",
path: "/sys/suite/trade",
component: "/sys/suite/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check",
permission: "sys:settings:edit"
}
},
{
title: "用户套餐",
name: "UserSuites",
path: "/sys/suite/user-suite",
component: "/sys/suite/user-suite/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true
}
}
]
}
]
}
];

View File

@@ -28,6 +28,7 @@ interface ResourceState {
export const useResourceStore = defineStore({
id: "app.resource",
//@ts-ignore
state: (): ResourceState => ({
// user info
topMenus: [],

View File

@@ -5,7 +5,7 @@ import * as _ from "lodash-es";
import { LocalStorage } from "/src/utils/util.storage";
import * as basicApi from "/@/api/modules/api.basic";
import { HeaderMenus, PlusInfo, SiteEnv, SiteInfo, SysInstallInfo, SysPublicSetting } from "/@/api/modules/api.basic";
import { HeaderMenus, PlusInfo, SiteEnv, SiteInfo, SuiteSetting, SysInstallInfo, SysPublicSetting } from "/@/api/modules/api.basic";
import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
@@ -37,6 +37,7 @@ export interface SettingState {
siteEnv?: SiteEnv;
headerMenus?: HeaderMenus;
inited?: boolean;
suiteSetting?: SuiteSetting;
}
const defaultThemeConfig = {
@@ -88,6 +89,7 @@ export const useSettingStore = defineStore({
headerMenus: {
menus: []
},
suiteSetting: { enabled: false },
inited: false
}),
getters: {
@@ -124,6 +126,10 @@ export const useSettingStore = defineStore({
getHeaderMenus() {
// @ts-ignore
return this.headerMenus?.menus || { menus: [] };
},
isSuiteEnabled() {
// @ts-ignore
return this.suiteSetting?.enabled === true;
}
},
actions: {
@@ -142,6 +148,7 @@ export const useSettingStore = defineStore({
_.merge(this.siteEnv, allSettings.siteEnv || {});
_.merge(this.plusInfo, allSettings.plusInfo || {});
_.merge(this.headerMenus, allSettings.headerMenus || {});
_.merge(this.suiteSetting, allSettings.suiteSetting || {});
//@ts-ignore
this.initSiteInfo(allSettings.siteInfo || {});
},
@@ -160,7 +167,7 @@ export const useSettingStore = defineStore({
async checkUrlBound() {
const userStore = useUserStore();
const settingStore = useSettingStore();
if (!userStore.isAdmin || !settingStore.isPlus) {
if (!userStore.isAdmin) {
return;
}

View File

@@ -41,3 +41,12 @@
.fs-search .ant-row{
}
.ant-modal {
max-width: calc(100% - 32px) !important ;
}
.fs-search .ant-row{
flex-flow: row wrap !important;
}

View File

@@ -68,3 +68,11 @@
width: 800px;
margin: 20px;
}
.fs-crud-table{
.ant-table-body {
height: 1000px !important;
table {
}
}
}

View File

@@ -54,15 +54,25 @@ h1, h2, h3, h4, h5, h6 {
justify-content: center;
align-items: center;
}
.flex-vc{
align-items: center;
}
.flex-vb{
align-items: baseline;
}
.flex-o {
display: flex !important;
align-items: center;
}
.flex-baseline{
display: flex !important;
align-items: baseline;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.flex {
@@ -96,6 +106,9 @@ h1, h2, h3, h4, h5, h6 {
overflow-y: auto;
}
.m-0{
margin:0
}
.m-2{
margin:2px
}
@@ -280,3 +293,7 @@ h1, h2, h3, h4, h5, h6 {
.fs-16{
font-size: 16px;
}
.w-50\%{
width: 50%;
}

View File

@@ -5,6 +5,7 @@ import commons from "./util.common";
import * as mitt from "./util.mitt";
import { routerUtils } from "./util.router";
import { treeUtils } from "./util.tree";
import { hashUtils } from "./util.hash";
export const util = {
...envs,
...sites,
@@ -12,5 +13,6 @@ export const util = {
...commons,
...mitt,
router: routerUtils,
tree: treeUtils
tree: treeUtils,
hash: hashUtils
};

View File

@@ -0,0 +1,5 @@
export const hashUtils = {
md5(data: string) {
throw new Error("Not implemented");
}
};

View File

@@ -1,7 +1,7 @@
<template>
<div class="access-selector">
<span v-if="modelValue" class="mr-5 cd-flex-inline">
<a-tag class="mr-5" color="green">{{ target.name || modelValue }}</a-tag>
<a-tag class="mr-5" color="green">{{ target?.name || modelValue }}</a-tag>
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
</span>
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>

View File

@@ -23,9 +23,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};

View File

@@ -53,4 +53,6 @@ onMounted(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
<style lang="less">
</style>

View File

@@ -22,9 +22,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};
@@ -69,7 +66,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
formItem: {
labelCol: {
style: {
width: "120px"
// width: "100px"
}
},
wrapperCol: {
style: {
width: "50%"
}
}
}
@@ -130,10 +132,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: "流水线名称",
type: "text",
search: {
show: true,
component: {
name: "a-input"
}
show: true
},
column: {
width: 300,

View File

@@ -0,0 +1,54 @@
import { request } from "/src/api/service";
export function createApi() {
const apiPrefix = "/monitor/cert";
return {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
},
async AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
},
async UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
},
async GetObj(id: number) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
},
async ListAll() {
return await request({
url: apiPrefix + "/all",
method: "post"
});
}
};
}
export const pipelineGroupApi = createApi();

View File

@@ -0,0 +1,194 @@
// @ts-ignore
import { useI18n } from "vue-i18n";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = pipelineGroupApi;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async (req: EditReq) => {
const { form, row } = req;
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {
const { form } = req;
const res = await api.AddObj(form);
return res;
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px"
}
},
col: {
span: 22
},
wrapper: {
width: 600
}
},
actionbar: { show: false },
rowHandle: {
width: 200,
fixed: "right",
buttons: {
view: { show: false },
copy: { show: false },
edit: { show: false },
remove: { show: false }
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
search: {
show: false
},
column: {
width: 100,
editable: {
disabled: true
}
},
form: {
show: false
}
},
domain: {
title: "主域名",
search: {
show: true
},
type: "text",
form: {
show: false
},
column: {
width: 180,
sorter: true,
component: {
name: "fs-values-format"
}
}
},
domains: {
title: "全部域名",
search: {
show: false
},
type: "text",
form: {
rules: [{ required: true, message: "请输入域名" }]
},
column: {
width: 350,
sorter: true,
component: {
name: "fs-values-format"
}
}
},
domainCount: {
title: "域名数量",
type: "number",
form: {
show: false
},
column: {
width: 120,
sorter: true,
show: false
}
},
"pipeline.title": {
title: "已关联流水线",
search: { show: false },
type: "link",
form: {
show: false
},
column: {
width: 250,
sorter: true,
component: {}
}
},
applyTime: {
title: "申请时间",
search: {
show: false
},
type: "datetime",
form: {
show: false
},
column: {
sorter: true
}
},
expiresTime: {
title: "过期时间",
search: {
show: true
},
type: "date",
form: {
show: false
},
column: {
sorter: true
}
},
fromType: {
title: "来源",
search: {
show: true
},
type: "text",
form: { show: false },
column: {
width: 100,
sorter: true
}
},
certProvider: {
title: "证书颁发机构",
search: {
show: true
},
type: "text",
form: {
show: false
},
column: {
width: 400
}
}
}
}
};
}

View File

@@ -0,0 +1,30 @@
<template>
<fs-page>
<template #header>
<div class="title">
证书仓库
<span class="sub">管理证书后续将支持手动上传证书并部署</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { defineComponent, onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createApi } from "./api";
defineOptions({
name: "CertStore"
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>

View File

@@ -0,0 +1,58 @@
import { request } from "/src/api/service";
const apiPrefix = "/monitor/site";
export const siteInfoApi = {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
},
async AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
},
async UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
},
async GetObj(id: number) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
},
async DoCheck(id: number) {
return await request({
url: apiPrefix + "/check",
method: "post",
data: { id }
});
},
async CheckAll() {
return await request({
url: apiPrefix + "/checkAll",
method: "post"
});
}
};

View File

@@ -0,0 +1,307 @@
// @ts-ignore
import { useI18n } from "vue-i18n";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { siteInfoApi } from "./api";
import dayjs from "dayjs";
import { notification } from "ant-design-vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = siteInfoApi;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async (req: EditReq) => {
const { form, row } = req;
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {
const { form } = req;
const res = await api.AddObj(form);
return res;
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px"
}
},
col: {
span: 22
},
wrapper: {
width: 600
}
},
rowHandle: {
fixed: "right",
width: 240,
buttons: {
check: {
order: 0,
type: "link",
text: null,
tooltip: {
title: "立即检查"
},
icon: "ion:play-sharp",
click: async ({ row }) => {
await api.DoCheck(row.id);
await crudExpose.doRefresh();
notification.success({
message: "检查完成"
});
}
}
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
search: {
show: false
},
column: {
width: 80,
align: "center"
},
form: {
show: false
}
},
name: {
title: "站点名称",
search: {
show: true
},
type: "text",
form: {
rules: [{ required: true, message: "请输入站点名称" }]
},
column: {
width: 160
}
},
domain: {
title: "网站域名",
search: {
show: true
},
type: "text",
form: {
rules: [
{ required: true, message: "请输入域名" },
{ type: "domains", message: "请输入正确的域名" }
]
},
column: {
width: 160,
sorter: true
}
},
httpsPort: {
title: "HTTPS端口",
search: {
show: false
},
type: "number",
form: {
value: 443,
rules: [{ required: true, message: "请输入端口" }]
},
column: {
align: "center",
width: 100
}
},
certDomains: {
title: "证书域名",
search: {
show: false
},
type: "text",
form: {
show: false
},
column: {
width: 200,
sorter: true,
show: true
}
},
certProvider: {
title: "证书颁发者",
search: {
show: false
},
type: "text",
form: {
show: false
},
column: {
width: 200,
sorter: true
}
},
certStatus: {
title: "证书状态",
search: {
show: true
},
type: "dict-select",
dict: dict({
data: [
{ label: "正常", value: "ok", color: "green" },
{ label: "过期", value: "expired", color: "red" }
]
}),
form: {
show: false
},
column: {
width: 100,
sorter: true,
show: false
}
},
certExpiresTime: {
title: "证书到期时间",
search: {
show: false
},
type: "date",
form: {
show: false
},
column: {
sorter: true,
cellRender({ value }) {
if (!value) {
return "-";
}
const expireDate = dayjs(value).format("YYYY-MM-DD");
const leftDays = dayjs(value).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100;
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />;
}
}
},
lastCheckTime: {
title: "上次检查时间",
search: {
show: false
},
type: "datetime",
form: {
show: false
},
column: {
sorter: true
}
},
checkStatus: {
title: "检查状态",
search: {
show: false
},
type: "dict-select",
dict: dict({
data: [
{ label: "正常", value: "ok", color: "green" },
{ label: "异常", value: "error", color: "red" }
]
}),
form: {
show: false
},
column: {
width: 100,
align: "center",
sorter: true
}
},
error: {
title: "错误信息",
search: {
show: false
},
type: "text",
form: {
show: false
},
column: {
width: 200,
sorter: true
}
},
pipelineId: {
title: "关联流水线id",
search: {
show: false
},
form: { show: false },
type: "number",
column: {
width: 200,
sorter: true,
show: false
}
},
certInfoId: {
title: "证书id",
search: {
show: false
},
type: "number",
form: { show: false },
column: {
width: 100,
sorter: true,
show: false
}
},
disabled: {
title: "禁用启用",
search: {
show: false
},
type: "dict-switch",
dict: dict({
data: [
{ label: "启用", value: false, color: "green" },
{ label: "禁用", value: true, color: "red" }
]
}),
form: {
value: false
},
column: {
width: 100,
sorter: true
}
}
}
}
};
}

View File

@@ -0,0 +1,48 @@
<template>
<fs-page>
<template #header>
<div class="title">
站点证书监控
<span class="sub">每天0点检查网站证书的过期时间并发出提醒</span>
</div>
<div class="more">
<a-button type="primary" @click="checkAll">检查全部</a-button>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { siteInfoApi } from "./api";
import { Modal, notification } from "ant-design-vue";
defineOptions({
name: "SiteCertMonitor"
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
function checkAll() {
Modal.confirm({
title: "确认",
content: "确认触发检查全部站点证书吗?",
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: "检查任务已提交",
description: "请稍后刷新页面查看结果"
});
}
});
}
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>

View File

@@ -100,18 +100,20 @@ const renderLabel = (option: any) => {
return <span>{option.name}</span>;
};
async function openTableSelectDialog(e: any) {
e.preventDefault();
await tableSelectRef.value.open();
async function openTableSelectDialog() {
selectOpened.value = false;
await tableSelectRef.value.open({});
await tableSelectRef.value.crudExpose.openAdd({});
}
const selectOpened = ref(false);
const selectSlots = ref({
dropdownRender({ menuNode }: any) {
dropdownRender({ menuNode, props }: any) {
const res = [];
res.push(menuNode);
res.push(<a-divider style="margin: 4px 0" />);
res.push(<a-space style="padding: 4px 8px" />);
res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
// res.push(<a-divider style="margin: 4px 0" />);
// res.push(<a-space style="padding: 4px 8px" />);
// res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
return res;
}
});

Some files were not shown because too many files have changed in this diff Show More