mirror of
https://github.com/certd/certd.git
synced 2026-04-03 22:20:51 +08:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36993cb6f8 | ||
|
|
c854415319 | ||
|
|
aecc1cd979 | ||
|
|
b2f3b0b584 | ||
|
|
c937f5afc7 | ||
|
|
2d580a26af | ||
|
|
4a00a3cc1b | ||
|
|
d3935219f2 | ||
|
|
040788c793 | ||
|
|
005622307e | ||
|
|
8ebf95a222 | ||
|
|
7f596ed315 | ||
|
|
ffa4de6911 | ||
|
|
cb27d4b490 | ||
|
|
bb4910f4e5 | ||
|
|
89c7f07034 | ||
|
|
b150b2f034 | ||
|
|
45d6347f5b | ||
|
|
67d762b6a5 | ||
|
|
faa28f88f9 | ||
|
|
9c8c7a7812 | ||
|
|
a019956698 | ||
|
|
d70e2b66a3 | ||
|
|
5d568efac3 | ||
|
|
08111f1418 | ||
|
|
45839f227a | ||
|
|
8814ffeda6 | ||
|
|
d224c4c124 | ||
|
|
549525fb37 | ||
|
|
1c8e25beb3 | ||
|
|
eda45c1528 | ||
|
|
53c38cf714 | ||
|
|
0e7578043e | ||
|
|
21f50e0b38 | ||
|
|
515f00c7cd | ||
|
|
8057586dc1 | ||
|
|
b101ac7c7f | ||
|
|
64319937a1 | ||
|
|
1c0cfd6769 | ||
|
|
f8e17d5285 | ||
|
|
d4385ad8a5 | ||
|
|
da07ce419f | ||
|
|
714e0206c4 | ||
|
|
40da82666a | ||
|
|
79f7ec4672 | ||
|
|
0f5c69040b | ||
|
|
c9d1c45d97 | ||
|
|
ea8fdb120c | ||
|
|
f6fa830ffe | ||
|
|
992e50c014 | ||
|
|
bd705d91ba | ||
|
|
2656394195 |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -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
|
||||
|
||||
69
README.md
69
README.md
@@ -12,11 +12,13 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
||||
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持40+部署插件)
|
||||
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
||||
* 邮件通知、webhook通知
|
||||
* 私有化部署,数据保存本地,镜像由Github Actions构建,过程公开透明
|
||||
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明
|
||||
* 支持SQLite,PostgreSQL、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等 |
|
||||
|
||||
************************
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
22:52
|
||||
00:19
|
||||
|
||||
@@ -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 # 数据库类型
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
docs/guide/install/1panel/images/upgrade-1.png
Normal file
BIN
docs/guide/install/1panel/images/upgrade-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
docs/guide/install/1panel/images/upgrade-2.png
Normal file
BIN
docs/guide/install/1panel/images/upgrade-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -31,15 +31,12 @@ admin/123456
|
||||
|
||||
## 三、升级
|
||||
|
||||
1. 找到容器,点击编辑
|
||||

|
||||
1. 找到容器,点击更多->升级
|
||||

|
||||
|
||||
2. 将latest修改为最新版本号
|
||||

|
||||
2. 选择强制拉取镜像,点击确认即可
|
||||

|
||||
|
||||

|
||||
|
||||
3. 点击确定,重启容器
|
||||
|
||||
## 四、数据备份
|
||||
|
||||
|
||||
@@ -38,28 +38,12 @@ admin/123456
|
||||
登录后请及时修改密码
|
||||
|
||||
## 三、如何升级
|
||||
宝塔升级certd非常简单
|
||||
|
||||
### 1. 应用商店安装,直接更新镜像即可
|
||||
`docker`->`容器编排`->`左侧选择Certd-xxxx`->`更新镜像`
|
||||
`docker`->`容器编排`->`左侧选择Certd`->`更新镜像`
|
||||

|
||||
|
||||
|
||||
### 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 应用商店部署方式
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
docs/guide/use/cert/index.md
Normal file
10
docs/guide/use/cert/index.md
Normal 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 |
@@ -1,8 +1,12 @@
|
||||
# 群晖部署和证书更新
|
||||
|
||||
支持群晖`6.x`、`7.x`
|
||||
|
||||
## 一、群晖部署Certd
|
||||
|
||||
以下是群晖`7.x`的部署`certd`步骤。
|
||||
群晖`6.x`请参考[docker部署](./../../install/docker/)
|
||||
|
||||
### 1. 打开Container Manager
|
||||
|
||||

