Compare commits

...

47 Commits

Author SHA1 Message Date
xiaojunnuo
11d0daa59a v1.26.12 2024-10-26 00:00:16 +08:00
xiaojunnuo
22764abd38 build: prepare to build 2024-10-25 23:57:17 +08:00
xiaojunnuo
a7414047ee chore: header menu 初步 2024-10-25 23:56:24 +08:00
xiaojunnuo
c4164c66e2 perf: 文件名特殊字符限制输入 2024-10-25 22:49:05 +08:00
xiaojunnuo
a90d1e68ee perf: 支持配置公共ZeroSSL授权 2024-10-25 21:47:28 +08:00
xiaojunnuo
7aac1460c3 chore: 优化域名match 2024-10-25 18:32:47 +08:00
xiaojunnuo
1cc1d1c03c chore: 优化域名match 2024-10-25 17:47:39 +08:00
xiaojunnuo
b421798a1b chore: 2024-10-25 16:51:36 +08:00
xiaojunnuo
f876ac99b0 chore: 2024-10-25 10:57:38 +08:00
xiaojunnuo
4075be7849 perf: 部署到阿里云任意云资源,阿里云部署大杀器 2024-10-24 17:50:42 +08:00
xiaojunnuo
98ef9aa479 chore: 群晖doc 2024-10-24 16:02:29 +08:00
xiaojunnuo
25689efc99 chore: 2024-10-24 14:46:16 +08:00
xiaojunnuo
a8a45d7f75 perf: 支持部署到腾讯云COS 2024-10-24 14:45:51 +08:00
xiaojunnuo
a3ef3fb5cf chore: 2024-10-24 12:10:20 +08:00
xiaojunnuo
d782655cb4 perf: 新增部署到腾讯云CDN-v2,推荐使用 2024-10-23 17:49:38 +08:00
xiaojunnuo
f126f9f932 perf: 新增部署到百度云CDN插件 2024-10-23 16:33:53 +08:00
xiaojunnuo
5b148b7ed9 perf: 优化cron选择器,增加下次触发时间显示 2024-10-23 16:04:57 +08:00
xiaojunnuo
f9e29ef041 build: publish 2024-10-23 10:38:50 +08:00
xiaojunnuo
11255a1ecf build: trigger build image 2024-10-23 10:38:34 +08:00
xiaojunnuo
3a78cb9929 v1.26.11 2024-10-23 10:37:06 +08:00
xiaojunnuo
b2f8ee3836 build: prepare to build 2024-10-23 10:35:28 +08:00
xiaojunnuo
3681d89a61 chore: 2024-10-23 10:34:55 +08:00
xiaojunnuo
4ea3edd59e fix: 修复移动任务后出现空阶段的bug 2024-10-23 09:38:03 +08:00
xiaojunnuo
1291e98e82 perf: 优化日志颜色 2024-10-22 19:13:47 +08:00
xiaojunnuo
09847ce074 pref: 允许忽略自签证书校验 2024-10-22 18:46:29 +08:00
xiaojunnuo
18ee87daff fix: 允许七牛云cdn插件输入.号开头的通配符域名 2024-10-22 18:45:56 +08:00
xiaojunnuo
2b89fba7eb chore: 2024-10-22 16:28:38 +08:00
xiaojunnuo
41d9c3ac83 perf: 优化证书申请速度和成功率,反代地址优化,google基本可以稳定请求。增加请求重试。 2024-10-22 16:21:35 +08:00
xiaojunnuo
a705182b85 perf: 申请证书启用新的反代地址 2024-10-22 11:31:32 +08:00
xiaojunnuo
f8b99b81a2 fix: 修复google证书*.xx.com与xx.com同时申请时报错的bug 2024-10-22 11:23:59 +08:00
xiaojunnuo
ffeede38af perf: 优化pfx密码密码输入框,让浏览器不自动填写密码 2024-10-22 11:22:59 +08:00
xiaojunnuo
3db216f515 fix: 申请证书没有使用到系统设置的http代理的bug 2024-10-22 01:01:04 +08:00
xiaojunnuo
4a7018ac26 chore: 1 2024-10-21 18:10:23 +08:00
xiaojunnuo
74a1873e58 chore: 1 2024-10-21 11:15:41 +08:00
xiaojunnuo
f984472257 build: publish 2024-10-20 12:35:06 +08:00
xiaojunnuo
e9a4b26595 build: trigger build image 2024-10-20 12:34:53 +08:00
xiaojunnuo
617cc13e29 v1.26.10 2024-10-20 12:33:44 +08:00
xiaojunnuo
3e0aabcd85 build: prepare to build 2024-10-20 12:31:44 +08:00
xiaojunnuo
027991b253 chore: 2024-10-20 12:31:35 +08:00
xiaojunnuo
4bda5b2167 chore: 2024-10-20 12:30:04 +08:00
xiaojunnuo
c1e3e2ee1f fix: 修复cname服务普通用户access访问权限问题 2024-10-20 11:47:35 +08:00
xiaojunnuo
e8b5fcf3ee chore: 2024-10-20 03:45:42 +08:00
xiaojunnuo
88478c1482 chore: 2024-10-20 03:38:53 +08:00
xiaojunnuo
eb937737c2 chore: baidutongji 2024-10-20 03:35:08 +08:00
xiaojunnuo
e646f01c2a chore: 2024-10-20 03:18:17 +08:00
xiaojunnuo
bfd31e8490 build: publish 2024-10-20 03:13:44 +08:00
xiaojunnuo
41b5e57d39 build: trigger build image 2024-10-20 03:13:31 +08:00
128 changed files with 1997 additions and 286 deletions

View File

