Compare commits

...

7 Commits

Author SHA1 Message Date
xiaojunnuo
1c634a702a v1.39.9 2026-04-06 01:24:46 +08:00
xiaojunnuo
909a9e4050 build: prepare to build 2026-04-06 01:21:18 +08:00
xiaojunnuo
b5cc794061 perf(monitor): 支持查看监控执行记录
- 新增监控任务执行记录页面及相关API
- 添加数据库表结构及多数据库支持
- 完善国际化翻译
- 实现批量删除功能
- 优化站点监控服务逻辑
2026-04-06 01:17:02 +08:00
xiaojunnuo
73b8e85976 fix: 修复cn域名获取不到到期时间的问题 2026-04-06 00:33:08 +08:00
xiaojunnuo
282b5d6893 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-05 23:49:30 +08:00
xiaojunnuo
c6628e7311 perf: 支持域名到期时间监控通知 2026-04-05 23:49:25 +08:00
xiaojunnuo
6b109d172f perf: 腾讯云CLB大区增加台北 2026-04-03 11:02:39 +08:00
64 changed files with 1521 additions and 245 deletions

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
### Performance Improvements
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes

View File

@@ -28,51 +28,53 @@
| 24.| **中国移动CND授权** | |
| 25.| **授权插件示例** | 这是一个示例授权插件,用于演示如何实现一个授权插件 |
| 26.| **dns.la授权** | |
| 27.| **多吉云** | |
| 28.| **Dokploy授权** | |
| 29.| **farcdn授权** | |
| 30.| **FlexCDN授权** | |
| 31.| **Gcore** | Gcore |
| 32.| **Github授权** | |
| 33.| **godaddy授权** | |
| 34.| **金山云授权** | |
| 35.| **FTP授权** | |
| 36.| **七牛OSS授权** | |
| 37.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 38.| **s3/minio授权** | S3/minio oss授权 |
| 39.| **namesilo授权** | |
| 40.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
| 41.| **1panel授权** | 账号和密码 |
| 42.| **支付宝** | |
| 43.| **白山云授权** | |
| 44.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
| 45.| **cdnfly授权** | |
| 46.| **k8s授权** | |
| 47.| **括彩云cdn授权** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 48.| **LeCDN授权** | |
| 49.| **lucky** | |
| 50.| **猫云授权** | |
| 51.| **plesk授权** | |
| 52.| **长亭雷池授权** | |
| 53.| **群晖登录授权** | |
| 54.| **uniCloud** | unicloud授权 |
| 55.| **微信支付** | |
| 56.| **易盾rcdn授权** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 57.| **易发云短信** | sms.yfyidc.cn/ |
| 58.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 59.| **易支付** | |
| 60.| **proxmox** | |
| 61.| **UCloud授权** | 优刻得授权 |
| 62.| **又拍云** | |
| 63.| **网宿授权** | |
| 64.| **西部数码授权** | |
| 65.| **我爱云授权** | 我爱云CDN |
| 66.| **新网授权(代理方式)** | |
| 67.| **新网授权** | |
| 68.| **新网互联授权** | 仅支持代理账号ip需要加入白名单 |
| 69.| **Zenlayer授权** | Zenlayer授权 |
| 70.| **GoEdge授权** | |
| 71.| **雨云授权** | https://app.rainyun.com/ |
| 27.| **彩虹DNS** | 彩虹DNS管理系统授权 |
| 28.| **多吉云** | |
| 29.| **Dokploy授权** | |
| 30.| **farcdn授权** | |
| 31.| **FlexCDN授权** | |
| 32.| **Gcore** | Gcore |
| 33.| **Github授权** | |
| 34.| **godaddy授权** | |
| 35.| **金山云授权** | |
| 36.| **FTP授权** | |
| 37.| **七牛OSS授权** | |
| 38.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 39.| **s3/minio授权** | S3/minio oss授权 |
| 40.| **namesilo授权** | |
| 41.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
| 42.| **1panel授权** | 账号和密码 |
| 43.| **支付宝** | |
| 44.| **白山云授权** | |
| 45.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
| 46.| **cdnfly授权** | |
| 47.| **k8s授权** | |
| 48.| **括彩云cdn授权** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 49.| **LeCDN授权** | |
| 50.| **lucky** | |
| 51.| **猫云授权** | |
| 52.| **plesk授权** | |
| 53.| **长亭雷池授权** | |
| 54.| **群晖登录授权** | |
| 55.| **uniCloud** | unicloud授权 |
| 56.| **微信支付** | |
| 57.| **易盾rcdn授权** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 58.| **易发云短信** | sms.yfyidc.cn/ |
| 59.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 60.| **易支付** | |
| 61.| **proxmox** | |
| 62.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
| 63.| **UCloud授权** | 优刻得授权 |
| 64.| **又拍云** | |
| 65.| **网宿授权** | |
| 66.| **西部数码授权** | |
| 67.| **我爱云授权** | 我爱云CDN |
| 68.| **新网授权(代理方式)** | |
| 69.| **新网授权** | |
| 70.| **新网互联授权** | 仅支持代理账号ip需要加入白名单 |
| 71.| **Zenlayer授权** | Zenlayer授权 |
| 72.| **GoEdge授权** | |
| 73.| **雨云授权** | https://app.rainyun.com/ |
<style module>
table th:first-of-type {

View File

@@ -20,8 +20,10 @@
| 16.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
| 17.| **西部数码** | west dns provider |
| 18.| **Dns提供商Demo** | dns provider示例 |
| 19.| **51dns** | 51DNS |
| 20.| **新网互联** | 新网互联 |
| 19.| **彩虹DNS** | 彩虹DNS管理系统 |
| 20.| **Spaceship** | Spaceship 域名解析 |
| 21.| **51dns** | 51DNS |
| 22.| **新网互联** | 新网互联 |
<style module>
table th:first-of-type {

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.39.8"
"version": "1.39.9"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/publishlab/node-acme-client/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/acme-client
## [1.39.8](https://github.com/publishlab/node-acme-client/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.39.8",
"@certd/basic": "^1.39.9",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Performance Improvements
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
23:54
01:21

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Performance Improvements
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,8 +18,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@certd/basic": "^1.39.9",
"@certd/plus-core": "^1.39.9",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"

View File

@@ -7,6 +7,7 @@ import { IEmailService } from "../service/index.js";
export type NotificationBody = {
userId?: number;
projectId?: number;
title: string;
content: string;
pipeline?: Pipeline;
@@ -20,6 +21,7 @@ export type NotificationBody = {
pipelineResult?: string;
pipelineTitle?: string;
errors?: string;
[key: string]: any; // 其他templateData
};
export type NotificationRequestHandleReqInput<T = any> = {

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-huawei
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-iframe
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/jdcloud
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.39.8",
"version": "1.39.9",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-k8s
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,7 +18,7 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.8",
"@certd/basic": "^1.39.9",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-server
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.39.8",
"version": "1.39.9",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -28,11 +28,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@certd/acme-client": "^1.39.9",
"@certd/basic": "^1.39.9",
"@certd/pipeline": "^1.39.9",
"@certd/plugin-lib": "^1.39.9",
"@certd/plus-core": "^1.39.9",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.39.8",
"version": "1.39.9",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/plugin-cert
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"@certd/acme-client": "^1.39.9",
"@certd/basic": "^1.39.9",
"@certd/pipeline": "^1.39.9",
"@certd/plugin-lib": "^1.39.9",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/plugin-lib
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.39.8",
"version": "1.39.9",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -22,10 +22,10 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@certd/acme-client": "^1.39.9",
"@certd/basic": "^1.39.9",
"@certd/pipeline": "^1.39.9",
"@certd/plus-core": "^1.39.9",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
### Performance Improvements
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.39.8",
"version": "1.39.9",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/lib-iframe": "^1.39.9",
"@certd/pipeline": "^1.39.9",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -221,6 +221,7 @@ export default {
projectJoin: "Join Project",
currentProject: "Current Project",
projectMemberManager: "Project Member",
domainMonitorSetting: "Domain Monitor Settings",
},
certificateRepo: {
title: "Certificate Repository",

View File

@@ -64,10 +64,41 @@ export default {
dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1 , support multiple",
certValidDays: "Certificate Valid Days",
certValidDaysHelper: "Number of days before expiration to send a notification",
domain: {
monitorSettings: "Domain Monitor Settings",
enabled: "Enable Domain Monitor",
enabledHelper: "Enable to monitor all domain registration expiration time",
notificationChannel: "Notification Channel",
setNotificationChannel: "Set the notification channel",
willExpireDays: "Will Expire Days",
willExpireDaysHelper: "Number of days before expiration to send a notification",
monitorCronSetting: "Monitoring Schedule",
cronTrigger: "Scheduled trigger for monitoring",
},
},
cert: {
expired: "Expired",
expiring: "Expiring",
noExpired: "Not Expired",
},
history: {
title: "Monitoring Execution Records",
description: "Monitoring execution records",
resultTitle: "Status",
contentTitle: "Content",
titleTitle: "Title",
jobTypeTitle: "Job Type",
startAtTitle: "Start Time",
endAtTitle: "End Time",
jobResultTitle: "Result",
jobResult: {
done: "Done",
start: "Start",
},
jobType: {
domainExpirationCheck: "Domain Expiration Check",
siteCertMonitor: "Site Certificate Monitor",
},
},
};

View File

@@ -226,6 +226,8 @@ export default {
projectJoin: "加入项目",
currentProject: "当前项目",
projectMemberManager: "项目成员管理",
domainMonitorSetting: "域名监控设置",
jobHistory: "监控执行记录",
},
certificateRepo: {
title: "证书仓库",

View File

@@ -68,10 +68,40 @@ export default {
dnsServerHelper: "使用自定义的域名解析服务器1.1.1.1 , 支持多个",
certValidDays: "证书到期前天数",
certValidDaysHelper: "证书到期前多少天发送通知",
domain: {
monitorSettings: "域名监控设置",
enabled: "启用域名监控",
enabledHelper: "启用后,监控“域名管理”中域名的过期时间,到期前通知提醒",
notificationChannel: "通知渠道",
setNotificationChannel: "设置通知渠道",
willExpireDays: "到期前天数",
willExpireDaysHelper: "域名有效期到期前多少天发送通知",
monitorCronSetting: "监控定时设置",
cronTrigger: "定时触发监控",
},
},
cert: {
expired: "已过期",
expiring: "即将过期",
noExpired: "未过期",
},
history: {
title: "监控执行记录",
description: "站点证书、域名等监控任务的执行记录",
resultTitle: "状态",
contentTitle: "内容",
titleTitle: "标题",
jobTypeTitle: "任务类型",
startAtTitle: "开始时间",
endAtTitle: "结束时间",
jobResultTitle: "任务结果",
jobResult: {
done: "完成",
start: "开始",
},
jobType: {
domainExpirationCheck: "域名到期检查",
siteCertMonitor: "站点证书监控",
},
},
};

View File

@@ -241,6 +241,28 @@ export const certdResources = [
isMenu: true,
},
},
{
title: "certd.sysResources.domainMonitorSetting",
name: "DomainMonitorSetting",
path: "/certd/cert/domain/setting",
component: "/certd/cert/domain/setting/index.vue",
meta: {
icon: "ion:stopwatch-outline",
auth: true,
isMenu: true,
},
},
{
title: "certd.sysResources.jobHistory",
name: "JobHistory",
path: "/certd/monitor/history",
component: "/certd/monitor/history/index.vue",
meta: {
icon: "ion:barcode-outline",
auth: true,
isMenu: true,
},
},
{
title: "certd.userSecurity",
name: "UserSecurity",

View File

@@ -128,6 +128,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, 2000);
},
},
monitorSettingSave: {
show: hasActionPermission("write"),
title: "域名过期监控设置",
type: "primary",
icon: "ion:save-outline",
text: "域名过期监控设置",
click: async () => {
router.push({
path: "/certd/cert/domain/setting",
});
},
},
},
},
columns: {

View File

@@ -0,0 +1,27 @@
// @ts-ignore
import { request } from "/src/api/service";
const apiPrefix = "/cert/domain/setting";
export type UserDomainMonitorSetting = {
enabled?: boolean;
notificationId?: number;
cron?: string;
willExpireDays?: number;
};
export async function DomainMonitorSettingsGet() {
const res = await request({
url: apiPrefix + "/get",
method: "post",
});
if (!res) {
return {};
}
return res as UserDomainMonitorSetting;
}
export async function DomainMonitorSettingsSave(data: UserDomainMonitorSetting) {
await request({
url: apiPrefix + "/save",
method: "post",
data: data,
});
}

View File

@@ -0,0 +1,100 @@
<template>
<fs-page class="page-user-settings page-domain-monitor-setting">
<template #header>
<div class="title">{{ t("monitor.setting.domain.monitorSettings") }}</div>
</template>
<div class="user-settings-form settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
<a-form-item :label="t('monitor.setting.domain.enabled')" :name="['enabled']">
<div class="flex flex-baseline">
<a-switch v-model:checked="formState.enabled" :disabled="!settingsStore.isPlus" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t("monitor.setting.domain.enabledHelper") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.notificationChannel')" :name="['notificationId']">
<div class="flex">
<NotificationSelector v-model="formState.notificationId" />
</div>
<div class="helper">{{ t("monitor.setting.domain.setNotificationChannel") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.willExpireDays')" :name="['willExpireDays']">
<div class="flex">
<a-input-number v-model:value="formState.willExpireDays" />
</div>
<div class="helper">{{ t("monitor.setting.domain.willExpireDaysHelper") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.monitorCronSetting')" :name="['cron']">
<div class="flex flex-baseline">
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t("monitor.setting.domain.cronTrigger") }}</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
<loading-button type="primary" html-type="button" :click="doSave" :disabled="!hasActionPermission('write')">{{ t("certd.save") }}</loading-button>
</a-form-item>
</a-form>
</div>
</fs-page>
</template>
<script setup lang="tsx">
import { notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { reactive } from "vue";
import * as api from "./api";
import { UserDomainMonitorSetting } from "./api";
import { useUserStore } from "/@/store/user";
import { utils } from "/@/utils";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useI18n } from "/src/locales";
import { useSettingStore } from "/src/store/settings";
import { useCrudPermission } from "/@/plugin/permission";
const { t } = useI18n();
const settingsStore = useSettingStore();
const userStore = useUserStore();
defineOptions({
name: "DomainMonitorSetting",
});
const randomHour = Math.floor(Math.random() * 9);
const randomMin = Math.floor(Math.random() * 59);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
const formState = reactive<Partial<UserDomainMonitorSetting>>({
enabled: false,
notificationId: 0,
cron: randomCron,
willExpireDays: 30,
});
async function loadUserSettings() {
const data: any = await api.DomainMonitorSettingsGet();
merge(formState, data);
}
const { hasActionPermission } = useCrudPermission({ permission: { isProjectPermission: true } });
loadUserSettings();
const doSave = async (form: any) => {
await utils.sleep(300);
await api.DomainMonitorSettingsSave({
...formState,
});
notification.success({
message: t("certd.saveSuccess"),
});
};
</script>
<style lang="less">
.page-domain-monitor-setting {
.settings-form {
width: 700px;
margin: 20px;
}
}
</style>

View File

@@ -0,0 +1,37 @@
import { request } from "/src/api/service";
const apiPrefix = "/monitor/job-history";
export const jobHistoryApi = {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
},
async BatchDelObj(ids: number[]) {
return await request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids },
});
},
async GetObj(id: number) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
},
};