|
||||
@@ -32,6 +36,8 @@
|
||||
|
||||
## 二、更新群晖证书
|
||||
|
||||
证书部署插件支持群晖`6.x`、`7.x`
|
||||
|
||||
## 1. 前提条件
|
||||
* 已经部署了certd
|
||||
* 群晖上已经设置好了证书(证书建议设置好描述,插件需要根据描述查找证书)
|
||||
|
||||
@@ -30,7 +30,7 @@ features:
|
||||
- title: 多证书格式支持
|
||||
details: 支持pem、pfx、der、jks等多种证书格式,支持Google、Letsencrypt、ZeroSSL证书颁发机构
|
||||
- title: 支持私有化部署
|
||||
details: 保障数据安全
|
||||
details: 授权数据加密存储,保障数据安全
|
||||
- title: 多数据库支持
|
||||
details: 支持SQLite、Postgresql、MySQL数据库
|
||||
---
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.28.3"
|
||||
"version": "1.29.0"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
12:05
|
||||
01:02
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
47
packages/core/basic/src/utils/util.lock.ts
Normal file
47
packages/core/basic/src/utils/util.lock.ts
Normal 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();
|
||||
2
packages/core/basic/src/utils/util.mitter.ts
Normal file
2
packages/core/basic/src/utils/util.mitter.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import mitt from 'mitt';
|
||||
export const mitter = mitt();
|
||||
@@ -37,6 +37,8 @@ function buildGroupOptions(options: any[], inDomains: string[]) {
|
||||
}
|
||||
|
||||
export const optionsUtils = {
|
||||
//获取分组
|
||||
groupByDomain,
|
||||
//构建分组后的选项列表,常用
|
||||
buildGroupOptions,
|
||||
};
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export type NotificationSendReq = {
|
||||
useDefault?: boolean;
|
||||
useEmail?: boolean;
|
||||
emailAddress?: string;
|
||||
logger?: ILogger;
|
||||
logger: ILogger;
|
||||
body: NotificationBody;
|
||||
};
|
||||
export interface INotificationService {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -36,6 +36,10 @@ export const Constants = {
|
||||
code: 88,
|
||||
message: '需要VIP',
|
||||
},
|
||||
needsuite: {
|
||||
code: 89,
|
||||
message: '需要购买或升级套餐',
|
||||
},
|
||||
loginError: {
|
||||
code: 2,
|
||||
message: '登录失败',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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}`)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
5
packages/libs/lib-server/src/user/access/index.ts
Normal file
5
packages/libs/lib-server/src/user/access/index.ts
Normal 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';
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
* 授权
|
||||
1
packages/libs/lib-server/src/user/index.ts
Normal file
1
packages/libs/lib-server/src/user/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './access/index.js';
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.pfx:pfx格式证书文件,iis服务器使用
|
||||
cert.der:der格式证书文件
|
||||
cert.jks:jks格式证书文件,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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
124
packages/ui/certd-client/src/layout/components/menu/index.vue
Normal file
124
packages/ui/certd-client/src/layout/components/menu/index.vue
Normal 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>
|
||||
@@ -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>;
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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";
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -28,6 +28,7 @@ interface ResourceState {
|
||||
|
||||
export const useResourceStore = defineStore({
|
||||
id: "app.resource",
|
||||
//@ts-ignore
|
||||
state: (): ResourceState => ({
|
||||
// user info
|
||||
topMenus: [],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -68,3 +68,11 @@
|
||||
width: 800px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.fs-crud-table{
|
||||
.ant-table-body {
|
||||
height: 1000px !important;
|
||||
table {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
5
packages/ui/certd-client/src/utils/util.hash.ts
Normal file
5
packages/ui/certd-client/src/utils/util.hash.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const hashUtils = {
|
||||
md5(data: string) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -53,4 +53,6 @@ onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
<style lang="less">
|
||||
|
||||
</style>
|
||||
|
||||
@@ -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,
|
||||
|
||||
54
packages/ui/certd-client/src/views/certd/monitor/cert/api.ts
Normal file
54
packages/ui/certd-client/src/views/certd/monitor/cert/api.ts
Normal 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();
|
||||
194
packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx
Normal file
194
packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
58
packages/ui/certd-client/src/views/certd/monitor/site/api.ts
Normal file
58
packages/ui/certd-client/src/views/certd/monitor/site/api.ts
Normal 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"
|
||||
});
|
||||
}
|
||||
};
|
||||
307
packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx
Normal file
307
packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
Reference in New Issue
Block a user