@@ -1,9 +1,9 @@
name: deploy-demo
on:
push:
branches: ['v2']
paths:
- "deploy.trigger"
branches: ['v2-dev']
# paths:
# - "deploy.trigger"
workflow_run:
workflows: [ "build-image" ]
types:

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
### Performance Improvements
* 部署到阿里云任意云资源,阿里云部署大杀器 ([4075be7](https://github.com/certd/certd/commit/4075be7849b140acb92bd8da8a9acbf4eef85180))
* 文件名特殊字符限制输入 ([c4164c6](https://github.com/certd/certd/commit/c4164c66e29f3ec799f98108a344806ca61e94ff))
* 新增部署到百度云CDN插件 ([f126f9f](https://github.com/certd/certd/commit/f126f9f932d37fa01fff1accc7bdd17d349f8db5))
* 新增部署到腾讯云CDN-v2推荐使用 ([d782655](https://github.com/certd/certd/commit/d782655cb4dfbb74138178afbffeee76fc755115))
* 优化cron选择器增加下次触发时间显示 ([5b148b7](https://github.com/certd/certd/commit/5b148b7ed960ca6f7f5b733b2eadd56eeecbd4c2))
* 支持部署到腾讯云COS ([a8a45d7](https://github.com/certd/certd/commit/a8a45d7f757820990e278533277a3deda5ba48f3))
* 支持配置公共ZeroSSL授权 ([a90d1e6](https://github.com/certd/certd/commit/a90d1e68ee9cbc3705223457b8a86f071b150968))
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Bug Fixes
* 申请证书没有使用到系统设置的http代理的bug ([3db216f](https://github.com/certd/certd/commit/3db216f515ba404cb4330fdab452971b22a50f08))
* 修复移动任务后出现空阶段的bug ([4ea3edd](https://github.com/certd/certd/commit/4ea3edd59e93ca4f5b2e43b20dd4ef33909caddb))
* 修复google证书*.xx.com与xx.com同时申请时报错的bug ([f8b99b8](https://github.com/certd/certd/commit/f8b99b81a23e7e9fd5e05ebd5caf355c41d67a90))
* 允许七牛云cdn插件输入.号开头的通配符域名 ([18ee87d](https://github.com/certd/certd/commit/18ee87daff6eafc2201b58e28d85aafd3cb7a5b9))
### Performance Improvements
* 申请证书启用新的反代地址 ([a705182](https://github.com/certd/certd/commit/a705182b85e51157883e48f23463263793bf3c12))
* 优化日志颜色 ([1291e98](https://github.com/certd/certd/commit/1291e98e821c5b1810aab7f0aebe3f5f5cd44a20))
* 优化证书申请速度和成功率反代地址优化google基本可以稳定请求。增加请求重试。 ([41d9c3a](https://github.com/certd/certd/commit/41d9c3ac8398def541e65351cbe920d4a927182d))
* 优化pfx密码密码输入框让浏览器不自动填写密码 ([ffeede3](https://github.com/certd/certd/commit/ffeede38afa70c5ff6f2015516bead23d2c4df87))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
### Bug Fixes
* 修复cname服务普通用户access访问权限问题 ([c1e3e2e](https://github.com/certd/certd/commit/c1e3e2ee1f923ee5806479dd5f178c3286a01ae0))
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
### Bug Fixes

View File

@@ -1 +1 @@
07:17
10:38

View File

@@ -25,7 +25,11 @@ export default defineConfig({
name: "keywords",
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署"
}],
["link", { rel: "icon", href: "/favicon.ico" }]
["meta", {
name: "google-site-verification",
content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"
}],
["link", { rel: "icon", href: "/static/logo/logo.svg" }]
],
themeConfig: {
logo: "/static/logo/logo.svg",

View File

@@ -4,6 +4,11 @@ import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'
import Layout from './Layout.vue'
import { registerAnalytics, siteIds, trackPageview } from './plugins/baidutongji'
import { inBrowser } from "vitepress";
export default {
extends: DefaultTheme,
Layout,
@@ -14,5 +19,17 @@ export default {
// },
enhanceApp({ app, router, siteData }) {
// ...
if (inBrowser) {
registerAnalytics(siteIds)
window.addEventListener('hashchange', () => {
const { href: url } = window.location
trackPageview(siteIds, url)
})
router.onAfterRouteChanged = (to) => {
trackPageview(siteIds, to)
}
}
}
} satisfies Theme

View File

@@ -0,0 +1,49 @@
import { inBrowser } from 'vitepress'
/**
* 统计站点的 ID 列表
*/
export const siteIds = 'a6ce877a899ae44292e4f854a53d688e'
declare global {
interface Window {
_hmt: any
}
}
/**
* 注册统计
*/
export function registerAnalytics(siteId: string) {
if (!inBrowser)
return
if (document.querySelector(`#analytics-plugin-${siteId}`))
return
window._hmt = window._hmt ? window._hmt : []
const script = document.createElement('script')
script.id = `analytics-${siteId}`
script.async = true
script.src = `https://hm.baidu.com/hm.js?${siteId}`
document.querySelector('head')?.appendChild(script)
}
/**
* 上报 PV 数据
* @param siteId - 站点 ID
* @param pageUrl - 页面 URL
*/
export function trackPageview(siteId: string, pageUrl: string) {
if (!inBrowser)
return
if (!pageUrl || typeof pageUrl !== 'string')
pageUrl = '/'
if (pageUrl.startsWith('http')) {
const urlFragment = pageUrl.split('/')
const origin = `${urlFragment[0]}//${urlFragment[2]}`
pageUrl = pageUrl.replace(origin, '')
}
window._hmt.push(['_setAccount', siteId])
window._hmt.push(['_trackPageview', pageUrl])
}

View File

@@ -20,7 +20,7 @@ _acme-challenge.cert.com ---> xxxxx.cname.proxy.com ----> txt-record-abcdefg
```
* 证书颁发机构就可以从`_acme-challenge.cert.com`查到TXT记录 `txt-record-abcdefg`,从而完成域名所有权校验。
* 以上可以看出 `xxxxx.cname.proxy.com ----> txt-record-abcdefg` 这一段`Certd`可以自动添加的。
* 剩下的只需要在你的`proxy.com`域名中手动添加一条固定的`CNAME解析`即可
* 剩下的只需要在你的`cert.com`域名中手动添加一条固定的`CNAME解析`即可
## 3. Certd CNAME使用步骤
@@ -36,6 +36,7 @@ _acme-challenge.cert.com ---> xxxxx.cname.proxy.com ----> txt-record-abcdefg
![](./images/cname3.png)
![](./images/cname4.png)
4. 申请过程中Certd会在`xxxxxx.cname.proxy.com`下自动添加TXT记录。
5. 到此即可自动化申请证书了

View File

@@ -0,0 +1,11 @@
# 阿里云相关
## 阿里云客户端请求超时配置
配置环境变量
```shell
ALIYUN_CLIENT_CONNECT_TIMEOUT=10000 # 连接超时,单位毫秒
ALIYUN_CLIENT_READ_TIMEOUT=10000 #读取数据超时,单位毫秒
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,5 @@
# 如何强制重新执行任务
## 强制重新执行任务
![](./images/rerun.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -37,4 +37,22 @@
* 群晖上已经设置好了证书(证书建议设置好描述,插件需要根据描述查找证书)
## 2. 在certd上配置自动更新群晖证书插件
![](./images/deploy.png)
![](./images/deploy.png)
## 3. 配置任务参数
![](./images/deploy1.png)
## 4. 创建授权
![](./images/deploy2.png)
> 注意群晖上要做两个设置
![](./images/setting2.png)
![](./images/setting1.png)
## 5. 运行部署
点击手动运行即可
![](./images/deploy3.png)
![](./images/deploy4.png)
## 6. 配置通知和自动运行
![](./images/notify.png)

View File

@@ -28,7 +28,7 @@ features:
- title: 多域名、泛域名打到一个证书上
details: 支持通配符域名/泛域名,支持多个域名打到一个证书上
- title: 多证书格式支持
details: 支持pem、pfx、der等多种证书格式
details: 支持pem、pfx、der等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构
- title: 支持私有化部署
details: 保障数据安全
- title: 多数据库支持

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.26.9"
"version": "1.26.12"
}

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/publishlab/node-acme-client/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/acme-client
## [1.26.11](https://github.com/publishlab/node-acme-client/compare/v1.26.10...v1.26.11) (2024-10-23)
### Bug Fixes
* 申请证书没有使用到系统设置的http代理的bug ([3db216f](https://github.com/publishlab/node-acme-client/commit/3db216f515ba404cb4330fdab452971b22a50f08))
* 修复google证书*.xx.com与xx.com同时申请时报错的bug ([f8b99b8](https://github.com/publishlab/node-acme-client/commit/f8b99b81a23e7e9fd5e05ebd5caf355c41d67a90))
### Performance Improvements
* 优化证书申请速度和成功率反代地址优化google基本可以稳定请求。增加请求重试。 ([41d9c3a](https://github.com/publishlab/node-acme-client/commit/41d9c3ac8398def541e65351cbe920d4a927182d))
## [1.26.10](https://github.com/publishlab/node-acme-client/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/acme-client
## [1.26.9](https://github.com/publishlab/node-acme-client/compare/v1.26.8...v1.26.9) (2024-10-19)
**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.26.9",
"version": "1.26.12",
"main": "src/index.js",
"types": "types/index.d.ts",
"license": "MIT",
@@ -20,6 +20,7 @@
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"debug": "^4.3.5",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"node-forge": "^1.3.1"
},
@@ -59,5 +60,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -0,0 +1,101 @@
const nodeHttp = require('node:http');
const https = require('node:https');
const { HttpProxyAgent } = require('http-proxy-agent');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { log } = require('./logger');
function createAgent(opts = {}) {
let httpAgent;
let
httpsAgent;
const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
if (httpProxy) {
log(`acme use httpProxy:${httpProxy}`);
httpAgent = new HttpProxyAgent(httpProxy, opts);
}
else {
httpAgent = new nodeHttp.Agent(opts);
}
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
if (httpsProxy) {
log(`acme use httpsProxy:${httpsProxy}`);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts);
}
else {
httpsAgent = new https.Agent(opts);
}
return {
httpAgent,
httpsAgent,
};
}
let defaultAgents = createAgent();
function getGlobalAgents() {
return defaultAgents;
}
function setGlobalProxy(opts) {
log('acme setGlobalProxy:', opts);
if (opts.httpProxy) {
process.env.HTTP_PROXY = opts.httpProxy;
}
if (opts.httpsProxy) {
process.env.HTTPS_PROXY = opts.httpsProxy;
}
defaultAgents = createAgent();
}
class HttpError extends Error {
constructor(error) {
super(error || error.message);
if (!error) {
return;
}
if (error.message.indexOf('ssl3_get_record:wrong version number') >= 0) {
this.message = 'http协议错误服务端要求http协议请检查是否使用了https请求';
}
this.name = error.name;
this.code = error.code;
this.cause = error.cause;
if (error.response) {
this.status = error.response.status;
this.statusText = error.response.statusText;
this.response = {
data: error.response.data,
};
}
let url = '';
if (error.config) {
this.request = {
baseURL: error.config.baseURL,
url: error.config.url,
method: error.config.method,
params: error.config.params,
data: error.config.data,
};
url = error.config.baseURL + error.config.url;
}
if (url) {
this.message = `${this.message}:${url}`;
}
delete error.response;
delete error.config;
delete error.request;
// logger.error(error);
}
}
module.exports = {
setGlobalProxy,
createAgent,
getGlobalAgents,
HttpError,
};

View File

@@ -30,6 +30,7 @@ class AcmeApi {
}
}
}
console.log(locationUrl, mapping);
return locationUrl;
}

View File

@@ -182,12 +182,19 @@ module.exports = async (client, userOpts) => {
authorizations.forEach((authz) => {
const d = authz.identifier.value;
log(`authorization:domain = ${d}, value = ${JSON.stringify(authz)}`);
if (authz.status === 'valid') {
log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
return;
}
let setd = false;
// eslint-disable-next-line no-restricted-syntax
for (const group of domainSets) {
if (!group[d]) {
group[d] = authz;
setd = true;
break;
}
}
if (!setd) {
@@ -197,6 +204,8 @@ module.exports = async (client, userOpts) => {
}
});
// log(`domainSets:${JSON.stringify(domainSets)}`);
const allChallengePromises = [];
// eslint-disable-next-line no-restricted-syntax
for (const domainSet of domainSets) {
@@ -233,28 +242,52 @@ module.exports = async (client, userOpts) => {
return Promise.all(results);
}
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new Error('用户取消');
}
try {
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new Error('用户取消');
}
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
}
catch (e) {
log(`证书申请失败${e.message}`);
throw e;
}
finally {
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
}
catch (e) {
log(`证书申请失败${e.message}`);
throw e;
}
finally {
if (client.opts.sslProvider !== 'google') {
// letsencrypt 如果同时检出两个TXT记录会以第一个为准就会校验失败所以需要提前删除
// zerossl 此方式测试无问题
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
log(e);
}
}
}
}
}
finally {
if (client.opts.sslProvider === 'google') {
// google 相同的域名txt记录是一样的不能提前删除否则校验失败报错如下
// Error: The TXT record retrieved from _acme-challenge.bbc.handsfree.work.
// at the time the challenge was validated did not contain JshHVu7dt_DT6uYILWhokHefFVad2Q6Mw1L-fNZFcq8
// (the base64url-encoded SHA-256 digest of RlJZNBR0LWnxNK_xd2zqtYVvCiNJOKJ3J1NmCjU_9BjaUJgL3k-qSpIhQ-uF4FBS.NRyqT8fRiq6THzzrvkgzgR5Xai2LsA2SyGLAq_wT3qc).
// See https://tools.ietf.org/html/rfc8555#section-8.4 for more information.
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
@@ -263,6 +296,7 @@ module.exports = async (client, userOpts) => {
}
}
}
log('challenge结束');
// log('[auto] Waiting for challenge valid status');

View File

@@ -1,11 +1,11 @@
/**
* Axios instance
*/
const axios = require('axios');
const { parseRetryAfterHeader } = require('./util');
const { log } = require('./logger');
const pkg = require('./../package.json');
const Agents = require('./agents');
const { AxiosError } = axios;
@@ -24,8 +24,8 @@ instance.defaults.acmeSettings = {
httpsChallengePort: 443,
tlsAlpnChallengePort: 443,
retryMaxAttempts: 5,
retryDefaultDelay: 5,
retryMaxAttempts: 3,
retryDefaultDelay: 3,
};
// instance.defaults.proxy = {
// host: '192.168.34.139',
@@ -56,19 +56,26 @@ function isRetryableError(error) {
/* https://github.com/axios/axios/blob/main/lib/core/settle.js */
function validateStatus(response) {
const validator = response.config.retryValidateStatus;
if (!response) {
return new Error('Response is undefined');
}
let validator = null;
if (response.config) {
validator = response.config.retryValidateStatus;
}
if (!response.status || !validator || validator(response.status)) {
return response;
}
throw new AxiosError(
const err = new AxiosError(
`Request failed with status code ${response.status}`,
(Math.floor(response.status / 100) === 4) ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
response.config,
response.request,
response,
);
throw new Agents.HttpError(err);
}
/* Pass all responses through the error interceptor */
@@ -76,8 +83,17 @@ instance.interceptors.request.use((config) => {
if (!('retryValidateStatus' in config)) {
config.retryValidateStatus = config.validateStatus;
}
config.validateStatus = () => false;
const agents = Agents.getGlobalAgents();
// if (config.skipSslVerify) {
// logger.info('跳过SSL验证');
// agents = createAgent({ rejectUnauthorized: false } as any);
// }
// delete config.skipSslVerify;
config.httpsAgent = agents.httpsAgent;
config.httpAgent = agents.httpAgent;
config.proxy = false; // 必须 否则还会走一层代理,
return config;
});
@@ -86,7 +102,7 @@ instance.interceptors.response.use(null, async (error) => {
const { config, response } = error;
if (!config) {
return Promise.reject(error);
return Promise.reject(new Agents.HttpError(error));
}
/* Pick up errors we want to retry */
@@ -115,6 +131,9 @@ instance.interceptors.response.use(null, async (error) => {
}
}
if (!response) {
return Promise.reject(new Agents.HttpError(error));
}
/* Validate and return response */
return validateStatus(response);
});

View File

@@ -558,6 +558,7 @@ class AcmeClient {
const verifyFn = async (abort) => {
if (this.opts.signal && this.opts.signal.aborted) {
abort();
throw new Error('用户取消');
}

View File

@@ -3,21 +3,9 @@
*/
const { createHmac, createSign, constants: { RSA_PKCS1_PADDING } } = require('crypto');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { getJwk } = require('./crypto');
const { log } = require('./logger');
const axios1 = require('./axios');
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
let httpsAgent = null;
if (httpsProxy) {
httpsAgent = new HttpsProxyAgent(httpsProxy);
log(`use https_proxy:${httpsProxy}`);
}
const axios = axios1.create({
proxy: false,
httpsAgent,
});
const axios = require('./axios');
/**
* ACME HTTP client

View File

@@ -39,6 +39,7 @@ exports.forge = require('./crypto/forge');
*/
exports.axios = require('./axios');
exports.agents = require('./agents');
/**
* Logger

View File

@@ -22,7 +22,7 @@ exports.setLogger = (fn) => {
* @param {string} msg Message
*/
exports.log = (msg) => {
debug(msg);
logger(msg);
exports.log = (...msg) => {
debug(...msg);
logger(...msg);
};

View File

@@ -37,6 +37,7 @@ export type UrlMapping={
*/
export interface ClientOptions {
sslProvider:string;
directoryUrl: string;
accountKey: PrivateKeyBuffer | PrivateKeyString;
accountUrl?: string;
@@ -192,6 +193,7 @@ export const forge: CryptoLegacyInterface;
export const axios: AxiosInstance;
export const agents: any;
/**
* Logger
*/

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.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
### Performance Improvements
* 新增部署到百度云CDN插件 ([f126f9f](https://github.com/certd/certd/commit/f126f9f932d37fa01fff1accc7bdd17d349f8db5))
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Bug Fixes
* 申请证书没有使用到系统设置的http代理的bug ([3db216f](https://github.com/certd/certd/commit/3db216f515ba404cb4330fdab452971b22a50f08))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/basic
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
03:09
23:57

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -64,5 +64,5 @@
"vite": "^4.3.8",
"vue-tsc": "^1.6.5"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -17,6 +17,8 @@ import { fileUtils } from './util.file.js';
import * as _ from 'lodash-es';
import { cache } from './util.cache.js';
import dayjs from 'dayjs';
import { domainUtils } from './util.domain.js';
import { optionsUtils } from './util.options.js';
export const utils = {
sleep,
@@ -30,4 +32,6 @@ export const utils = {
cache,
nanoid,
dayjs,
domain: domainUtils,
options: optionsUtils,
};

View File

@@ -0,0 +1,51 @@
//域名是否匹配,支持通配符
function match(targetDomains: string | string[], inDomains: string[]) {
if (!targetDomains || targetDomains.length == 0) {
return false;
}
if (!inDomains || inDomains.length == 0) {
return false;
}
if (typeof targetDomains === 'string') {
targetDomains = [targetDomains];
}
for (let targetDomain of targetDomains) {
let matched = false;
if (targetDomain.startsWith('.')) {
targetDomain = '*' + targetDomain;
}
for (let inDomain of inDomains) {
if (inDomain.startsWith('.')) {
inDomain = '*' + inDomain;
}
if (targetDomain === inDomain) {
matched = true;
break;
}
if (!inDomain.startsWith('*.')) {
//不可能匹配
continue;
}
//子域名匹配通配符即可
const firstDotIndex = targetDomain.indexOf('.');
const targetDomainSuffix = targetDomain.substring(firstDotIndex + 1);
if (targetDomainSuffix === inDomain.substring(2)) {
matched = true;
break;
}
}
//有一个没有匹配上,就失败
if (!matched) {
return false;
}
//这个匹配上了,检查下一个
}
//没有提前return 全部匹配上了
return true;
}
export const domainUtils = {
match,
};

View File

@@ -0,0 +1,42 @@
import { domainUtils } from './util.domain.js';
function groupByDomain(options: any[], inDomains: string[]) {
const matched = [];
const notMatched = [];
for (const item of options) {
if (domainUtils.match(item.domain, inDomains)) {
matched.push(item);
} else {
notMatched.push(item);
}
}
return {
matched,
notMatched,
};
}
function buildGroupOptions(options: any[], inDomains: string[]) {
const grouped = groupByDomain(options, inDomains);
const groupOptions = [];
groupOptions.push({ value: '', disabled: true, label: '----已匹配----' });
if (grouped.matched.length === 0) {
options.push({ value: '', disabled: true, label: '没有可以匹配的域名' });
} else {
for (const matched of grouped.matched) {
groupOptions.push(matched);
}
}
if (grouped.notMatched.length > 0) {
groupOptions.push({ value: '', disabled: true, label: '----未匹配----' });
for (const notMatched of grouped.notMatched) {
groupOptions.push(notMatched);
}
}
return groupOptions;
}
export const optionsUtils = {
groupByDomain,
buildGroupOptions,
};

View File

@@ -5,6 +5,7 @@ import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeHttp from 'http';
import * as https from 'node:https';
import { merge } from 'lodash-es';
export class HttpError extends Error {
status?: number;
statusText?: string;
@@ -35,6 +36,14 @@ export class HttpError extends Error {
params: error.config?.params,
data: error.config?.data,
};
let url = error.config?.url;
if (error.config?.baseURL) {
url = error.config?.baseURL + url;
}
if (url) {
this.message = `${this.message} : url=${url}`;
}
this.response = {
data: error.response?.data,
};
@@ -62,6 +71,10 @@ export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }
defaultAgents = createAgent();
}
export function getGlobalAgents() {
return defaultAgents;
}
/**
* @description 创建请求实例
*/
@@ -72,7 +85,10 @@ export function createAxiosService({ logger }: { logger: Logger }) {
// 请求拦截
service.interceptors.request.use(
(config: any) => {
logger.info(`http request:${config.url}method:${config.method}params:${JSON.stringify(config.params)}`);
logger.info(`http request:${config.url}method:${config.method}`);
if (config.logParams !== false) {
logger.info(`params:${JSON.stringify(config.params)}`);
}
if (config.timeout == null) {
config.timeout = 15000;
}
@@ -84,6 +100,11 @@ export function createAxiosService({ logger }: { logger: Logger }) {
delete config.skipSslVerify;
config.httpsAgent = agents.httpsAgent;
config.httpAgent = agents.httpAgent;
// const agent = new https.Agent({
// rejectUnauthorized: false // 允许自签名证书
// });
// config.httpsAgent = agent;
config.proxy = false; //必须 否则还会走一层代理,
return config;
},
@@ -96,7 +117,11 @@ export function createAxiosService({ logger }: { logger: Logger }) {
// 响应拦截
service.interceptors.response.use(
(response: any) => {
logger.info('http response:', JSON.stringify(response?.data));
if (response?.config?.logRes !== false) {
logger.info(`http response : status=${response?.status},data=${JSON.stringify(response?.data)}`);
} else {
logger.info('http response status:', response?.status);
}
return response.data;
},
(error: any) => {
@@ -157,9 +182,11 @@ export function createAxiosService({ logger }: { logger: Logger }) {
export const http = createAxiosService({ logger }) as HttpClient;
export type HttpClientResponse<R> = any;
export type HttpRequestConfig<D> = {
export type HttpRequestConfig<D=any> = {
skipSslVerify?: boolean;
skipCheckRes?: boolean;
logParams?: boolean;
logRes?: boolean;
} & AxiosRequestConfig<D>;
export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
@@ -171,6 +198,7 @@ export function createAgent(opts: nodeHttp.AgentOptions = {}) {
if (httpProxy) {
logger.info('use httpProxy:', httpProxy);
httpAgent = new HttpProxyAgent(httpProxy, opts as any);
merge(httpAgent.options, opts);
} else {
httpAgent = new nodeHttp.Agent(opts);
}
@@ -178,6 +206,7 @@ export function createAgent(opts: nodeHttp.AgentOptions = {}) {
if (httpsProxy) {
logger.info('use httpsProxy:', httpsProxy);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts as any);
merge(httpsAgent.options, opts);
} else {
httpsAgent = new https.Agent(opts);
}

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.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/pipeline
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Performance Improvements
* 申请证书启用新的反代地址 ([a705182](https://github.com/certd/certd/commit/a705182b85e51157883e48f23463263793bf3c12))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
### Bug Fixes
* 修复cname服务普通用户access访问权限问题 ([c1e3e2e](https://github.com/certd/certd/commit/c1e3e2ee1f923ee5806479dd5f178c3286a01ae0))
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,8 +15,8 @@
"test": "mocha --loader=ts-node/esm"
},
"dependencies": {
"@certd/basic": "^1.26.9",
"@certd/plus-core": "^1.26.9",
"@certd/basic": "^1.26.12",
"@certd/plus-core": "^1.26.12",
"axios": "^1.7.2",
"dayjs": "^1.11.7",
"fix-path": "^4.0.0",
@@ -66,5 +66,5 @@
"vite": "^4.3.8",
"vue-tsc": "^1.6.5"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -16,6 +16,7 @@ export type AccessDefine = Registrable & {
};
export interface IAccessService {
getById<T = any>(id: any): Promise<T>;
getCommonById<T = any>(id: any): Promise<T>;
}
export interface IAccess {

View File

@@ -128,6 +128,10 @@ export class Executor {
this.runtime.skip(runnable);
return resultType;
}
if (resultType == ResultType.disabled) {
this.runtime.disabled(runnable);
return resultType;
}
this.runtime.success(runnable);
return ResultType.success;
} catch (e: any) {
@@ -164,12 +168,14 @@ export class Executor {
let resList: ResultType[] = [];
if (stage.concurrency === ConcurrencyStrategy.Parallel) {
//并行
const pList = [];
for (const item of runnerList) {
pList.push(item());
}
resList = await Promise.all(pList);
} else {
//串行
for (let i = 0; i < runnerList.length; i++) {
const runner = runnerList[i];
resList[i] = await runner();
@@ -181,6 +187,7 @@ export class Executor {
compositionResultType(resList: ResultType[]): ResultType {
let hasSuccess = false;
let hasSkip = false;
let hasDisabled = false;
for (const type of resList) {
if (type === ResultType.error) {
return ResultType.error;
@@ -188,8 +195,14 @@ export class Executor {
hasSuccess = true;
} else if (type === ResultType.skip) {
hasSkip = true;
} else if (type === ResultType.disabled) {
hasDisabled = true;
}
}
if (!hasSuccess && !hasSkip && hasDisabled) {
//全是disabled
return ResultType.disabled;
}
if (!hasSuccess && hasSkip) {
//全是跳过
return ResultType.skip;

View File

@@ -1,9 +1,13 @@
import { IAccess } from "../access";
export type CnameProvider = {
id: any;
domain: string;
dnsProviderType: string;
access?: IAccess;
accessId: any;
};
export type CnameRecord = {
id: any;
domain: string;

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -17,5 +17,5 @@
"rimraf": "^5.0.5",
"rollup": "^3.7.4"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -39,5 +39,5 @@
"tslib": "^2.5.2",
"typescript": "^5.4.2"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/lib-jdcloud

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-jdcloud",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"main": "./dist/bundle.mjs",
"module": "./dist/bundle.mjs",
"types": "./dist/d/index.d.ts",
@@ -27,5 +27,5 @@
"rimraf": "^5.0.5",
"rollup": "^3.7.4"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -18,7 +18,7 @@
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
"@certd/pipeline": "^1.26.9",
"@certd/pipeline": "^1.26.12",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
@@ -40,5 +40,5 @@
"tslib": "^2.5.2",
"typescript": "^5.4.2"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/lib-server
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Bug Fixes
* 申请证书没有使用到系统设置的http代理的bug ([3db216f](https://github.com/certd/certd/commit/3db216f515ba404cb4330fdab452971b22a50f08))
### Performance Improvements
* 申请证书启用新的反代地址 ([a705182](https://github.com/certd/certd/commit/a705182b85e51157883e48f23463263793bf3c12))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/lib-server
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.26.9",
"version": "1.26.12",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -26,8 +26,9 @@
],
"license": "AGPL",
"dependencies": {
"@certd/basic": "^1.26.9",
"@certd/pipeline": "^1.26.9",
"@certd/acme-client": "^1.26.12",
"@certd/basic": "^1.26.12",
"@certd/pipeline": "^1.26.12",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.17.1",
"@midwayjs/i18n": "~3.17.3",
@@ -68,5 +69,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -102,3 +102,20 @@ export class SysSiteEnv {
contactLink?: string;
};
}
export type MenuItem = {
id: string;
title: string;
icon: string;
link: string;
auth: boolean;
permission?: string;
children?: MenuItem[];
};
export class SysHeaderMenus extends BaseSettings {
static __title__ = '顶部菜单';
static __key__ = 'sys.header.menus';
static __access__ = 'public';
menus: MenuItem[];
}

View File

@@ -7,7 +7,7 @@ import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, Sy
import * as _ from 'lodash-es';
import { BaseService } from '../../../basic/index.js';
import { logger, setGlobalProxy } from '@certd/basic';
import { agents } from '@certd/acme-client';
/**
* 设置
*/
@@ -23,7 +23,6 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
getRepository() {
return this.repository;
}
async getById(id: any): Promise<SysSettingsEntity | null> {
const entity = await this.info(id);
if (!entity) {
@@ -129,10 +128,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
async reloadPrivateSettings() {
const bean = await this.getPrivateSettings();
if (bean.httpProxy || bean.httpsProxy) {
setGlobalProxy({
const opts = {
httpProxy: bean.httpProxy,
httpsProxy: bean.httpsProxy,
});
};
setGlobalProxy(opts);
agents.setGlobalProxy(opts);
}
}
@@ -149,10 +150,10 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
async backupSecret() {
const settings = await this.getSettingByKey(SysSecretBackup.__key__);
const privateSettings = await this.getPrivateSettings();
const installInfo = await this.getSetting<SysInstallInfo>(SysInstallInfo);
if (settings == null) {
const backup = new SysSecretBackup();
const privateSettings = await this.getPrivateSettings();
const installInfo = await this.getSetting<SysInstallInfo>(SysInstallInfo);
if (installInfo.siteId == null || privateSettings.encryptSecret == null) {
logger.error('备份密钥失败siteId或encryptSecret为空');
return;
@@ -161,6 +162,14 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
backup.encryptSecret = privateSettings.encryptSecret;
await this.saveSetting(backup);
logger.info('备份密钥成功');
} else {
//校验是否有变化
if (settings.siteId !== installInfo.siteId) {
throw new Error(`siteId与备份不一致可能是数据异常请检查backup=${settings.siteId}, current=${installInfo.siteId}`);
}
if (settings.encryptSecret !== privateSettings.encryptSecret) {
throw new Error('encryptSecret与备份不一致可能是数据异常请检查');
}
}
}
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.26.9",
"version": "1.26.12",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -56,5 +56,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
### Performance Improvements
* 支持配置公共ZeroSSL授权 ([a90d1e6](https://github.com/certd/certd/commit/a90d1e68ee9cbc3705223457b8a86f071b150968))
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Performance Improvements
* 申请证书启用新的反代地址 ([a705182](https://github.com/certd/certd/commit/a705182b85e51157883e48f23463263793bf3c12))
* 优化证书申请速度和成功率反代地址优化google基本可以稳定请求。增加请求重试。 ([41d9c3a](https://github.com/certd/certd/commit/41d9c3ac8398def541e65351cbe920d4a927182d))
* 优化pfx密码密码输入框让浏览器不自动填写密码 ([ffeede3](https://github.com/certd/certd/commit/ffeede38afa70c5ff6f2015516bead23d2c4df87))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
### Bug Fixes
* 修复cname服务普通用户access访问权限问题 ([c1e3e2e](https://github.com/certd/certd/commit/c1e3e2ee1f923ee5806479dd5f178c3286a01ae0))
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.26.9",
"version": "1.26.12",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,9 +15,9 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^1.26.9",
"@certd/basic": "^1.26.9",
"@certd/pipeline": "^1.26.9",
"@certd/acme-client": "^1.26.12",
"@certd/basic": "^1.26.12",
"@certd/pipeline": "^1.26.12",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -57,5 +57,5 @@
"vite": "^3.1.0",
"vue-tsc": "^0.38.9"
},
"gitHead": "fc42ade63fab71fb273981b712c09d6a858f0a0f"
"gitHead": "3a78cb9929fd63bb72f0e00f4389e775c926c789"
}

View File

@@ -89,12 +89,15 @@ export class AcmeService {
}
async getAcmeClient(email: string, isTest = false): Promise<acme.Client> {
const mappings = {};
if (this.sslProvider === "letsencrypt") {
mappings["acme-v02.api.letsencrypt.org"] = this.options.reverseProxy || "le.px.certd.handfree.work";
} else if (this.sslProvider === "google") {
mappings["dv.acme-v02.api.pki.goog"] = this.options.reverseProxy || "gg.px.certd.handfree.work";
}
const urlMapping: UrlMapping = {
enabled: false,
mappings: {
"acme-v02.api.letsencrypt.org": this.options.reverseProxy || "letsencrypt.proxy.handsfree.work",
"dv.acme-v02.api.pki.goog": this.options.reverseProxy || "google.proxy.handsfree.work",
},
mappings,
};
const conf = await this.getAccountConfig(email, urlMapping);
if (conf.key == null) {
@@ -119,6 +122,7 @@ export class AcmeService {
}
}
const client = new acme.Client({
sslProvider: this.sslProvider,
directoryUrl: directoryUrl,
accountKey: conf.key,
accountUrl: conf.accountUrl,
@@ -172,7 +176,7 @@ export class AcmeService {
this.logger.info(`Would create TXT record "${fullRecord}" with value "${recordValue}"`);
let domain = parseDomain(fullDomain);
this.logger.info("解析到域名domain=", domain);
this.logger.info("解析到域名domain=" + domain);
if (domainsVerifyPlan) {
//按照计划执行
@@ -384,7 +388,7 @@ export class AcmeService {
timeout: 10000,
});
} catch (e) {
this.logger.error(`${directoryUrl},测试访问失败`, e.stack);
this.logger.error(`${directoryUrl},测试访问失败`, e.message);
return false;
}
this.logger.info(`${directoryUrl},测试访问成功`);

View File

@@ -40,7 +40,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
name: "a-input",
vModel: "value",
},
rules: [{ type: "email" }],
rules: [{ type: "email", message: "请输入正确的邮箱" }],
required: true,
order: -1,
helper: "请输入邮箱",
@@ -50,7 +50,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
@TaskInput({
title: "PFX证书密码",
component: {
name: "a-input-password",
name: "input-password",
vModel: "value",
},
required: false,
@@ -227,7 +227,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
* "successNotify": true,
* "pfxPassword": "123456"
*/
const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "dnsProviderAccess", "pfxPassword"];
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;

View File

@@ -162,7 +162,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
maybeNeed: true,
required: false,
helper:
"需要提供EAB授权\nZeroSSL请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB Credentials'\n Google:请查看[google获取eab帮助文档](https://gitee.com/certd/certd/blob/v2/doc/google/google.md)用过一次后会绑定邮箱后续复用EAB要用同一个邮箱",
"需要提供EAB授权\nZeroSSL请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB Credentials'\n Google:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/)用过一次后会绑定邮箱后续复用EAB要用同一个邮箱",
mergeScript: `
return {
show: ctx.compute(({form})=>{
@@ -182,7 +182,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
maybeNeed: true,
required: false,
helper:
"google服务账号授权与EAB授权选填其中一个[服务账号授权获取方法](https://gitee.com/certd/certd/blob/v2/doc/google/google.md)\n服务账号授权需要配置代理或者服务器本身在海外",
"google服务账号授权与EAB授权选填其中一个[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/)\n服务账号授权需要配置代理或者服务器本身在海外",
mergeScript: `
return {
show: ctx.compute(({form})=>{
@@ -266,7 +266,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
eab = await this.ctx.accessService.getById(this.eabAccessId);
} else if (this.googleCommonEabAccessId) {
this.logger.info("当前正在使用 google公共EAB授权");
eab = await this.ctx.accessService.getById(this.googleCommonEabAccessId);
eab = await this.ctx.accessService.getCommonById(this.googleCommonEabAccessId);
} else {
this.logger.error("google需要配置EAB授权或服务账号授权");
return;
@@ -277,7 +277,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
eab = await this.ctx.accessService.getById(this.eabAccessId);
} else if (this.zerosslCommonEabAccessId) {
this.logger.info("当前正在使用 zerossl 公共EAB授权");
eab = await this.ctx.accessService.getById(this.zerosslCommonEabAccessId);
eab = await this.ctx.accessService.getCommonById(this.zerosslCommonEabAccessId);
} else {
this.logger.error("zerossl需要配置EAB授权");
return;
@@ -324,8 +324,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
domainsVerifyPlan = await this.createDomainsVerifyPlan();
} else {
const dnsProviderType = this.dnsProviderType;
const dnsProviderAccessId = this.dnsProviderAccess;
dnsProvider = await this.createDnsProvider(dnsProviderType, dnsProviderAccessId);
const access = await this.ctx.accessService.getById(this.dnsProviderAccess);
dnsProvider = await this.createDnsProvider(dnsProviderType, access);
}
try {
@@ -351,9 +351,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}
}
async createDnsProvider(dnsProviderType: string, dnsProviderAccessId: number): Promise<IDnsProvider> {
const access = await this.accessService.getById(dnsProviderAccessId);
const context: DnsProviderContext = { access, logger: this.logger, http: this.ctx.http, utils };
async createDnsProvider(dnsProviderType: string, dnsProviderAccess: any): Promise<IDnsProvider> {
const context: DnsProviderContext = { access: dnsProviderAccess, logger: this.logger, http: this.ctx.http, utils };
return await createDnsProvider({
dnsProviderType,
context,
@@ -367,14 +366,15 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
let dnsProvider = null;
const cnameVerifyPlan: Record<string, CnameVerifyPlan> = {};
if (domainVerifyPlan.type === "dns") {
dnsProvider = await this.createDnsProvider(domainVerifyPlan.dnsProviderType, domainVerifyPlan.dnsProviderAccessId);
const access = await this.ctx.accessService.getById(domainVerifyPlan.dnsProviderAccessId);
dnsProvider = await this.createDnsProvider(domainVerifyPlan.dnsProviderType, access);
} else {
for (const key in domainVerifyPlan.cnameVerifyPlan) {
const cnameRecord = await this.ctx.cnameProxyService.getByDomain(key);
cnameVerifyPlan[key] = {
domain: cnameRecord.cnameProvider.domain,
fullRecord: cnameRecord.recordValue,
dnsProvider: await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.accessId),
dnsProvider: await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.access),
};
}
}

View File

@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.12](https://github.com/certd/certd/compare/v1.26.11...v1.26.12) (2024-10-25)
### Performance Improvements
* 部署到阿里云任意云资源,阿里云部署大杀器 ([4075be7](https://github.com/certd/certd/commit/4075be7849b140acb92bd8da8a9acbf4eef85180))
* 文件名特殊字符限制输入 ([c4164c6](https://github.com/certd/certd/commit/c4164c66e29f3ec799f98108a344806ca61e94ff))
* 新增部署到百度云CDN插件 ([f126f9f](https://github.com/certd/certd/commit/f126f9f932d37fa01fff1accc7bdd17d349f8db5))
* 优化cron选择器增加下次触发时间显示 ([5b148b7](https://github.com/certd/certd/commit/5b148b7ed960ca6f7f5b733b2eadd56eeecbd4c2))
* 支持配置公共ZeroSSL授权 ([a90d1e6](https://github.com/certd/certd/commit/a90d1e68ee9cbc3705223457b8a86f071b150968))
## [1.26.11](https://github.com/certd/certd/compare/v1.26.10...v1.26.11) (2024-10-23)
### Bug Fixes
* 修复移动任务后出现空阶段的bug ([4ea3edd](https://github.com/certd/certd/commit/4ea3edd59e93ca4f5b2e43b20dd4ef33909caddb))
* 允许七牛云cdn插件输入.号开头的通配符域名 ([18ee87d](https://github.com/certd/certd/commit/18ee87daff6eafc2201b58e28d85aafd3cb7a5b9))
### Performance Improvements
* 优化日志颜色 ([1291e98](https://github.com/certd/certd/commit/1291e98e821c5b1810aab7f0aebe3f5f5cd44a20))
* 优化证书申请速度和成功率反代地址优化google基本可以稳定请求。增加请求重试。 ([41d9c3a](https://github.com/certd/certd/commit/41d9c3ac8398def541e65351cbe920d4a927182d))
* 优化pfx密码密码输入框让浏览器不自动填写密码 ([ffeede3](https://github.com/certd/certd/commit/ffeede38afa70c5ff6f2015516bead23d2c4df87))
## [1.26.10](https://github.com/certd/certd/compare/v1.26.9...v1.26.10) (2024-10-20)
### Bug Fixes
* 修复cname服务普通用户access访问权限问题 ([c1e3e2e](https://github.com/certd/certd/commit/c1e3e2ee1f923ee5806479dd5f178c3286a01ae0))
## [1.26.9](https://github.com/certd/certd/compare/v1.26.8...v1.26.9) (2024-10-19)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.26.9",
"version": "1.26.12",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -11,6 +11,7 @@
"debug:force": "vite --force --mode debug",
"build": " vite build ",
"dev-build": "echo 1",
"test:unit": "vitest",
"serve": "vite preview",
"preview": "vite preview",
"pretty-quick": "pretty-quick",
@@ -25,10 +26,10 @@
"dependencies": {
"@ant-design/colors": "^7.0.2",
"@ant-design/icons-vue": "^6.1.0",
"@fast-crud/fast-crud": "^1.21.2",
"@fast-crud/fast-extends": "^1.21.2",
"@fast-crud/ui-antdv4": "^1.21.2",
"@fast-crud/ui-interface": "^1.21.2",
"@fast-crud/fast-crud": "^1.22.2",
"@fast-crud/fast-extends": "^1.22.2",
"@fast-crud/ui-antdv4": "^1.22.2",
"@fast-crud/ui-interface": "^1.22.2",
"@iconify/vue": "^4.1.1",
"@soerenmartius/vue3-clipboard": "^0.1.2",
"@vue-js-cron/light": "^4.0.5",
@@ -41,6 +42,7 @@
"china-division": "^2.7.0",
"core-js": "^3.36.0",
"cos-js-sdk-v5": "^1.7.0",
"cron-parser": "^4.9.0",
"cropperjs": "^1.6.1",
"dayjs": "^1.11.10",
"highlight.js": "^11.9.0",
@@ -61,8 +63,8 @@
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@certd/lib-iframe": "^1.26.9",
"@certd/pipeline": "^1.26.9",
"@certd/lib-iframe": "^1.26.12",
"@certd/pipeline": "^1.26.12",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -77,7 +79,7 @@
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-sfc": "^3.4.21",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.5",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.18",
"caller-path": "^4.0.0",
"chai": "^5.1.0",
@@ -113,6 +115,7 @@
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-windicss": "^1.9.3",
"vitest": "^2.1.2",
"vue-eslint-parser": "^9.4.2",
"vue-tsc": "^1.8.8"
},

View File

@@ -32,6 +32,16 @@ export type SysPublicSetting = {
export type SysInstallInfo = {
siteId: string;
};
export type MenuItem = {
id: string;
title: string;
icon?: string;
path?: string;
children?: MenuItem[];
};
export type HeaderMenus = {
menus: MenuItem[];
};
export type AllSettings = {
sysPublic: SysPublicSetting;
@@ -39,6 +49,7 @@ export type AllSettings = {
plusInfo: PlusInfo;
siteInfo: SiteInfo;
siteEnv: SiteEnv;
headerMenus: HeaderMenus;
};
export async function loadAllSettings(): Promise<AllSettings> {

View File

@@ -53,7 +53,9 @@ function createService() {
// @ts-ignore
response.config.onError(new Error(errorMessage));
}
errorCreate(`${errorMessage}: ${response.config.url}`);
//@ts-ignore
const showErrorNotify = response?.config?.showErrorNotify;
errorCreate(`${errorMessage}: ${response.config.url}`, showErrorNotify);
return dataAxios;
}
}
@@ -97,7 +99,7 @@ function createService() {
default:
break;
}
errorLog(error);
errorLog(error, error?.response?.config?.showErrorNotify);
if (status === 401) {
const userStore = useUserStore();
userStore.logout();

View File

@@ -48,7 +48,7 @@ export function responseError(data = {}, msg = "请求失败", code = 500) {
* @description 记录和显示错误
* @param {Error} error 错误对象
*/
export function errorLog(error: any) {
export function errorLog(error: any, notify = true) {
// 打印到控制台
console.error("errorLog", error);
let message = error.message;
@@ -58,17 +58,22 @@ export function errorLog(error: any) {
if (message.indexOf("ssl3_get_record:wrong version number") >= 0) {
message = "http协议错误服务端要求http协议请检查是否使用了https请求";
}
// 显示提示
uiContext.get().notification.error({ message });
if (notify) {
// 显示提示
uiContext.get().notification.error({ message });
}
}
/**
* @description 创建一个错误
* @param {String} msg 错误信息
*/
export function errorCreate(msg: string) {
export function errorCreate(msg: string, notify = true) {
const err = new Error(msg);
console.error("errorCreate", err);
uiContext.get().notification.error({ message: err.message });
if (notify) {
uiContext.get().notification.error({ message: err.message });
}
throw err;
}

View File

@@ -16,12 +16,15 @@
<div class="mt-5">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
</div>
<div class="helper">下次触发时间{{ nextTime }}</div>
<div class="fs-helper">{{ errorMessage }}</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import parser from "cron-parser";
import { computed, ref } from "vue";
import dayjs from "dayjs";
defineOptions({
name: "CronEditor"
});
@@ -31,8 +34,10 @@ const props = defineProps<{
readonly?: boolean;
}>();
const period = ref<string>("day");
const period = ref<string>("");
if (props.modelValue == null) {
period.value = "day";
}
const emit = defineEmits<{
"update:modelValue": any;
}>();
@@ -58,6 +63,17 @@ const onChange = (e: any) => {
const onError = (error: any) => {
errorMessage.value = error;
};
const nextTime = computed(() => {
try {
const interval = parser.parseExpression(props.modelValue);
const next = interval.next().getTime();
return dayjs(next).format("YYYY-MM-DD HH:mm:ss");
} catch (e) {
console.log(e);
return "请先设置正确的cron表达式";
}
});
</script>
<style lang="less">
.cron-editor {

View File

@@ -0,0 +1,50 @@
<template>
<div class="cd-fold-box">
<div class="handle pointer">
<div class="line"></div>
<div class="icon">
<fs-icon icon="ion:chevron-collapse-sharp"></fs-icon>
</div>
</div>
<div class="content" :class="{ hidden: !props.open }">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
open: boolean;
}>();
</script>
<style lang="less">
.cd-fold-box {
.handle {
height: 20px;
display: flex;
align-items: center;
.line {
//虚线
border-top: 1px dashed #ccc;
width: 100%;
height: 1px;
}
.icon {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
.content {
display: flex;
flex-direction: column;
&.hidden {
display: none;
}
}
}
</style>

View File

@@ -3,6 +3,7 @@ import TextEditable from "./editable.vue";
import vip from "./vip-button/install.js";
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
import CronEditor from "./cron-editor/index.vue";
import FoldBox from "./fold-box.vue";
import { CronLight } from "@vue-js-cron/light";
import "@vue-js-cron/light/dist/light.css";
import Plugins from "./plugins/index";
@@ -15,6 +16,8 @@ export default {
app.component("CronLight", CronLight);
app.component("CronEditor", CronEditor);
app.component("FoldBox", FoldBox);
app.component("CheckCircleOutlined", CheckCircleOutlined);
app.component("InfoCircleOutlined", InfoCircleOutlined);
app.component("UndoOutlined", UndoOutlined);

View File

@@ -28,9 +28,9 @@ export default {
});
}
options.value = array;
if (props.modelValue == null && options.value.length > 0) {
ctx.emit("update:modelValue", options.value[0].value);
}
// if (props.modelValue == null && options.value.length > 0) {
// ctx.emit("update:modelValue", options.value[0].value);
// }
}
onCreate();

View File

@@ -3,8 +3,10 @@ import { request } from "/src/api/service";
const apiPrefix = "/cname/record";
export type CnameRecord = {
id: number;
status: string;
id?: number;
status?: string;
hostRecord?: string;
recordValue?: string;
};
export async function GetList() {

View File

@@ -7,6 +7,7 @@
<td class="host-record" :title="'域名:' + props.domain">
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
</td>
<td style="text-align: center">CNAME</td>
<td class="record-value">
<fs-copyable v-model="cnameRecord.recordValue"></fs-copyable>
</td>
@@ -44,10 +45,12 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
change: {
id: number | null;
status: string | null;
};
change: [
{
id: number | null;
status: string | null;
}
];
}>();
const cnameRecord = ref<CnameRecord | null>(null);

View File

@@ -3,6 +3,7 @@
<thead>
<tr>
<td style="width: 160px">主机记录</td>
<td style="width: 100px; text-align: center">记录类型</td>
<td style="width: 250px">请设置CNAME记录验证成功以后不要删除</td>
<td style="width: 120px" class="center">状态</td>
<td style="width: 80px" class="center">操作</td>

View File

@@ -0,0 +1,62 @@
<template>
<div class="api-test">
<div>
<fs-button :loading="loading" type="primary" text="测试" icon="ion:refresh-outline" @click="doTest"></fs-button>
</div>
<div class="helper" :class="{ error: hasError }">
{{ message }}
</div>
</div>
</template>
<script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { ref } from "vue";
defineOptions({
name: "ApiTest"
});
const props = defineProps<{} & ComponentPropsType>();
const emit = defineEmits<{
"update:value": any;
}>();
const message = ref("");
const hasError = ref(false);
const loading = ref(false);
const doTest = async () => {
if (loading.value) {
return;
}
message.value = "";
hasError.value = false;
loading.value = true;
try {
const res = await doRequest(
{
type: props.type,
typeName: props.typeName,
action: props.action,
input: props.form
},
{
onError(err: any) {
hasError.value = true;
message.value = `错误:${err.message}`;
},
showErrorNotify: false
}
);
if (res && res.length > 0) {
message.value = "测试请求成功";
}
} finally {
loading.value = false;
}
};
</script>
<style lang="less"></style>

View File

@@ -1,6 +1,10 @@
<template>
<a-select mode="tags" readonly :value="modelValue" />
<div>{{ errorRef }}</div>
<div class="cert-domains-getter">
<div>
<a-tag v-for="item of modelValue" :key="item" type="success" class="m-3">{{ item }}</a-tag>
</div>
<div class="helper">{{ errorRef }}</div>
</div>
</template>
<script setup lang="ts">
@@ -34,17 +38,29 @@ function findStepFromPipeline(targetStepId: string) {
}
const errorRef = ref("");
function getDomainFromPipeline(inputKey: string) {
function getStepIdFromInputKey(inputKey: string) {
if (!inputKey) {
errorRef.value = "请先选择域名证书";
return;
}
const targetStepId = inputKey.split(".")[1];
const certStep = findStepFromPipeline(targetStepId);
return inputKey.split(".")[1];
}
function getDomainFromPipeline(inputKey: string) {
let targetStepId = getStepIdFromInputKey(inputKey);
let certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = "找不到目标步骤,请先选择域名证书";
return;
}
if (certStep.type !== "CertApply" && certStep.type !== "CertApplyLego") {
targetStepId = getStepIdFromInputKey(certStep.input?.cert);
certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = "找不到目标步骤,请先选择域名证书";
return;
}
}
const domain = certStep.input["domains"];
emit("update:modelValue", domain);
}

View File

@@ -0,0 +1,28 @@
<template>
<a-input class="cd-input-password" :class="{ show: showRef }">
<template #suffix>
<fs-icon class="pointer" :icon="computedIcon" @click="showRef = !showRef" />
</template>
</a-input>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
const showRef = ref(false);
const computedIcon = computed(() => {
return showRef.value ? "ion:eye-outline" : "ion:eye-off-outline";
});
</script>
<style lang="less">
.cd-input-password {
text-security: disc;
-webkit-text-security: disc;
.fs-iconify {
font-size: 16px;
}
&.show {
text-security: none;
-webkit-text-security: none;
}
}
</style>

View File

@@ -12,17 +12,18 @@
@update:value="emit('update:value', $event)"
/>
<div class="ml-5">
<fs-button title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div>
</div>
<div class="helper error">
<div class="helper" :class="{ error: hasError }">
{{ message }}
</div>
</div>
</template>
<script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { ref, useAttrs, watch } from "vue";
import { inject, ref, useAttrs, watch } from "vue";
import { PluginDefine } from "@certd/pipeline";
defineOptions({
name: "RemoteSelect"
@@ -40,41 +41,78 @@ const emit = defineEmits<{
const attrs = useAttrs();
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine");
const optionsRef = ref([]);
const message = ref("");
const hasError = ref(false);
const loading = ref(false);
const getOptions = async () => {
const res = await doRequest(
{
type: props.type,
typeName: props.typeName,
action: props.action,
input: props.form
},
{
onError(err: any) {
message.value = `获取选项出错:${err.message}`;
if (loading.value) {
return;
}
if (!getCurrentPluginDefine) {
return;
}
const define: PluginDefine = getCurrentPluginDefine()?.value;
if (!define) {
return;
}
for (let key in define.input) {
const inWatches = props.watches.includes(key);
const inputDefine = define.input[key];
if (inWatches && inputDefine.required) {
const value = props.form[key];
if (value == null || value === "") {
console.log("remote-select required", key);
return;
}
}
);
}
message.value = "";
return res;
hasError.value = false;
loading.value = true;
optionsRef.value = [];
try {
const res = await doRequest(
{
type: props.type,
typeName: props.typeName,
action: props.action,
input: props.form
},
{
onError(err: any) {
hasError.value = true;
message.value = `获取选项出错:${err.message}`;
},
showErrorNotify: false
}
);
if (res && res.length > 0) {
message.value = "获取数据成功,请从下拉框中选择";
}
optionsRef.value = res;
return res;
} finally {
loading.value = false;
}
};
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 || String(option.value).toLowerCase().indexOf(input.toLowerCase());
};
let isFirst = true;
async function onClick() {
if (!isFirst) {
return;
if (optionsRef.value?.length === 0) {
await refreshOptions();
}
isFirst = false;
await refreshOptions();
}
async function refreshOptions() {
optionsRef.value = await getOptions();
await getOptions();
}
watch(
@@ -89,7 +127,10 @@ watch(
};
},
async () => {
optionsRef.value = await getOptions();
await getOptions();
},
{
immediate: true
}
);
</script>

View File

@@ -5,6 +5,8 @@ import OutputSelector from "/@/components/plugins/common/output-selector/index.v
import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue";
import DomainsVerifyPlanEditor from "/@/components/plugins/cert/domains-verify-plan-editor/index.vue";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import InputPassword from "./common/input-password.vue";
import ApiTest from "./common/api-test.vue";
export * from "./cert/index.js";
export default {
install(app: any) {
@@ -12,9 +14,11 @@ export default {
app.component("DnsProviderSelector", DnsProviderSelector);
app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor);
app.component("AccessSelector", AccessSelector);
app.component("ApiTest", ApiTest);
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
app.component("RemoteSelect", RemoteSelect);
app.component("CertDomainsGetter", CertDomainsGetter);
app.component("InputPassword", InputPassword);
}
};

View File

@@ -199,7 +199,7 @@ const steps = ref<Step[]>([
{
image: "/static/doc/images/15-1-email.png",
title: "设置邮件通知",
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(免费版需要配置邮件服务器)"]
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"]
},
{
title: "教程结束",

View File

@@ -59,7 +59,7 @@ const text = computed<Text>(() => {
title: "此为专业版功能"
},
nav: {
name: "免费版",
name: "基础版",
title: "升级专业版享受更多VIP特权"
}
}
@@ -93,7 +93,7 @@ const formState = reactive({
const vipTypeDefine = {
free: {
title: "免费版",
title: "基础版",
type: "free",
privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"]
},

View File

@@ -0,0 +1,75 @@
import { describe, expect, it } from "vitest";
import { isDomain, isFilePath } from "/@/plugin/validator";
describe("domain_validator", () => {
it("ok", () => {
const value = ["a.cc.com", "*.zz.com", "a.cc.com"];
const v = isDomain({}, value);
expect(v).to.be.true;
});
it("allowDotStart", () => {
let value = ["&.cc.com"];
function test() {
return isDomain({ allowDotStart: true }, value);
}
expect(test).to.throw(Error, "域名有误:&.cc.com请输入正确的域名");
value = ["a,cc.com"];
expect(test).to.throw(Error, "域名有误a,cc.com请输入正确的域名");
value = ["&cc.com"];
expect(test).to.throw(Error, "域名有误:&cc.com请输入正确的域名");
value = [".cc.com"];
expect(test()).to.be.true;
});
it("default", () => {
let value = ["&.cc.com"];
function test() {
return isDomain({ allowDotStart: false }, value);
}
expect(test).to.throw(Error, "域名有误:&.cc.com请输入正确的域名");
value = ["&cc.com"];
expect(test).to.throw(Error, "域名有误:&cc.com请输入正确的域名");
value = ["a,cc.com"];
expect(test).to.throw(Error, "域名有误a,cc.com请输入正确的域名");
value = [".cc.com"];
expect(test).to.throw(Error, "域名有误:.cc.com请输入正确的域名");
});
it("isFilePath", () => {
let value = "/a/$/bc";
function test() {
return isFilePath({}, value);
}
expect(test()).to.be.true;
value = "/a/&/bc";
expect(test()).to.be.true;
//*?“<>|等特殊字符
value = "/a/&/b>c.txt";
const errorMessage = '文件名不能包含*?"<>|等特殊字符';
expect(test).to.throw(Error, errorMessage);
value = "/a/&/b<c.txt";
expect(test).to.throw(Error, errorMessage);
value = "/a/&/b|c.txt";
expect(test).to.throw(Error, errorMessage);
value = "/a/&/b?c.txt";
expect(test).to.throw(Error, errorMessage);
value = "/a/&/b*c.txt";
expect(test).to.throw(Error, errorMessage);
});
});

View File

@@ -1,6 +1,6 @@
import Validator from "async-validator";
// 自定义验证器函数
function isDomain(rule, value) {
export function isDomain(rule: any, value: any) {
if (value == null) {
return true;
}
@@ -8,13 +8,31 @@ function isDomain(rule, value) {
if (typeof value === "string") {
domains = value.split(",");
}
const allowDotStart = rule.allowDotStart ? "\\.|" : "";
const exp = `^(?:${allowDotStart}\\*\\.|[0-9a-zA-Z\u4e00-\u9fa5-]+\\.)+[0-9a-zA-Z\u4e00-\u9fa5-]+$`;
const compiled = new RegExp(exp);
for (const domain of domains) {
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和. ,可以_开头
if (!/^(?:\*\.|[0-9a-zA-Z\u4e00-\u9fa5-]+\.)+[0-9a-zA-Z\u4e00-\u9fa5-]+$/.test(domain)) {
if (!compiled.test(domain)) {
throw new Error(`域名有误:${domain},请输入正确的域名`);
}
}
return true;
}
// 注册自定义验证器
Validator.register("domains", isDomain);
export function isFilePath(rule: any, value: any) {
if (value == null) {
return true;
}
// 文件名不能用*?"<>|等特殊符号
if (!/^[^*?"<>|]*$/.test(value)) {
throw new Error(`文件名不能包含*?"<>|等特殊字符`);
}
return true;
}
Validator.register("filepath", isFilePath);

View File

@@ -1,7 +1,10 @@
export const headerResource = [
{
title: "文档",
path: "https://certd.docmirror.cn"
path: "https://certd.docmirror.cn",
meta: {
icon: "ion:document-text-outline"
},
},
{
title: "源码",

View File

@@ -47,7 +47,7 @@ export const sysResources = [
title: "邮箱设置",
name: "EmailSetting",
path: "/sys/settings/email",
component: "/sys/settings/email-setting.vue",
component: "/sys/settings/email/index.vue",
meta: {
permission: "sys:settings:view",
icon: "ion:mail-outline",
@@ -68,6 +68,20 @@ export const sysResources = [
permission: "sys:settings:view"
}
},
// {
// title: "顶部菜单设置",
// name: "HeaderMenus",
// path: "/sys/settings/header-menus",
// component: "/sys/settings/header-menus/index.vue",
// meta: {
// show: () => {
// const settingStore = useSettingStore();
// return settingStore.isComm;
// },
// icon: "ion:document-text-outline",
// permission: "sys:settings:view"
// }
// },
{
title: "系统级授权",
name: "SysAccess",

View File

@@ -5,7 +5,7 @@ import _ from "lodash-es";
import { LocalStorage } from "/src/utils/util.storage";
import * as basicApi from "/@/api/modules/api.basic";
import { PlusInfo, SiteEnv, SiteInfo, SysInstallInfo, SysPublicSetting } from "/@/api/modules/api.basic";
import { HeaderMenus, PlusInfo, SiteEnv, SiteInfo, 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";
@@ -36,6 +36,7 @@ export interface SettingState {
siteInfo: SiteInfo;
plusInfo?: PlusInfo;
siteEnv?: SiteEnv;
headerMenus?: HeaderMenus;
inited?: boolean;
}
@@ -85,6 +86,9 @@ export const useSettingStore = defineStore({
contactLink: ""
}
},
headerMenus: {
menus: []
},
inited: false
}),
getters: {
@@ -111,7 +115,7 @@ export const useSettingStore = defineStore({
},
vipLabel(): string {
const vipLabelMap: any = {
free: "免费版",
free: "基础版",
plus: "专业版",
comm: "商业版"
};

View File

@@ -26,3 +26,6 @@
padding-inline:revert;
}
.fs-form-wrapper .fs-form-header {
padding-right: 30px;
}

View File

@@ -78,12 +78,20 @@ h1, h2, h3, h4, h5, h6 {
overflow-y: auto;
}
.m-2{
margin:2px
}
.m-3{
margin:3px
}
.mb-2 {
margin-bottom: 2px;
}
.mb-5 {
margin-bottom: 5px;
}
.ml-5 {
margin-left: 5px;
}
@@ -170,6 +178,9 @@ h1, h2, h3, h4, h5, h6 {
color: #1890ff;
}
.iconify{
//font-size: 16px;
}
.icon-box {
display: inline-flex;

View File

@@ -2,7 +2,7 @@
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -70,6 +70,33 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 300
}
},
from: {
title: "级别",
type: "dict-select",
dict: dict({
data: [
{ label: "系统", value: "sys" },
{ label: "用户", value: "user" }
]
}),
search: {
show: false
},
form: {
show: false
},
column: {
width: 100,
align: "center",
component: {
color: "auto"
},
order: 10
},
valueBuilder: ({ row, key, value }) => {
row[key] = row.userId > 0 ? "user" : "sys";
}
},
...commonColumnsDefine
}
}

View File

@@ -13,7 +13,8 @@ import { useSettingStore } from "/@/store/modules/settings";
import _ from "lodash-es";
import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue";
import { eachRunnable, eachStages } from "./utils";
import { eachStages } from "./utils";
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();

View File

@@ -20,7 +20,7 @@ import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
import PiCertdForm from "./certd-form/index.vue";
export default defineComponent({
name: "PipelineManager",
name: "PipelineManager1",
components: { PiCertdForm },
setup() {
const certdFormRef = ref();

View File

@@ -107,7 +107,7 @@
<script lang="tsx">
import { message, Modal } from "ant-design-vue";
import { computed, inject, Ref, ref, watch } from "vue";
import { computed, inject, Ref, ref, watch, provide } from "vue";
import _ from "lodash-es";
import { nanoid } from "nanoid";
import { CopyOutlined } from "@ant-design/icons-vue";
@@ -219,6 +219,9 @@ export default {
};
const currentPluginDefine = ref();
provide("getCurrentPluginDefine", () => {
return currentPluginDefine;
});
function getContext() {
return {

View File

@@ -8,7 +8,11 @@
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
</div>
</template>
<pre class="pi-task-view-logs" style="overflow: auto"><template v-for="(text, index) of item.logs" :key="index">{{ text }}</template></pre>
<div class="pi-task-view-logs" style="overflow: auto">
<template v-for="(logItem, index) of item.logs" :key="index">
<span :class="logItem.color"> {{ logItem.time }}</span> <span>{{ logItem.content }}</span>
</template>
</div>
</a-tab-pane>
</a-tabs>
</a-modal>
@@ -57,7 +61,20 @@ export default {
if (currentHistory?.value?.logs != null) {
node.logs = computed(() => {
if (currentHistory?.value?.logs && currentHistory.value?.logs[node.node.id] != null) {
return currentHistory.value?.logs[node.node.id];
const logs = currentHistory.value?.logs[node.node.id];
const list = [];
for (let log of logs) {
const index = log.indexOf("]", 27) + 1;
const time = log.substring(0, index);
const content = log.substring(index);
const color = time.includes("ERROR") ? "red" : time.includes("WARN") ? "yellow" : "green";
list.push({
time,
content,
color
});
}
return list;
}
return [];
});
@@ -92,6 +109,7 @@ export default {
.pi-task-view {
.tab-title {
display: flex;
.tab-title-text {
display: inline-block;
width: 180px;
@@ -104,11 +122,26 @@ export default {
.pi-task-view-logs {
background-color: #000c17;
color: #fafafa;
color: #e9e9e9;
font-family: monospace;
padding: 5px;
min-height: 300px;
max-height: 580px;
white-space: pre-wrap;
word-wrap: break-word;
> div {
padding: 0;
margin: 0;
}
.green {
color: rgba(0, 255, 0, 0.8);
}
.yellow {
color: yellow;
}
.red {
color: red;
}
}
}
</style>

View File

@@ -101,8 +101,9 @@
<fs-icon
v-if="!editMode"
class="pointer color-blue ml-2"
title="完全重新运行此步骤"
icon="SyncOutlined"
style="font-size: 16px"
title="强制重新执行此步骤"
icon="icon-park-outline:replay-music"
@click="run(item.id)"
></fs-icon>
</div>
@@ -263,7 +264,7 @@ import _ from "lodash-es";
import { message, Modal, notification } from "ant-design-vue";
import { nanoid } from "nanoid";
import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./type";
import type { Runnable } from "@certd/pipeline";
import type { Runnable, Stage } from "@certd/pipeline";
import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue";
import { FsIcon } from "@fast-crud/fast-crud";
import { useSettingStore } from "/@/store/modules/settings";
@@ -632,6 +633,12 @@ export default defineComponent({
}
pipeline.value.version++;
currentPipeline.value = pipeline.value;
//移除空阶段
_.remove(pipeline.value.stages, (item: Stage) => {
return item.tasks.length === 0;
});
await props.options.doSave(pipeline.value);
}
toggleEditMode(false);

View File

@@ -4,6 +4,7 @@ export type StatusEnumItem = {
color: string;
icon: string;
spin?: boolean;
iconSpin?: boolean;
};
export type StatusEnumType = {
[key: string]: StatusEnumItem;
@@ -34,13 +35,13 @@ const StatusEnum: StatusEnumType = {
label: "运行中",
color: "blue",
spin: true,
iconSpin: true,
icon: "ant-design:sync-outlined"
},
canceled: {
value: "canceled",
label: "已取消",
color: "yellow",
spin: true,
icon: "ant-design:minus-circle-twotone"
},
none: {

View File

@@ -19,7 +19,7 @@ import { createAccessApi } from "/@/views/certd/access/api";
export default defineComponent({
name: "SysAccessManager",
setup() {
const api = createAccessApi("/sys/access");
const api = createAccessApi("sys");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
// 页面打开后获取列表数据

View File

@@ -130,7 +130,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
component: {
name: "access-selector",
vModel: "modelValue",
from: "sys",
type: compute(({ form }) => {
return form.dnsProviderType;
})

View File

@@ -5,6 +5,7 @@
CNAME服务配置
<span class="sub">
此处配置的域名作为其他域名校验的代理当别的域名需要申请证书时通过CNAME映射到此域名上来验证所有权好处是任何域名都可以通过此方式申请证书也无需填写AccessSecret
<a href="https://certd.docmirror.cn/guide/feature/cname/" taget="_blank">CNAME功能原理及使用说明</a>
</span>
</div>
</template>

View File

@@ -11,9 +11,17 @@
<div class="helper">
<div>设置公共Google EAB授权给用户使用避免用户自己去翻墙获取Google EAB授权</div>
<div>
<a href="https://gitee.com/certd/certd/blob/v2/doc/google/google.md#21-%E7%9B%B4%E6%8E%A5%E8%8E%B7%E5%8F%96eab-%E6%8E%A8%E8%8D%90">
获取Google EAB授权方法
</a>
<a href="https://certd.docmirror.cn/guide/use/google/">获取Google EAB授权方法 </a>
</div>
</div>
</a-form-item>
<a-form-item label="公共ZeroSSL EAB授权" :name="['CertApply', 'sysSetting', 'input', 'zerosslCommonEabAccessId']">
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.zerosslCommonEabAccessId" type="eab" from="sys"></access-selector>
<div class="helper">
<div>设置公共ZeroSSL EAB授权给用户使用避免用户自己去翻墙获取Zero EAB授权注意ZeroSSL免费证书有数量限制</div>
<div>
<a href="https://app.zerossl.com/developer">zerossl开发者中心获取EAB </a>
</div>
</div>
</a-form-item>

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