View File

@@ -0,0 +1,257 @@
// @ts-ignore
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { message, Modal } from "ant-design-vue";
import { ref } from "vue";
import { createGroupDictRef } from "../../basic/group/api";
import { useDicts } from "../../dicts";
import { jobHistoryApi } from "./api";
import { useCrudPermission } from "/@/plugin/permission";
import { useProjectStore } from "/@/store/project";
import { useI18n } from "/src/locales";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = jobHistoryApi;
const { crudBinding } = crudExpose;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async (req: EditReq) => {};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {};
const { myProjectDict } = useDicts();
const historyResultDict = dict({
data: [
{ label: t("monitor.history.jobResult.done"), value: "done", color: "green" },
{ label: t("monitor.history.jobResult.start"), value: "start", color: "blue" },
],
});
const jobTypeDict = dict({
data: [
{ label: t("monitor.history.jobType.domainExpirationCheck"), value: "domainExpirationCheck", color: "green" },
{ label: t("monitor.history.jobType.siteCertMonitor"), value: "siteCertMonitor", color: "blue" },
],
});
const selectedRowKeys = ref([]);
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await api.BatchDelObj(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error("请先勾选记录");
}
};
context.handleBatchDelete = handleBatchDelete;
const GroupTypeSite = "site";
const groupDictRef = createGroupDictRef(GroupTypeSite);
function getDefaultGroupId() {
const searchFrom = crudExpose.getSearchValidatedFormData();
if (searchFrom.groupId) {
return searchFrom.groupId;
}
}
const projectStore = useProjectStore();
const { hasActionPermission } = useCrudPermission({ permission: context.permission });
return {
id: "jobHistoryCrud",
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
// tabs: {
// name: "groupId",
// show: true,
// },
toolbar: {
buttons: {
export: {
show: true,
},
},
},
pagination: {
pageSizeOptions: ["10", "20", "50", "100", "200"],
},
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
props: {
multiple: true,
crossPage: false,
selectedRowKeys: () => {
return selectedRowKeys;
},
},
},
},
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px",
},
},
col: {
span: 22,
},
wrapper: {
width: 600,
},
},
actionbar: {
buttons: {
add: {
show: false,
},
},
},
rowHandle: {
fixed: "right",
width: 280,
buttons: {
edit: {
show: false,
},
},
},
// tabs: {
// name: "disabled",
// show: true,
// },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
search: {
show: false,
},
column: {
width: 80,
align: "center",
},
form: {
show: false,
},
},
type: {
title: t("monitor.history.jobTypeTitle"),
search: {
show: true,
},
type: "dict-select",
dict: jobTypeDict,
form: {
show: false,
},
column: {
width: 120,
},
},
title: {
title: t("monitor.history.titleTitle"),
search: {
show: true,
},
type: "text",
column: {
width: 200,
},
},
content: {
title: t("monitor.history.contentTitle"),
search: {
show: true,
},
type: "text",
column: {
width: 460,
ellipsis: true,
},
},
result: {
title: t("monitor.history.resultTitle"),
search: {
show: false,
},
type: "dict-select",
dict: historyResultDict,
form: {
show: false,
},
column: {
width: 100,
align: "center",
sorter: true,
cellRender({ value, row }) {
return (
<a-tooltip title={row.error}>
<fs-values-format v-model={value} dict={historyResultDict}></fs-values-format>
</a-tooltip>
);
},
},
},
startAt: {
title: t("monitor.history.startAtTitle"),
search: {
show: true,
},
type: "datetime",
column: {
width: 160,
},
},
endAt: {
title: t("monitor.history.endAtTitle"),
search: {
show: true,
},
type: "datetime",
column: {
width: 160,
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
}

View File

@@ -0,0 +1,48 @@
<template>
<fs-page>
<template #header>
<div class="title flex items-center">
{{ t("monitor.history.title") }}
<div class="sub flex-1">
<div>
{{ t("monitor.history.description") }}
</div>
</div>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
name: "JobHistory",
});
const context: any = {
permission: {
isProjectPermission: true,
},
};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>

View File

@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
### Performance Improvements
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes

View File

@@ -0,0 +1,24 @@
CREATE TABLE `cd_job_history`
(
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
`user_id` bigint NOT NULL,
`project_id` bigint ,
`type` varchar(100) NOT NULL,
`title` varchar(512) NOT NULL,
`related_id` varchar(100),
`result` varchar(100) NOT NULL,
`content` longtext ,
`start_at` bigint NOT NULL,
`end_at` bigint ,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX `index_job_history_user_id` ON `cd_job_history` (`user_id`);
CREATE INDEX `index_job_history_project_id` ON `cd_job_history` (`project_id`);
CREATE INDEX `index_job_history_type` ON `cd_job_history` (`type`);
ALTER TABLE `cd_job_history` ENGINE = InnoDB;

View File

@@ -0,0 +1,22 @@
CREATE TABLE "cd_job_history"
(
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
"user_id" bigint NOT NULL,
"project_id" bigint ,
"type" varchar(100) NOT NULL,
"title" varchar(512) NOT NULL,
"related_id" varchar(100),
"result" varchar(100) NOT NULL,
"content" text ,
"start_at" bigint NOT NULL,
"end_at" bigint ,
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id");
CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id");
CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type");

View File

@@ -0,0 +1,22 @@
CREATE TABLE "cd_job_history"
(
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"user_id" integer NOT NULL,
"project_id" integer ,
"type" varchar(100) NOT NULL,
"title" varchar(512) NOT NULL,
"related_id" varchar(100),
"result" varchar(100) NOT NULL,
"content" text ,
"start_at" integer NOT NULL,
"end_at" integer ,
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id");
CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id");
CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type");

View File

@@ -0,0 +1,32 @@
name: dnsmgr
title: 彩虹DNS
icon: clarity:plugin-line
desc: 彩虹DNS管理系统授权
input:
endpoint:
title: 系统地址
component:
name: a-input
allowClear: true
placeholder: https://dnsmgr.example.com
required: true
uid:
title: 用户ID
component:
name: a-input
allowClear: true
placeholder: '123456'
required: true
key:
title: API密钥
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-dnsmgr/access.js

View File

@@ -0,0 +1,29 @@
name: spaceship
title: Spaceship.com 授权
icon: clarity:plugin-line
desc: Spaceship.com API 授权插件
input:
apiKey:
title: API Key
component:
placeholder: 请输入 API Key
required: true
encrypt: true
helper: 前往 [获取 API Key](https://www.spaceship.com/application/api-manager/)
apiSecret:
title: API Secret
component:
name: a-input-password
vModel: value
placeholder: 请输入 API Secret
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试 API 连接是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-spaceship/access.js

View File

@@ -8,6 +8,24 @@ icon: svg:icon-tencentcloud
group: tencent
desc: 暂时只支持单向认证证书,暂时只支持通用负载均衡
input:
cert:
title: 域名证书
helper: 请选择前置任务输出的域名证书
component:
name: output-selector
from:
- ':cert:'
- UploadCertToTencent
required: true
order: 0
accessId:
title: Access提供者
helper: access授权
component:
name: access-selector
type: tencent
required: true
order: 0
region:
title: 大区
component:
@@ -33,14 +51,12 @@ input:
- value: na-siliconvalley
- value: na-toronto
- value: sa-saopaulo
- value: ap-taipei
helper: 如果列表中没有,您可以手动输入
required: true
order: 0
certName:
title: 证书名称前缀
order: 0
loadBalancerId:
title: 负载均衡ID
helper: 如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个
required: true
order: 0
listenerId:
@@ -57,22 +73,8 @@ input:
mode: tags
helper: 如果开启了sni则此项必须填写未开启则不要填写
order: 0
cert:
title: 域名证书
helper: 请选择前置任务输出的域名证书
component:
name: output-selector
from:
- ':cert:'
required: true
order: 0
accessId:
title: Access提供者
helper: access授权
component:
name: access-selector
type: tencent
required: true
certName:
title: 证书名称前缀
order: 0
output: {}
pluginType: deploy

View File

@@ -0,0 +1,9 @@
name: dnsmgr
title: 彩虹DNS
desc: 彩虹DNS管理系统
icon: clarity:plugin-line
accessType: dnsmgr
order: 99
pluginType: dnsProvider
type: builtIn
scriptFilePath: /plugins/plugin-dnsmgr/dns-provider.js

View File

@@ -0,0 +1,9 @@
name: spaceship
title: Spaceship
desc: Spaceship 域名解析
icon: clarity:plugin-line
accessType: spaceship
order: 99
pluginType: dnsProvider
type: builtIn
scriptFilePath: /plugins/plugin-spaceship/dns-provider.js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.39.8",
"version": "1.39.9",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -50,20 +50,20 @@
"@aws-sdk/client-route-53": "^3.964.0",
"@aws-sdk/client-s3": "^3.964.0",
"@aws-sdk/client-sts": "^3.990.0",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/commercial-core": "^1.39.8",
"@certd/acme-client": "^1.39.9",
"@certd/basic": "^1.39.9",
"@certd/commercial-core": "^1.39.9",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.39.8",
"@certd/lib-huawei": "^1.39.8",
"@certd/lib-k8s": "^1.39.8",
"@certd/lib-server": "^1.39.8",
"@certd/midway-flyway-js": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-cert": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"@certd/plugin-plus": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@certd/jdcloud": "^1.39.9",
"@certd/lib-huawei": "^1.39.9",
"@certd/lib-k8s": "^1.39.9",
"@certd/lib-server": "^1.39.9",
"@certd/midway-flyway-js": "^1.39.9",
"@certd/pipeline": "^1.39.9",
"@certd/plugin-cert": "^1.39.9",
"@certd/plugin-lib": "^1.39.9",
"@certd/plugin-plus": "^1.39.9",
"@certd/plus-core": "^1.39.9",
"@google-cloud/publicca": "^1.3.0",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.185",
@@ -141,6 +141,7 @@
"typeorm": "^0.3.20",
"uuid": "^10.0.0",
"wechatpay-node-v3": "^2.2.1",
"whoiser": "2.0.0-beta.10",
"xml2js": "^0.6.2"
},
"devDependencies": {

View File

@@ -20,7 +20,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/page', { description: Constants.per.authOnly, summary: "查询域名分页列表" })
async page(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
body.query = body.query ?? {};
body.query.projectId = projectId;
body.query.userId = userId;
@@ -44,7 +44,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/list', { description: Constants.per.authOnly, summary: "查询域名列表" })
async list(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
body.query = body.query ?? {};
body.query.projectId = projectId;
body.query.userId = userId;
@@ -54,7 +54,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/add', { description: Constants.per.authOnly, summary: "添加域名" })
async add(@Body(ALL) bean: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
bean.projectId = projectId;
bean.userId = userId;
return super.add(bean);
@@ -82,7 +82,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/deleteByIds', { description: Constants.per.authOnly, summary: "批量删除域名" })
async deleteByIds(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
await this.service.delete(body.ids, {
userId: userId,
projectId: projectId,
@@ -94,10 +94,10 @@ export class DomainController extends CrudController<DomainService> {
@Post('/import/start', { description: Constants.per.authOnly, summary: "开始域名导入任务" })
async importStart(@Body(ALL) body: any) {
checkPlus();
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
const { key } = body;
const req = {
key,
key,
userId: userId,
projectId: projectId,
}
@@ -107,7 +107,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/import/status', { description: Constants.per.authOnly, summary: "查询域名导入任务状态" })
async importStatus() {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
const req = {
userId: userId,
projectId: projectId,
@@ -119,7 +119,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/import/delete', { description: Constants.per.authOnly, summary: "删除域名导入任务" })
async importDelete(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
const { key } = body;
const req = {
userId: userId,
@@ -133,12 +133,12 @@ export class DomainController extends CrudController<DomainService> {
@Post('/import/save', { description: Constants.per.authOnly, summary: "保存域名导入任务" })
async importSave(@Body(ALL) body: any) {
checkPlus();
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
const { dnsProviderType, dnsProviderAccessId, key } = body;
const req = {
userId: userId,
projectId: projectId,
dnsProviderType, dnsProviderAccessId, key
dnsProviderType, dnsProviderAccessId, key
}
const item = await this.service.saveDomainImportTask(req);
return this.ok(item);
@@ -147,7 +147,7 @@ export class DomainController extends CrudController<DomainService> {
@Post('/sync/expiration/start', { description: Constants.per.authOnly, summary: "开始同步域名过期时间任务" })
async syncExpirationStart(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
await this.service.startSyncExpirationTask({
userId: userId,
projectId: projectId,
@@ -156,7 +156,7 @@ export class DomainController extends CrudController<DomainService> {
}
@Post('/sync/expiration/status', { description: Constants.per.authOnly, summary: "查询同步域名过期时间任务状态" })
async syncExpirationStatus(@Body(ALL) body: any) {
const {projectId,userId} = await this.getProjectUserIdRead();
const { projectId, userId } = await this.getProjectUserIdRead();
const status = await this.service.getSyncExpirationTaskStatus({
userId: userId,
projectId: projectId,
@@ -165,4 +165,26 @@ export class DomainController extends CrudController<DomainService> {
}
@Post('/setting/save', { description: Constants.per.authOnly, summary: "保存域名监控设置" })
async settingSave(@Body(ALL) body: any) {
const { projectId, userId } = await this.getProjectUserIdWrite();
await this.service.monitorSettingSave({
userId: userId,
projectId: projectId,
setting: {...body},
})
return this.ok();
}
@Post('/setting/get', { description: Constants.per.authOnly, summary: "查询域名监控设置" })
async settingGet() {
const { projectId, userId } = await this.getProjectUserIdRead();
const setting = await this.service.monitorSettingGet({
userId: userId,
projectId: projectId,
})
return this.ok(setting);
}
}

View File

@@ -0,0 +1,65 @@
import { Constants, CrudController } from "@certd/lib-server";
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
import { ApiTags } from "@midwayjs/swagger";
import { SiteInfoService } from "../../../modules/monitor/index.js";
import { JobHistoryService } from "../../../modules/monitor/service/job-history-service.js";
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
/**
*/
@Provide()
@Controller('/api/monitor/job-history')
@ApiTags(['monitor'])
export class JobHistoryController extends CrudController<JobHistoryService> {
@Inject()
service: JobHistoryService;
@Inject()
authService: AuthService;
@Inject()
siteInfoService: SiteInfoService;
getService(): JobHistoryService {
return this.service;
}
@Post('/page', { description: Constants.per.authOnly, summary: "查询监控运行历史分页列表" })
async page(@Body(ALL) body: any) {
const { projectId, userId } = await this.getProjectUserIdRead()
body.query = body.query ?? {};
body.query.userId = userId;
body.query.projectId = projectId
const res = await this.service.page({
query: body.query,
page: body.page,
sort: body.sort,
});
return this.ok(res);
}
@Post('/list', { description: Constants.per.authOnly, summary: "查询监控运行历史列表" })
async list(@Body(ALL) body: any) {
body.query = body.query ?? {};
const { projectId, userId } = await this.getProjectUserIdRead()
body.query.userId = userId;
body.query.projectId = projectId
return await super.list(body);
}
@Post('/info', { description: Constants.per.authOnly, summary: "查询监控运行历史详情" })
async info(@Query('id') id: number) {
await this.checkOwner(this.service,id,"read");
return await super.info(id);
}
@Post('/delete', { description: Constants.per.authOnly, summary: "删除监控运行历史" })
async delete(@Query('id') id: number) {
await this.checkOwner(this.service,id,"write");
return await super.delete(id);
}
@Post('/batchDelete', { description: Constants.per.authOnly, summary: "批量删除监控运行历史" })
async batchDelete(@Body('ids') ids: number[]) {
const { projectId, userId } = await this.getProjectUserIdWrite()
await this.service.batchDelete(ids,userId,projectId);
return this.ok();
}
}

View File

@@ -67,6 +67,7 @@ export class AutoCRegisterCron {
await this.registerUserExpireCheckCron();
await this.registerDomainExpireCheckCron();
}
async registerSiteMonitorCron() {
@@ -211,11 +212,11 @@ export class AutoCRegisterCron {
if (!isPlus()){
return
}
// 添加域名即将到期检查任务
// 添加域名即将到期同步任务
const randomWeek = Math.floor(Math.random() * 7) + 1
const randomHour = Math.floor(Math.random() * 24)
const randomMinute = Math.floor(Math.random() * 60)
logger.info(`注册域名注册过期时间检查任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`)
logger.info(`注册域名注册过期时间同步任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`)
this.cron.register({
name: 'domain-expire-check',
cron: `0 ${randomMinute} ${randomHour} ? * ${randomWeek}`, // 每周随机一天检查一次

View File

@@ -1,20 +1,24 @@
import { http, logger, utils } from '@certd/basic';
import { AccessService, BaseService } from '@certd/lib-server';
import { AccessService, BaseService, isEnterprise } from '@certd/lib-server';
import { doPageTurn, Pager, PageRes } from '@certd/pipeline';
import { DomainVerifiers } from "@certd/plugin-cert";
import { createDnsProvider, dnsProviderRegistry, DomainParser, parseDomainByPsl } from "@certd/plugin-lib";
import { createDnsProvider, dnsProviderRegistry, DomainParser } from "@certd/plugin-lib";
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import dayjs from 'dayjs';
import { In, Not, Repository } from 'typeorm';
import { merge } from 'lodash-es';
import { In, LessThan, Not, Repository } from 'typeorm';
import { BackTask, taskExecutor } from '../../basic/service/task-executor.js';
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
import { UserDomainImportSetting } from '../../mine/service/models.js';
import { Cron } from '../../cron/cron.js';
import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js';
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
import { JobHistoryService } from '../../monitor/service/job-history-service.js';
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
import { DomainEntity } from '../entity/domain.js';
import { TldClient } from './tld-client.js';
export interface SyncFromProviderReq {
userId: number;
@@ -27,6 +31,8 @@ export interface SyncFromProviderReq {
const DOMAIN_IMPORT_TASK_TYPE = 'domainImportTask'
const DOMAIN_EXPIRE_TASK_TYPE = 'domainExpirationSyncTask'
const DOMAIN_EXPIRE_CHECK_TYPE = 'domainExpirationCheck'
/**
*
@@ -51,6 +57,14 @@ export class DomainService extends BaseService<DomainEntity> {
@Inject()
userSettingService: UserSettingsService;
@Inject()
jobHistoryService: JobHistoryService;
@Inject()
cron: Cron;
//@ts-ignore
getRepository() {
return this.repository;
@@ -320,9 +334,9 @@ export class DomainService extends BaseService<DomainEntity> {
logger.info(`从域名提供商${dnsProviderType}导入域名完成(${key}),共导入${task.total}个域名,跳过${task.getSkipCount()}个域名,成功${task.getSuccessCount()}个域名,失败${task.getErrorCount()}个域名`)
}
async getDomainImportTaskStatus(req: { userId?: number ,projectId?: number}) {
async getDomainImportTaskStatus(req: { userId?: number, projectId?: number }) {
const userId = req.userId || 0
const projectId = req.projectId
const projectId = req.projectId
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
const list = setting?.domainImportList || []
@@ -429,8 +443,6 @@ export class DomainService extends BaseService<DomainEntity> {
await this.deleteDomainImportTask({ userId, projectId, key })
}
return await this.addDomainImportTask({ userId, projectId, dnsProviderType, dnsProviderAccessId, index })
}
@@ -441,7 +453,7 @@ export class DomainService extends BaseService<DomainEntity> {
const userId = req.userId ?? 'all'
const projectId = req.projectId
let key = `user_${userId}`
if (projectId!=null) {
if (projectId != null) {
key += `_${projectId}`
}
const task = taskExecutor.get(DOMAIN_EXPIRE_TASK_TYPE, key)
@@ -452,7 +464,7 @@ export class DomainService extends BaseService<DomainEntity> {
const userId = req.userId
const projectId = req.projectId
let key = `user_${userId ?? 'all'}`
if (projectId!=null) {
if (projectId != null) {
key += `_${projectId}`
}
taskExecutor.start(new BackTask({
@@ -461,61 +473,22 @@ export class DomainService extends BaseService<DomainEntity> {
title: `同步注册域名过期时间(${key}))`,
run: async (task: BackTask) => {
await this._syncDomainsExpirationDate({ userId, projectId, task })
if (userId != null) {
await this.startCheckDomainExpiration({ userId, projectId })
}
}
}))
}
private async _syncDomainsExpirationDate(req: { userId?: number, projectId?: number, task: BackTask }) {
//同步所有域名的过期时间
const pager = new Pager({
pageNo: 1,
pageSize: 100,
})
const dnsJson = await http.request({
url: "https://data.iana.org/rdap/dns.json",
method: "GET",
})
const rdapMap: Record<string, string> = {}
for (const item of dnsJson.services) {
// [["store","work"], ["https://rdap.centralnic.com/store/"]],
const suffixes = item[0]
const urls = item[1]
for (const suffix of suffixes) {
rdapMap[suffix] = urls[0]
}
}
const getDomainExpirationDate = async (domain: string) => {
const parsed = parseDomainByPsl(domain)
const mainDomain = parsed.domain || ''
if (mainDomain !== domain) {
req.task.addError(`${domain}】为子域名,跳过同步`)
return
}
const suffix = parsed.tld || ''
const rdapUrl = rdapMap[suffix]
if (!rdapUrl) {
req.task.addError(`${domain}】未找到${suffix}的rdap地址`)
return
}
// https://rdap.nic.work/domain/handsfree.work
const rdap = await http.request({
url: `${rdapUrl}domain/${domain}`,
method: "GET",
})
let res: any = {}
const events = rdap.events || []
for (const item of events) {
if (item.eventAction === 'expiration') {
res.expirationDate = dayjs(item.eventDate).valueOf()
} else if (item.eventAction === 'registration') {
res.registrationDate = dayjs(item.eventDate).valueOf()
}
}
return res
}
const tldClient = new TldClient();
const query: any = {
challengeType: "dns",
}
@@ -546,10 +519,7 @@ export class DomainService extends BaseService<DomainEntity> {
const itemHandle = async (item: any) => {
req.task.incrementCurrent()
try {
const res = await getDomainExpirationDate(item.domain)
if (!res) {
return
}
const res = await tldClient.getDomainExpirationDate(item.domain)
const { expirationDate, registrationDate } = res
if (!expirationDate) {
req.task.addError(`${item.domain}】获取域名${item.domain}过期时间失败`)
@@ -566,6 +536,7 @@ export class DomainService extends BaseService<DomainEntity> {
} catch (error) {
const errorMsg = `${item.domain}${error.message ?? error}`
req.task.addError(errorMsg)
logger.error(errorMsg)
} finally {
await utils.sleep(1000)
}
@@ -573,7 +544,151 @@ export class DomainService extends BaseService<DomainEntity> {
await doPageTurn({ pager, getPage: getDomainPage, itemHandle: itemHandle })
const key = `user_${req.userId || 'all'}`
logger.info(`同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`)
const log = `同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`
logger.info(log)
}
public async startCheckDomainExpiration(req: { userId?: number, projectId?: number }) {
const { userId, projectId } = req
if (userId == null) {
throw new Error('userId is required');
}
if (projectId && !isEnterprise()) {
logger.warn(`当前未开启企业模式,跳过检查项目(${projectId})的域名过期时间`)
return
}
const setting = await this.monitorSettingGet({ userId, projectId })
if (!setting || !setting.enabled) {
return
}
const jobHistory: any = {
userId,
projectId,
type: DOMAIN_EXPIRE_CHECK_TYPE,
title: `检查注册域名过期时间`,
startAt: dayjs().valueOf(),
result: "start",
}
await this.jobHistoryService.add(jobHistory)
const expireDays = setting.willExpireDays || 30
const ltTime = dayjs().add(expireDays, 'day').valueOf()
const total = await this.repository.count({
where:{
userId,
projectId,
disabled: false,
}
})
//开始检查域名过期时间
const list = await this.repository.find({
where: {
userId,
projectId,
disabled: false,
expirationDate: LessThan(ltTime)
}
})
const now = dayjs().valueOf()
let willExpireDomains = []
let hasExpireDomains = []
for (const item of list) {
const { expirationDate } = item
const leftDays = dayjs(expirationDate).diff(dayjs(), 'day')
//@ts-ignore
item.leftDays = leftDays
if (expirationDate < now) {
hasExpireDomains.push(item)
} else {
willExpireDomains.push(item)
}
}
const title = `域名过期检查:即将过期 ${willExpireDomains.length} 个域名,已过期 ${hasExpireDomains.length} 个域名,共 ${total} 个域名`
try {
await this.jobHistoryService.update({
id: jobHistory.id,
content: title,
result: "done",
endAt: dayjs().valueOf(),
})
} catch (error) {
logger.error(`更新域名过期检查任务状态失败:${error.message ?? error}`)
}
if (list.length == 0) {
//没有过期域名 不发通知
return
}
//发送通知
const willExpireDomainsStr = willExpireDomains.map(item => `${item.domain} (剩余${item.leftDays}天)`).join('\n ')
const hasExpireDomainsStr = hasExpireDomains.map(item => `${item.domain} (已过期${item.leftDays}天)`).join('\n ')
const content = `您有域名即将过期,请尽快续费
即将过期域名: ${willExpireDomains.length} 个 (有效期<${expireDays}天)
${willExpireDomainsStr}
已过期域名: ${hasExpireDomains.length}
${hasExpireDomainsStr}`
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
const notificationService = await taskService.getNotificationService()
const url = await notificationService.getBindUrl("#/certd/cert/domain");
await notificationService.send({
id: setting.notificationId,
useDefault: true,
logger: logger,
body: {
title: title,
content: content,
url: url,
errorMessage: title,
notificationType: DOMAIN_EXPIRE_CHECK_TYPE,
willExpireDomains,
hasExpireDomains,
}
})
}
public async monitorSettingGet(req: { userId?: number, projectId?: number }) {
const { userId, projectId } = req
const setting = await this.userSettingService.getSetting<UserDomainMonitorSetting>(userId, projectId, UserDomainMonitorSetting)
return setting || {}
}
public async monitorSettingSave(req: { userId?: number, projectId?: number, setting?: any }) {
const { userId, projectId, setting } = req
const bean: UserDomainMonitorSetting = new UserDomainMonitorSetting()
merge(bean, setting)
await this.userSettingService.saveSetting<UserDomainMonitorSetting>(userId, projectId, bean)
await this.registerMonitorCron({ userId, projectId })
}
public async registerMonitorCron(req: { userId?: number, projectId?: number }) {
const { userId, projectId } = req
const setting = await this.monitorSettingGet(req)
const key = `${DOMAIN_EXPIRE_CHECK_TYPE}:${userId}_${projectId || ''}`
this.cron.remove(key)
if (setting.enabled) {
this.cron.register({
cron: setting.cron,
name: key,
job: async () => {
await this.startCheckDomainExpiration({ userId, projectId })
},
})
}
}
}

View File

@@ -0,0 +1,128 @@
import { http, logger } from '@certd/basic';
import { parseDomainByPsl } from "@certd/plugin-lib";
import dayjs from 'dayjs';
export interface DomainInfo {
expirationDate?: number;
registrationDate?: number;
}
export class TldClient {
private rdapMap: Record<string, string> = {}
private isInitialized = false;
constructor() {
}
async init() {
if (this.isInitialized) {
return;
}
const dnsJson = await http.request({
url: "https://data.iana.org/rdap/dns.json",
method: "GET",
})
for (const item of dnsJson.services) {
const suffixes = item[0]
const urls = item[1]
for (const suffix of suffixes) {
this.rdapMap[suffix] = urls[0]
}
}
this.isInitialized = true;
}
async getDomainExpirationDate(domain: string): Promise<DomainInfo> {
await this.init();
const parsed = parseDomainByPsl(domain)
const mainDomain = parsed.domain || ''
if (mainDomain !== domain) {
const message= `${domain}】为子域名,无法获取过期时间`
logger.warn(message)
throw new Error(message)
}
try {
return await this.getDomainExpirationByRdap(domain, parsed.tld || '')
} catch (error) {
logger.error(error.message)
return await this.getDomainExpirationByWhoiser(domain)
}
}
private async getDomainExpirationByRdap(domain: string, suffix: string): Promise<DomainInfo> {
const rdapUrl = this.rdapMap[suffix]
if (!rdapUrl) {
throw new Error(`${domain}】未找到${suffix}的rdap地址`)
}
const rdap = await http.request({
url: `${rdapUrl}domain/${domain}`,
method: "GET",
})
let res: DomainInfo = {}
const events = rdap.events || []
for (const item of events) {
if (item.eventAction === 'expiration') {
res.expirationDate = dayjs(item.eventDate).valueOf()
} else if (item.eventAction === 'registration') {
res.registrationDate = dayjs(item.eventDate).valueOf()
}
}
return res
}
private async getDomainExpirationByWhoiser(domain: string): Promise<DomainInfo> {
const whoiser = await import("whoiser")
const result = await whoiser.whoisDomain(domain, {
follow: 2,
timeout: 5000
})
let res: DomainInfo = {}
/**
* {
"Domain Status": [
"ok",
],
"Name Server": [
"dns21.hichina.com",
"dns22.hichina.com",
],
text: [
"",
],
"Domain Name": "docmirror.cn",
ROID: "20200907s10001s31265717-cn",
"Registrant Name": "肖君诺",
"Registrant Email": "252959493@qq.com",
Registrar: "阿里巴巴云计算(北京)有限公司",
"Created Date": "2020-09-07 09:22:54",
"Expiry Date": "2026-09-07 09:22:54",
DNSSEC: "unsigned",
}
*/
for (const server in result) {
const data = result[server] as any
if (data['Expiry Date']) {
res.expirationDate = dayjs(data['Expiry Date']).valueOf()
}
if (data['Created Date']) {
res.registrationDate = dayjs(data['Created Date']).valueOf()
}
if (res.expirationDate && res.registrationDate) {
break
}
}
if (!res.expirationDate) {
throw new Error(`${domain}】whois查询未找到过期时间`)
}
return res
}
}

View File

@@ -1,6 +1,5 @@
import { Config, Configuration, Logger } from '@midwayjs/core';
import { ILogger } from '@midwayjs/logger';
import { IMidwayContainer } from '@midwayjs/core';
import { logger } from '@certd/basic';
import { Config, Configuration, IMidwayContainer } from '@midwayjs/core';
import { Cron } from './cron.js';
// ... (see below) ...
@@ -11,18 +10,15 @@ import { Cron } from './cron.js';
export class CronConfiguration {
@Config()
config;
@Logger()
logger: ILogger;
cron: Cron;
async onReady(container: IMidwayContainer) {
this.logger.info('cron start');
logger.info('cron start');
this.cron = new Cron({
logger: this.logger,
logger: logger,
...this.config,
});
container.registerObject('cron', this.cron);
this.cron.start();
this.logger.info('cron started');
logger.info('cron started');
}
}

View File

@@ -31,6 +31,18 @@ export class UserSiteMonitorSetting extends BaseSettings {
certValidDays?:number = 14;
}
export class UserDomainMonitorSetting extends BaseSettings {
static __title__ = "域名到期监控设置";
static __key__ = "user.domain.monitor";
enabled?:boolean = false;
notificationId?:number= 0;
cron?:string = undefined;
willExpireDays?:number = 30;
}
export class UserEmailSetting extends BaseSettings {
static __title__ = "用户邮箱设置";
static __key__ = "user.email";

View File

@@ -0,0 +1,52 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
@Entity('cd_job_history')
export class JobHistoryEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ name: 'project_id', comment: '项目id' })
projectId: number;
@Column({ name: 'type', comment: '类型' })
type: string;
@Column({ name: 'title', comment: '标题' })
title: string;
@Column({ name: 'content', comment: '内容' })
content: string;
@Column({ name: 'related_id', comment: '关联id' })
relatedId: string;
@Column({ name: 'result', comment: '结果' })
result: string;
@Column({ name: 'start_at', comment: '开始时间' })
startAt: number;
@Column({ name: 'end_at', comment: '结束时间' })
endAt: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
pipeline?: PipelineEntity;
}

View File

@@ -0,0 +1,23 @@
import { BaseService } from "@certd/lib-server";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { JobHistoryEntity } from "../entity/job-history.js";
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class JobHistoryService extends BaseService<JobHistoryEntity> {
@InjectEntityModel(JobHistoryEntity)
repository: Repository<JobHistoryEntity>;
@Inject()
userSettingsService: UserSettingsService;
//@ts-ignore
getRepository() {
return this.repository;
}
}

View File

@@ -17,6 +17,8 @@ import {SiteIpEntity} from "../entity/site-ip.js";
import {Cron} from "../../cron/cron.js";
import { dnsContainer } from "./dns-custom.js";
import { merge } from "lodash-es";
import { JobHistoryService } from "./job-history-service.js";
import { JobHistoryEntity } from "../entity/job-history.js";
@Provide()
@Scope(ScopeEnum.Request, {allowDowngrade: true})
@@ -39,6 +41,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
@Inject()
siteIpService: SiteIpService;
@Inject()
jobHistoryService: JobHistoryService;
@Inject()
cron: Cron;
@@ -352,10 +357,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (userId==null) {
throw new Error("userId is required");
}
const sites = await this.repository.find({
where: {userId,projectId}
});
this.checkList(sites,false);
// const sites = await this.repository.find({
// where: {userId,projectId}
// });
// this.checkList(sites,false);
await this.triggerJobOnce(userId,projectId);
}
async checkList(sites: SiteInfoEntity[],isCommon: boolean) {
@@ -516,6 +522,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async triggerJobOnce(userId?:number,projectId?:number) {
logger.info(`站点证书检查开始执行[${userId??'所有用户'}_${projectId??'所有项目'}]`);
const query:any = { disabled: false };
let jobEntity :Partial<JobHistoryEntity> = null;
if(userId!=null){
query.userId = userId;
if(projectId){
@@ -523,12 +530,22 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
//判断是否已关闭
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
if (!setting.cron) {
if (setting && !setting.cron) {
return;
}
jobEntity = {
userId,
projectId,
type:"siteCertMonitor",
title: '站点证书检查',
result:"start",
startAt:new Date().getTime(),
}
await this.jobHistoryService.add(jobEntity);
}
let offset = 0;
const limit = 50;
let count = 0;
while (true) {
const res = await this.page({
query: query,
@@ -541,10 +558,20 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
offset += records.length;
const isCommon = !userId;
count += records.length;
await this.checkList(records,isCommon);
}
logger.info(`站点证书检查完成[${userId??'所有用户'}_${projectId??'所有项目'}]`);
if(jobEntity){
await this.jobHistoryService.update({
id: jobEntity.id,
result: "done",
content:`共检查${count}个站点`,
endAt:new Date().getTime(),
updateTime:new Date(),
});
}
}
async batchDelete(ids: number[], userId: number,projectId?:number): Promise<void> {

View File

@@ -23,4 +23,8 @@ export class NotificationGetter implements INotificationService {
async send(req: NotificationSendReq): Promise<void> {
return await this.notificationService.send(req, this.userId, this.projectId);
}
async getBindUrl(url: string) {
return await this.notificationService.getBindUrl(url);
}
}

View File

@@ -65,7 +65,9 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
{ value: 'na-siliconvalley' },
{ value: 'na-toronto' },
{ value: 'sa-saopaulo' },
{ value: 'ap-taipei' },
],
helper: '如果列表中没有,您可以手动输入',
},
required: true,
})

104
pnpm-lock.yaml generated
View File

@@ -49,7 +49,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@@ -213,10 +213,10 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../basic
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/plus-core
dayjs:
specifier: ^1.11.7
@@ -412,7 +412,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@@ -452,19 +452,19 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/plus-core
'@midwayjs/cache':
specifier: 3.14.0
@@ -610,16 +610,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../plugin-lib
psl:
specifier: ^1.9.0
@@ -683,16 +683,16 @@ importers:
specifier: ^3.964.0
version: 3.964.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/plus-core
'@kubernetes/client-node':
specifier: 0.21.0
@@ -783,16 +783,16 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../plus-core
'@midwayjs/core':
specifier: 3.20.11
@@ -868,16 +868,16 @@ importers:
packages/pro/plugin-plus:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../plus-core
crypto-js:
specifier: ^4.2.0
@@ -953,7 +953,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1249,10 +1249,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-iframe
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@@ -1447,46 +1447,46 @@ importers:
specifier: ^3.990.0
version: 3.990.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.2
version: 8.4.2
'@certd/jdcloud':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-k8s
'@certd/lib-server':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/plugin-plus
'@certd/plus-core':
specifier: ^1.39.7
specifier: ^1.39.8
version: link:../../pro/plus-core
'@google-cloud/publicca':
specifier: ^1.3.0
@@ -1719,6 +1719,9 @@ importers:
wechatpay-node-v3:
specifier: ^2.2.1
version: 2.2.1
whoiser:
specifier: 2.0.0-beta.10
version: 2.0.0-beta.10
xml2js:
specifier: ^0.6.2
version: 0.6.2
@@ -10743,6 +10746,10 @@ packages:
pump@3.0.2:
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
punycode-esm@1.0.15:
resolution: {integrity: sha512-pR7pzaunGU4g3v3vMIXD9WnrGUiEBs2ezcVYr9piTC/HVem9F+MJ+JNdSeYD9pK7EumLMUMO9F3Gsa3RS+o7pA==}
engines: {node: '>=20'}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
@@ -12737,6 +12744,9 @@ packages:
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
whoiser@2.0.0-beta.10:
resolution: {integrity: sha512-Y1uyLNR6zQnKj/mZPYtofs6u/9ZAnBzAuiTgU1aEMKjlnknrQdLyOKv9ErmIeF2mySMjGFj0tCam+DpxotBXHw==}
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
@@ -20854,13 +20864,13 @@ snapshots:
resolve: 1.22.10
semver: 6.3.1
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8):
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
dependencies:
eslint: 7.32.0
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
optionalDependencies:
eslint-config-prettier: 8.10.0(eslint@8.57.0)
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
dependencies:
@@ -23286,7 +23296,7 @@ snapshots:
eslint: 7.32.0
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-node: 11.1.0(eslint@7.32.0)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
execa: 5.1.1
inquirer: 7.3.3
json5: 2.2.3
@@ -24561,6 +24571,8 @@ snapshots:
end-of-stream: 1.4.4
once: 1.4.0
punycode-esm@1.0.15: {}
punycode.js@2.3.1: {}
punycode@1.4.1: {}
@@ -26843,6 +26855,10 @@ snapshots:
dependencies:
isexe: 3.1.1
whoiser@2.0.0-beta.10:
dependencies:
punycode-esm: 1.0.15
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0