mirror of
https://github.com/certd/certd.git
synced 2026-04-11 02:40:55 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bef6b981e2 | ||
|
|
a77cd65789 | ||
|
|
415b731d9a | ||
|
|
6c0099d600 | ||
|
|
98b77f8084 | ||
|
|
2f47ffb76b | ||
|
|
35a3603c41 | ||
|
|
ea775adae1 | ||
|
|
724a85028b | ||
|
|
b2d595e85c | ||
|
|
d9b1ff8c5c | ||
|
|
1c17970b98 | ||
|
|
b9bddbfabb | ||
|
|
ee617095ef | ||
|
|
bee20c7f51 | ||
|
|
b8e05e9b44 | ||
|
|
869e14bad9 | ||
|
|
952e01ab7d | ||
|
|
db61033633 | ||
|
|
42a56b581d | ||
|
|
d6bb9f6af4 | ||
|
|
a430b27034 | ||
|
|
0f6679425f | ||
|
|
4b9d1eb4b5 | ||
|
|
ca4a1b8d92 | ||
|
|
08a702a758 | ||
|
|
589191244f | ||
|
|
f3ddcd3054 | ||
|
|
f923655d91 | ||
|
|
879e2609ca | ||
|
|
d227dd64e3 | ||
|
|
d2997624b0 |
10
.github/workflows/build-image.yml
vendored
10
.github/workflows/build-image.yml
vendored
@@ -58,8 +58,14 @@ jobs:
|
||||
username: ${{ secrets.aliyun_cs_username }}
|
||||
password: ${{ secrets.aliyun_cs_password }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.dockerhub_username }}
|
||||
password: ${{ secrets.dockerhub_password }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.5.0
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
@@ -67,3 +73,5 @@ jobs:
|
||||
tags: |
|
||||
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
|
||||
greper/certd:latest
|
||||
greper/certd:${{steps.get_certd_version.outputs.result}}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,3 +39,4 @@ tsconfig.tsbuildinfo
|
||||
test/**/*.js
|
||||
/packages/ui/certd-server/data/db.sqlite
|
||||
/packages/ui/certd-server/data/keys.yaml
|
||||
/packages/pro/
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 激活仅限管理员 ([1c17970](https://github.com/certd/certd/commit/1c17970b981f0987c506744ee6b2283fd5e40493))
|
||||
* 修复在没有勾选使用代理的情况下,仍然会使用代理的bug ([0f66794](https://github.com/certd/certd/commit/0f6679425f6a736bb0128527dd99c085fac17d84))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署插件支持宝塔、易盾云等 ([ee61709](https://github.com/certd/certd/commit/ee617095efa1171548cf52fd45f0f98a368555a3))
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/certd/certd/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
* 优化内存占用 ([db61033](https://github.com/certd/certd/commit/db6103363364440b650bc10bb334834e4a9470c7))
|
||||
* 支持阿里云 DCDN ([98b77f8](https://github.com/certd/certd/commit/98b77f80843834616fb26f83b4c42245326abd06))
|
||||
* 支持已跳过的步骤重新运行 ([ea775ad](https://github.com/certd/certd/commit/ea775adae18d57a04470cfba6b9460d761d74035))
|
||||
* 支持cdnfly ([724a850](https://github.com/certd/certd/commit/724a85028b4a7146c9e3b4df4497dcf2a7bf7c67))
|
||||
* 支持ftp上传 ([b9bddbf](https://github.com/certd/certd/commit/b9bddbfabb5664365f1232e9432532187c98006c))
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
29
README.md
29
README.md
@@ -10,10 +10,15 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的工具。
|
||||
https://afdian.com/a/greper
|
||||
|
||||
发电权益:
|
||||
1. 可加入发电专属群(先加我好友,发送发电截图,我拉你进群)
|
||||
2. 你的需求优先实现
|
||||
3. 可以获得作者一对一技术支持
|
||||
4. 更多权益陆续增加中...
|
||||
1. 可加入发电专属群,可以获得作者一对一技术支持
|
||||
2. 您的需求我们将优先实现,并且将作为专业版功能提供
|
||||
3. 一年期专业版激活码
|
||||
4. 赠送国外免费服务器部署方案(0成本使用Certd,不过该服务器需要翻墙)
|
||||
|
||||
专业版特权
|
||||
1. 证书流水线条数无限制(免费版限制10条)
|
||||
2. 免配置发邮件功能
|
||||
3. 更多功能增加中...
|
||||
************************
|
||||
|
||||
## 一、特性
|
||||
@@ -93,8 +98,11 @@ docker compose up -d
|
||||
> https://docs.docker.com/compose/install/linux/
|
||||
|
||||
#### 镜像说明:
|
||||
* certd镜像地址:
|
||||
* 国内镜像地址:
|
||||
* `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest`
|
||||
* DockerHub地址:
|
||||
* `https://hub.docker.com/r/greper/certd`
|
||||
* `docker pull greper/certd:latest`
|
||||
|
||||
* 镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
|
||||
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
||||
@@ -112,11 +120,16 @@ http://your_server_ip:7001
|
||||
## 五、 升级
|
||||
如果使用固定版本号
|
||||
1. 修改`docker-compose.yaml`中的镜像版本号
|
||||
2. 运行 `docker compose up -d` 即可
|
||||
2. 运行`docker compose up -d` 即可
|
||||
|
||||
如果使用`latest`版本
|
||||
1. 重新拉取镜像 `docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest`
|
||||
2. 重新启动容器 `docker compose restart`
|
||||
```shell
|
||||
#重新拉取镜像
|
||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||
# 重新启动容器
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
11:39
|
||||
1
|
||||
|
||||
14
docker/ftp/docker-compose.yaml
Normal file
14
docker/ftp/docker-compose.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
ftp:
|
||||
# 镜像 # ↓↓↓↓↓ --- 1、 镜像版本号,建议改成固定版本号【可选】
|
||||
image: gists/pure-ftpd
|
||||
container_name: ftp # 容器名
|
||||
restart: unless-stopped # 自动重启
|
||||
volumes:
|
||||
- /data/ftp2/:/home/ftpuser
|
||||
ports: # 端口映射
|
||||
- "21:21"
|
||||
- "30000-30009:30000-30009"
|
||||
environment: # 环境变量
|
||||
- TZ=Asia/Shanghai
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.24.0"
|
||||
"version": "1.24.1"
|
||||
}
|
||||
|
||||
1
packages/certd-pro
Submodule
1
packages/certd-pro
Submodule
Submodule packages/certd-pro added at 0dec0d461f
@@ -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.24.1](https://github.com/publishlab/node-acme-client/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复在没有勾选使用代理的情况下,仍然会使用代理的bug ([0f66794](https://github.com/publishlab/node-acme-client/commit/0f6679425f6a736bb0128527dd99c085fac17d84))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署插件支持宝塔、易盾云等 ([ee61709](https://github.com/publishlab/node-acme-client/commit/ee617095efa1171548cf52fd45f0f98a368555a3))
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/publishlab/node-acme-client/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
|
||||
# [1.24.0](https://github.com/publishlab/node-acme-client/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"main": "src/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -13,8 +13,12 @@ const defaultOpts = {
|
||||
termsOfServiceAgreed: false,
|
||||
skipChallengeVerification: false,
|
||||
challengePriority: ['http-01', 'dns-01'],
|
||||
challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); },
|
||||
challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); },
|
||||
challengeCreateFn: async () => {
|
||||
throw new Error('Missing challengeCreateFn()');
|
||||
},
|
||||
challengeRemoveFn: async () => {
|
||||
throw new Error('Missing challengeRemoveFn()');
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -209,6 +213,7 @@ module.exports = async (client, userOpts) => {
|
||||
}
|
||||
|
||||
log(`[auto] challengeGroups:${allChallengePromises.length}`);
|
||||
|
||||
function runAllPromise(tasks) {
|
||||
let promise = Promise.resolve();
|
||||
tasks.forEach((task) => {
|
||||
@@ -228,48 +233,48 @@ module.exports = async (client, userOpts) => {
|
||||
return Promise.all(results);
|
||||
}
|
||||
|
||||
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('用户取消');
|
||||
}
|
||||
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);
|
||||
}
|
||||
log('challenge结束');
|
||||
|
||||
// log('[auto] Waiting for challenge valid status');
|
||||
// await Promise.all(challengePromises);
|
||||
|
||||
/**
|
||||
* Finalize order and download certificate
|
||||
*/
|
||||
|
||||
log('[auto] Finalizing order and downloading certificate');
|
||||
const finalized = await client.finalizeOrder(order, opts.csr);
|
||||
return await client.getCertificate(finalized, opts.preferredChain);
|
||||
}
|
||||
catch (e) {
|
||||
log('证书申请失败');
|
||||
log(e);
|
||||
throw new Error(`证书申请失败:${e.message}`);
|
||||
}
|
||||
finally {
|
||||
log(`清理challenge痕迹,length:${clearTasks.length}`);
|
||||
try {
|
||||
await runAllPromise(clearTasks);
|
||||
}
|
||||
catch (e) {
|
||||
log('清理challenge失败');
|
||||
log(e);
|
||||
log(`证书申请失败${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
log(`清理challenge痕迹,length:${clearTasks.length}`);
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await runAllPromise(clearTasks);
|
||||
}
|
||||
catch (e) {
|
||||
log('清理challenge失败');
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
log('challenge结束');
|
||||
|
||||
// log('[auto] Waiting for challenge valid status');
|
||||
// await Promise.all(challengePromises);
|
||||
/**
|
||||
* Finalize order and download certificate
|
||||
*/
|
||||
|
||||
log('[auto] Finalizing order and downloading certificate');
|
||||
const finalized = await client.finalizeOrder(order, opts.csr);
|
||||
const res = await client.getCertificate(finalized, opts.preferredChain);
|
||||
return res;
|
||||
// try {
|
||||
// await Promise.allSettled(challengePromises);
|
||||
// }
|
||||
|
||||
@@ -290,7 +290,6 @@ exports.readCsrDomains = (csrPem) => {
|
||||
if (Buffer.isBuffer(csrPem)) {
|
||||
csrPem = csrPem.toString();
|
||||
}
|
||||
|
||||
const dec = x509.PemConverter.decodeFirst(csrPem);
|
||||
const csr = new x509.Pkcs10CertificateRequest(dec);
|
||||
return parseDomains(csr);
|
||||
|
||||
@@ -55,7 +55,7 @@ class HttpClient {
|
||||
*/
|
||||
|
||||
async request(url, method, opts = {}) {
|
||||
if (this.urlMapping && this.urlMapping.mappings) {
|
||||
if (this.urlMapping && this.urlMapping.enabled && this.urlMapping.mappings) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key in this.urlMapping.mappings) {
|
||||
if (url.includes(key)) {
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署插件支持宝塔、易盾云等 ([ee61709](https://github.com/certd/certd/commit/ee617095efa1171548cf52fd45f0f98a368555a3))
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/certd/certd/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
* 支持阿里云 DCDN ([98b77f8](https://github.com/certd/certd/commit/98b77f80843834616fb26f83b4c42245326abd06))
|
||||
* 支持已跳过的步骤重新运行 ([ea775ad](https://github.com/certd/certd/commit/ea775adae18d57a04470cfba6b9460d761d74035))
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1 +1 @@
|
||||
14:26
|
||||
23:56
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -14,6 +14,7 @@
|
||||
"test": "mocha --loader=ts-node/esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/plus": "1.22.1",
|
||||
"axios": "^1.7.2",
|
||||
"fix-path": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FormItemProps } from "../dt/index.js";
|
||||
export type AccessInputDefine = FormItemProps & {
|
||||
title: string;
|
||||
required?: boolean;
|
||||
encrypt?: boolean;
|
||||
};
|
||||
export type AccessDefine = Registrable & {
|
||||
input?: {
|
||||
|
||||
@@ -36,6 +36,8 @@ export class Executor {
|
||||
options: ExecutorOptions;
|
||||
abort: AbortController = new AbortController();
|
||||
|
||||
_inited = false;
|
||||
|
||||
onChanged: (history: RunHistory) => Promise<void>;
|
||||
constructor(options: ExecutorOptions) {
|
||||
this.options = options;
|
||||
@@ -50,6 +52,10 @@ export class Executor {
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this._inited) {
|
||||
return;
|
||||
}
|
||||
this._inited = true;
|
||||
const lastRuntime = await this.pipelineContext.getObj(`lastRuntime`);
|
||||
this.lastRuntime = lastRuntime;
|
||||
this.lastStatusMap = new RunnableCollection(lastRuntime?.pipeline);
|
||||
@@ -77,9 +83,9 @@ export class Executor {
|
||||
await this.notification("turnToSuccess");
|
||||
}
|
||||
await this.notification("success");
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
await this.notification("error", e);
|
||||
this.logger.error("pipeline 执行失败", e);
|
||||
this.logger.error("pipeline 执行失败", e.stack);
|
||||
} finally {
|
||||
await this.pipelineContext.setObj("lastRuntime", this.runtime);
|
||||
this.logger.info(`pipeline.${this.pipeline.id} end`);
|
||||
@@ -315,4 +321,8 @@ export class Executor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearLastStatus(stepId: string) {
|
||||
this.lastStatusMap.clearById(stepId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { isPlus, verify } from "./license.js";
|
||||
import { equal } from "assert";
|
||||
describe("license", function () {
|
||||
it("#license", async function () {
|
||||
const req = {
|
||||
subjectId: "999",
|
||||
license: "",
|
||||
};
|
||||
const plus = isPlus();
|
||||
equal(plus, false);
|
||||
const res = await verify(req);
|
||||
equal(res, true);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +1,4 @@
|
||||
import { createVerify } from "node:crypto";
|
||||
import { logger } from "../utils/index.js";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
let SecreteKey =
|
||||
"LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQW9VWE1EWUhjdi82WFROWEZFSUI2RlpuR2FER0cwZnR5bTV1dVhPck9NaVl0UkxSb1lvSGMKNVZxenE0N00rdEFqRFBhaTBlOFhWS1c3aytUQUw3MUs0N2JCQVEyWTBxNU5Ya3lYcE5PTVdueVFMYXBwb0tWNgpPMkFJMnpFVURWMVJVa0ZtMFZTVjU0VXNzMDcrdjI2aW5aQU1CWitDMU42eWFDc2tZL3grNnVlNkVRNVcyZXdFCjZOWEhJcUU1bHdEUmU2SXJtdEpnU2doSnlHTS91azIyejN6NGEraFVPVUlWMy9DbEhYV0VhRHBBRFFsakt3NSsKeHR0dURiTHZyUmdzdWp6czB0dEI2OE1SbXE0R0FJL0JtNWVPWkhlNGxFQjBFVVhFUXdVWE1jV1N1VFZSMUE2cApUM21LRGo5MGcwVDFZUlNOdE5TMm9aRzgvRWIwOVlxK3Z3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
|
||||
let appKey = "kQth6FHM71IPV3qdWc";
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
SecreteKey =
|
||||
"LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXY3TGtMaUp1dGM0NzhTU3RaTExjajVGZXh1YjJwR2NLMGxwa0hwVnlZWjhMY29rRFhuUlAKUGQ5UlJSTVRTaGJsbFl2Mzd4QUhOV1ZIQ0ZsWHkrQklVU001bUlBU1NDQTV0azlJNmpZZ2F4bEFDQm1BY0lGMwozKzBjeGZIYVkrVW9YdVluMkZ6YUt2Ym5GdFZIZ0lkMDg4a3d4clZTZzlCT3BDRVZIR1pxR2I5TWN5MXVHVXhUClFTVENCbmpoTWZlZ0p6cXVPYWVOY0ZPSE5tbmtWRWpLTythbTBPeEhNS1lyS3ZnQnVEbzdoVnFENlBFMUd6V3AKZHdwZUV4QXZDSVJxL2pWTkdRK3FtMkRWOVNJZ3U5bmF4MktmSUtFeU50dUFFS1VpekdqL0VmRFhDM1cxMExhegpKaGNYNGw1SUFZU1o3L3JWVmpGbExWSVl0WDU1T054L1Z3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
|
||||
appKey = "z4nXOeTeSnnpUpnmsV1";
|
||||
}
|
||||
|
||||
export const AppKey = appKey;
|
||||
|
||||
export type LicenseVerifyReq = {
|
||||
subjectId: string;
|
||||
license: string;
|
||||
};
|
||||
|
||||
type License = {
|
||||
appKey: string;
|
||||
code: string;
|
||||
subjectId: string;
|
||||
expireTime: number;
|
||||
activeTime: number;
|
||||
duration: number;
|
||||
version: number;
|
||||
secret: string;
|
||||
vipType: string;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
class LicenseHolder {
|
||||
isPlus = false;
|
||||
expireTime = 0;
|
||||
vipType = "";
|
||||
message?: string = undefined;
|
||||
secret?: string = undefined;
|
||||
}
|
||||
const holder = new LicenseHolder();
|
||||
holder.isPlus = false;
|
||||
|
||||
class LicenseVerifier {
|
||||
checked = false;
|
||||
licenseReq?: LicenseVerifyReq = undefined;
|
||||
async reVerify(req: LicenseVerifyReq) {
|
||||
this.checked = false;
|
||||
return await this.verify(req);
|
||||
}
|
||||
|
||||
setPlus(value: boolean, info: any = {}) {
|
||||
if (value && info) {
|
||||
holder.isPlus = true;
|
||||
holder.expireTime = info.expireTime;
|
||||
holder.secret = info.secret;
|
||||
holder.vipType = info.vipType;
|
||||
} else {
|
||||
holder.isPlus = false;
|
||||
holder.expireTime = 0;
|
||||
holder.vipType = "";
|
||||
holder.message = info.message;
|
||||
holder.secret = undefined;
|
||||
}
|
||||
return {
|
||||
...holder,
|
||||
};
|
||||
}
|
||||
async verify(req: LicenseVerifyReq) {
|
||||
this.licenseReq = req;
|
||||
if (this.checked) {
|
||||
return this.setPlus(false);
|
||||
}
|
||||
const license = req?.license;
|
||||
if (!license) {
|
||||
this.checked = true;
|
||||
return this.setPlus(false);
|
||||
}
|
||||
|
||||
const licenseJson = Buffer.from(license, "base64").toString();
|
||||
const json: License = JSON.parse(licenseJson);
|
||||
if (json.expireTime < Date.now()) {
|
||||
logger.warn("授权已过期");
|
||||
return this.setPlus(false, { message: "授权已过期" });
|
||||
}
|
||||
const content = `${appKey},${this.licenseReq.subjectId},${json.code},${json.secret},${json.vipType},${json.activeTime},${json.duration},${json.expireTime},${json.version}`;
|
||||
// content := fmt.Sprintf("%s,%s,%s,%s,%d,%d,%d,%d,%d", entity.AppKey, entity.SubjectId, entity.Code, entity.Secret, entity.Level, entity.ActiveTime, entity.Duration, entity.ExpireTime, entity.Version)
|
||||
//z4nXOeTeSnnpUpnmsV,_m9jFTdNHktdaEN4xBDw_,HZz7rAAR3h3zGlDMhScO1wGBYPjXpZ9S_1,uUpr9I8p6K3jWSzu2Wh5NECvgG2FNynU,0,1724199847470,365,1787271324416,1
|
||||
logger.debug("content:", content);
|
||||
const publicKey = Buffer.from(SecreteKey, "base64").toString();
|
||||
const res = this.verifySignature(content, json.signature, publicKey);
|
||||
this.checked = true;
|
||||
if (!res) {
|
||||
logger.warn("授权校验失败");
|
||||
return this.setPlus(false, { message: "授权校验失败" });
|
||||
}
|
||||
logger.info(`授权校验成功,到期时间:${dayjs(json.expireTime).format("YYYY-MM-DD HH:mm:ss")}`);
|
||||
return this.setPlus(true, {
|
||||
expireTime: json.expireTime,
|
||||
vipType: json.vipType || "plus",
|
||||
secret: json.secret,
|
||||
});
|
||||
}
|
||||
|
||||
verifySignature(content: string, signature: any, publicKey: string) {
|
||||
const verify = createVerify("RSA-SHA256");
|
||||
verify.update(content);
|
||||
return verify.verify(publicKey, signature, "base64");
|
||||
}
|
||||
}
|
||||
|
||||
const verifier = new LicenseVerifier();
|
||||
|
||||
export function isPlus() {
|
||||
return holder.isPlus && holder.expireTime > Date.now();
|
||||
}
|
||||
|
||||
export function isCommercial() {
|
||||
return holder.isPlus && holder.vipType === "comm" && holder.expireTime > Date.now();
|
||||
}
|
||||
|
||||
export function getPlusInfo() {
|
||||
return {
|
||||
isPlus: holder.isPlus,
|
||||
vipType: holder.vipType,
|
||||
expireTime: holder.expireTime,
|
||||
secret: holder.secret,
|
||||
};
|
||||
}
|
||||
|
||||
export async function verify(req: LicenseVerifyReq) {
|
||||
try {
|
||||
return await verifier.reVerify(req);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return verifier.setPlus(false, { message: "授权校验失败" });
|
||||
}
|
||||
}
|
||||
import { setLogger } from "@certd/plus";
|
||||
setLogger(logger);
|
||||
export * from "@certd/plus";
|
||||
|
||||
@@ -104,12 +104,13 @@ export class RunHistory {
|
||||
|
||||
log(runnable: Runnable, text: string) {
|
||||
// @ts-ignore
|
||||
this._loggers[runnable.id].info(`[${runnable.title}]<id:${runnable.id}> [${runnable.runnableType}]`, text);
|
||||
this._loggers[runnable.id].info(`[${runnable.runnableType}] [${runnable.title}]<id:${runnable.id}> :`, text);
|
||||
}
|
||||
|
||||
logError(runnable: Runnable, e: Error) {
|
||||
// @ts-ignore
|
||||
this._loggers[runnable.id].error(`[${runnable.title}]<id:${runnable.id}> [${runnable.runnableType}]`, e);
|
||||
const errorInfo = runnable.runnableType == "step" ? e.stack : e.message;
|
||||
this._loggers[runnable.id].error(`[${runnable.runnableType}] [${runnable.title}]<id:${runnable.id}> :${errorInfo}`);
|
||||
}
|
||||
|
||||
finally(runnable: Runnable) {
|
||||
@@ -166,6 +167,14 @@ export class RunnableCollection {
|
||||
});
|
||||
}
|
||||
|
||||
clearById(id: string) {
|
||||
const runnable = this.collection[id];
|
||||
if (runnable?.status) {
|
||||
runnable.status.status = ResultType.none;
|
||||
runnable.status.result = ResultType.none;
|
||||
}
|
||||
}
|
||||
|
||||
add(runnable: Runnable) {
|
||||
this.collection[runnable.id] = runnable;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ export type PluginDefine = Registrable & {
|
||||
dest: string;
|
||||
type: "computed";
|
||||
}[];
|
||||
|
||||
needPlus?: boolean;
|
||||
};
|
||||
|
||||
export type ITaskPlugin = {
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
import axios from "axios";
|
||||
// @ts-ignore
|
||||
import qs from "qs";
|
||||
import { logger } from "./util.log.js";
|
||||
import { Logger } from "log4js";
|
||||
|
||||
export class HttpError extends Error {
|
||||
request?: { url: string; method: string; data?: any };
|
||||
response?: { data: any };
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
constructor(error: any) {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
super(error.message);
|
||||
this.name = error.name;
|
||||
this.stack = error.stack;
|
||||
this.status = error?.response?.status;
|
||||
this.statusText = error?.response?.statusText;
|
||||
this.request = {
|
||||
url: error?.response?.config?.url,
|
||||
method: error?.response?.config?.method,
|
||||
data: error?.response?.config?.data,
|
||||
};
|
||||
this.response = {
|
||||
data: error?.response?.data,
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
@@ -12,13 +35,6 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||
// 请求拦截
|
||||
service.interceptors.request.use(
|
||||
(config: any) => {
|
||||
if (config.formData) {
|
||||
config.data = qs.stringify(config.formData, {
|
||||
arrayFormat: "indices",
|
||||
allowDots: true,
|
||||
}); // 序列化请求参数
|
||||
delete config.formData;
|
||||
}
|
||||
logger.info(`http request:${config.url},method:${config.method}`);
|
||||
return config;
|
||||
},
|
||||
@@ -50,26 +66,12 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||
// case 505: error.message = 'HTTP版本不受支持'; break
|
||||
// default: break
|
||||
// }
|
||||
logger.error(`请求出错:url:${error?.response?.config.url},method:${error?.response?.config?.method},status:${error?.response?.status}`);
|
||||
logger.info("返回数据:", JSON.stringify(error?.response?.data));
|
||||
delete error.config;
|
||||
const data = error?.response?.data;
|
||||
if (!data) {
|
||||
error.message = data.message || data.msg || data.error || data;
|
||||
}
|
||||
if (error?.response) {
|
||||
return Promise.reject({
|
||||
status: error?.response?.status,
|
||||
statusText: error?.response?.statusText,
|
||||
request: {
|
||||
url: error?.response?.config?.url,
|
||||
method: error?.response?.config?.method,
|
||||
data: error?.response?.data,
|
||||
},
|
||||
data: error?.response?.data,
|
||||
});
|
||||
}
|
||||
return Promise.reject(error);
|
||||
logger.error(
|
||||
`请求出错:status:${error?.response?.status},statusText:${error?.response?.statusText},url:${error?.config?.url},method:${error?.config?.method}。`
|
||||
);
|
||||
logger.error("返回数据:", JSON.stringify(error?.response?.data));
|
||||
const err = new HttpError(error);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
return service;
|
||||
|
||||
@@ -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.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.22.1](https://github.com/certd/certd/compare/v1.22.0...v1.22.1) (2024-07-20)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.22.1",
|
||||
"version": "1.24.1",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
|
||||
@@ -26,7 +26,7 @@ module.exports = {
|
||||
rootDir: "src",
|
||||
declaration: true,
|
||||
declarationDir: "dist/d",
|
||||
exclude: ["./node_modules/**", "./src/**/*.vue"],
|
||||
exclude: ["./node_modules/**", "./src/**/*.vue", "./src/**/*.spec.ts"],
|
||||
allowSyntheticDefaultImports: true,
|
||||
}),
|
||||
json(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
|
||||
@@ -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.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "^1.24.0",
|
||||
"@certd/pipeline": "^1.24.1",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -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.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/certd/certd/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -13,8 +13,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.24.0",
|
||||
"@certd/pipeline": "^1.24.0",
|
||||
"@certd/acme-client": "^1.24.1",
|
||||
"@certd/pipeline": "^1.24.1",
|
||||
"jszip": "^3.10.1",
|
||||
"node-forge": "^0.10.0",
|
||||
"psl": "^1.9.0"
|
||||
|
||||
@@ -13,6 +13,7 @@ export class EabAccess {
|
||||
},
|
||||
helper: "EAB KID",
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
kid = "";
|
||||
@AccessInput({
|
||||
@@ -22,6 +23,7 @@ export class EabAccess {
|
||||
},
|
||||
helper: "EAB HMAC Key",
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
hmacKey = "";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IContext } from "@certd/pipeline";
|
||||
import { IDnsProvider } from "../../dns-provider/index.js";
|
||||
import psl from "psl";
|
||||
import { ClientExternalAccountBindingOptions, UrlMapping } from "@certd/acme-client";
|
||||
|
||||
import { utils } from "@certd/pipeline";
|
||||
export type CertInfo = {
|
||||
crt: string;
|
||||
key: string;
|
||||
@@ -90,6 +90,13 @@ export class AcmeService {
|
||||
}
|
||||
if (this.options.useMappingProxy) {
|
||||
urlMapping.enabled = true;
|
||||
} else {
|
||||
//测试directory是否可以访问
|
||||
const isOk = await this.testDirectory(directoryUrl);
|
||||
if (!isOk) {
|
||||
this.logger.info("测试访问失败,自动使用代理");
|
||||
urlMapping.enabled = true;
|
||||
}
|
||||
}
|
||||
const client = new acme.Client({
|
||||
directoryUrl: directoryUrl,
|
||||
@@ -295,4 +302,19 @@ export class AcmeService {
|
||||
altNames,
|
||||
};
|
||||
}
|
||||
|
||||
private async testDirectory(directoryUrl: string) {
|
||||
try {
|
||||
await utils.http({
|
||||
url: directoryUrl,
|
||||
method: "GET",
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.error(`${directoryUrl},测试访问失败`, e);
|
||||
return false;
|
||||
}
|
||||
this.logger.info(`${directoryUrl},测试访问成功`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||
name: "pi-dns-provider-selector",
|
||||
},
|
||||
required: true,
|
||||
helper: "请选择dns解析提供商",
|
||||
helper:
|
||||
"请选择dns解析提供商,您的域名是在哪里注册的,或者域名的dns解析服务器属于哪个平台\n如果这里没有您的dns解析提供商,您可以将域名解析服务器设置成上面的任意一个提供商",
|
||||
})
|
||||
dnsProviderType!: string;
|
||||
|
||||
@@ -108,7 +109,6 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
},
|
||||
maybeNeed: true,
|
||||
helper: "如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项",
|
||||
})
|
||||
useProxy = false;
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
/* rules from the 'recommended' preset: */
|
||||
{
|
||||
name: 'no-circular',
|
||||
severity: 'warn',
|
||||
comment:
|
||||
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||
from: {},
|
||||
to: {
|
||||
circular: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-orphans',
|
||||
comment:
|
||||
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||
severity: 'warn',
|
||||
from: {
|
||||
orphan: true,
|
||||
pathNot: [
|
||||
'(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$', // dot files
|
||||
'\\.d\\.ts$', // TypeScript declaration files
|
||||
'(^|/)tsconfig\\.json$', // TypeScript config
|
||||
'(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$' // other configs
|
||||
]
|
||||
},
|
||||
to: {},
|
||||
},
|
||||
{
|
||||
name: 'no-deprecated-core',
|
||||
comment:
|
||||
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||
"bound to exist - node doesn't deprecate lightly.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'core'
|
||||
],
|
||||
path: [
|
||||
'^(v8\/tools\/codemap)$',
|
||||
'^(v8\/tools\/consarray)$',
|
||||
'^(v8\/tools\/csvparser)$',
|
||||
'^(v8\/tools\/logreader)$',
|
||||
'^(v8\/tools\/profile_view)$',
|
||||
'^(v8\/tools\/profile)$',
|
||||
'^(v8\/tools\/SourceMap)$',
|
||||
'^(v8\/tools\/splaytree)$',
|
||||
'^(v8\/tools\/tickprocessor-driver)$',
|
||||
'^(v8\/tools\/tickprocessor)$',
|
||||
'^(node-inspect\/lib\/_inspect)$',
|
||||
'^(node-inspect\/lib\/internal\/inspect_client)$',
|
||||
'^(node-inspect\/lib\/internal\/inspect_repl)$',
|
||||
'^(async_hooks)$',
|
||||
'^(punycode)$',
|
||||
'^(domain)$',
|
||||
'^(constants)$',
|
||||
'^(sys)$',
|
||||
'^(_linklist)$',
|
||||
'^(_stream_wrap)$'
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-deprecated',
|
||||
comment:
|
||||
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'deprecated'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-non-package-json',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
|
||||
"in your package.json.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-no-pkg',
|
||||
'npm-unknown'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-unresolvable',
|
||||
comment:
|
||||
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
couldNotResolve: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-duplicate-dep-types',
|
||||
comment:
|
||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||
"maintenance problems later on.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
moreThanOneDependencyType: true,
|
||||
// as it's pretty common to have a type import be a type only import
|
||||
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||
// types for this rule
|
||||
dependencyTypesNot: ["type-only"]
|
||||
}
|
||||
},
|
||||
|
||||
/* rules you might want to tweak for your specific situation: */
|
||||
{
|
||||
name: 'not-to-test',
|
||||
comment:
|
||||
"This module depends on code within a folder that should only contain tests. As tests don't " +
|
||||
"implement functionality this is odd. Either you're writing a test outside the test folder " +
|
||||
"or there's something in the test folder that isn't a test.",
|
||||
severity: 'error',
|
||||
from: {
|
||||
pathNot: '^(tests)'
|
||||
},
|
||||
to: {
|
||||
path: '^(tests)'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-spec',
|
||||
comment:
|
||||
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
||||
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
path: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-dev-dep',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||
'section of your package.json. If this module is development only - add it to the ' +
|
||||
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||
from: {
|
||||
path: '^(src)',
|
||||
pathNot: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$'
|
||||
},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-dev'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'optional-deps-used',
|
||||
severity: 'info',
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as an optional dependency " +
|
||||
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||
"If you're using an optional dependency here by design - add an exception to your" +
|
||||
"dependency-cruiser configuration.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-optional'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'peer-deps-used',
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as a peer dependency " +
|
||||
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||
"add an exception to your dependency-cruiser configuration.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-peer'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
options: {
|
||||
|
||||
/* conditions specifying which files not to follow further when encountered:
|
||||
- path: a regular expression to match
|
||||
- dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/master/doc/rules-reference.md#dependencytypes-and-dependencytypesnot
|
||||
for a complete list
|
||||
*/
|
||||
doNotFollow: {
|
||||
path: 'node_modules'
|
||||
},
|
||||
|
||||
/* conditions specifying which dependencies to exclude
|
||||
- path: a regular expression to match
|
||||
- dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies.
|
||||
leave out if you want to exclude neither (recommended!)
|
||||
*/
|
||||
// exclude : {
|
||||
// path: '',
|
||||
// dynamic: true
|
||||
// },
|
||||
|
||||
/* pattern specifying which files to include (regular expression)
|
||||
dependency-cruiser will skip everything not matching this pattern
|
||||
*/
|
||||
// includeOnly : '',
|
||||
|
||||
/* dependency-cruiser will include modules matching against the focus
|
||||
regular expression in its output, as well as their neighbours (direct
|
||||
dependencies and dependents)
|
||||
*/
|
||||
// focus : '',
|
||||
|
||||
/* list of module systems to cruise */
|
||||
// moduleSystems: ['amd', 'cjs', 'es6', 'tsd'],
|
||||
|
||||
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/develop/'
|
||||
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||
open it in visual studio code),
|
||||
*/
|
||||
// prefix: '',
|
||||
|
||||
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
|
||||
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||
"specify": for each dependency identify whether it only exists before compilation or also after
|
||||
*/
|
||||
tsPreCompilationDeps: true,
|
||||
|
||||
/*
|
||||
list of extensions to scan that aren't javascript or compile-to-javascript.
|
||||
Empty by default. Only put extensions in here that you want to take into
|
||||
account that are _not_ parsable.
|
||||
*/
|
||||
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
|
||||
|
||||
/* if true combines the package.jsons found from the module up to the base
|
||||
folder the cruise is initiated from. Useful for how (some) mono-repos
|
||||
manage dependencies & dependency definitions.
|
||||
*/
|
||||
// combinedDependencies: false,
|
||||
|
||||
/* if true leave symlinks untouched, otherwise use the realpath */
|
||||
// preserveSymlinks: false,
|
||||
|
||||
/* TypeScript project file ('tsconfig.json') to use for
|
||||
(1) compilation and
|
||||
(2) resolution (e.g. with the paths property)
|
||||
|
||||
The (optional) fileName attribute specifies which file to take (relative to
|
||||
dependency-cruiser's current working directory). When not provided
|
||||
defaults to './tsconfig.json'.
|
||||
*/
|
||||
tsConfig: {
|
||||
fileName: 'tsconfig.json'
|
||||
},
|
||||
|
||||
/* Webpack configuration to use to get resolve options from.
|
||||
|
||||
The (optional) fileName attribute specifies which file to take (relative
|
||||
to dependency-cruiser's current working directory. When not provided defaults
|
||||
to './webpack.conf.js'.
|
||||
|
||||
The (optional) `env` and `args` attributes contain the parameters to be passed if
|
||||
your webpack config is a function and takes them (see webpack documentation
|
||||
for details)
|
||||
*/
|
||||
// webpackConfig: {
|
||||
// fileName: './webpack.config.js',
|
||||
// env: {},
|
||||
// args: {},
|
||||
// },
|
||||
|
||||
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
|
||||
for compilation (and whatever other naughty things babel plugins do to
|
||||
source code). This feature is well tested and usable, but might change
|
||||
behavior a bit over time (e.g. more precise results for used module
|
||||
systems) without dependency-cruiser getting a major version bump.
|
||||
*/
|
||||
// babelConfig: {
|
||||
// fileName: './.babelrc'
|
||||
// },
|
||||
|
||||
/* List of strings you have in use in addition to cjs/ es6 requires
|
||||
& imports to declare module dependencies. Use this e.g. if you've
|
||||
re-declared require, use a require-wrapper or use window.require as
|
||||
a hack.
|
||||
*/
|
||||
// exoticRequireStrings: [],
|
||||
/* options to pass on to enhanced-resolve, the package dependency-cruiser
|
||||
uses to resolve module references to disk. You can set most of these
|
||||
options in a webpack.conf.js - this section is here for those
|
||||
projects that don't have a separate webpack config file.
|
||||
|
||||
Note: settings in webpack.conf.js override the ones specified here.
|
||||
*/
|
||||
enhancedResolveOptions: {
|
||||
/* List of strings to consider as 'exports' fields in package.json. Use
|
||||
['exports'] when you use packages that use such a field and your environment
|
||||
supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack).
|
||||
|
||||
If you have an `exportsFields` attribute in your webpack config, that one
|
||||
will have precedence over the one specified here.
|
||||
*/
|
||||
exportsFields: ["exports"],
|
||||
/* List of conditions to check for in the exports field. e.g. use ['imports']
|
||||
if you're only interested in exposed es6 modules, ['require'] for commonjs,
|
||||
or all conditions at once `(['import', 'require', 'node', 'default']`)
|
||||
if anything goes for you. Only works when the 'exportsFields' array is
|
||||
non-empty.
|
||||
|
||||
If you have a 'conditionNames' attribute in your webpack config, that one will
|
||||
have precedence over the one specified here.
|
||||
*/
|
||||
conditionNames: ["import", "require", "node", "default"],
|
||||
/*
|
||||
The extensions, by default are the same as the ones dependency-cruiser
|
||||
can access (run `npx depcruise --info` to see which ones that are in
|
||||
_your_ environment. If that list is larger than what you need (e.g.
|
||||
it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use
|
||||
TypeScript you can pass just the extensions you actually use (e.g.
|
||||
[".js", ".jsx"]). This can speed up the most expensive step in
|
||||
dependency cruising (module resolution) quite a bit.
|
||||
*/
|
||||
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
|
||||
/*
|
||||
If your TypeScript project makes use of types specified in 'types'
|
||||
fields in package.jsons of external dependencies, specify "types"
|
||||
in addition to "main" in here, so enhanced-resolve (the resolver
|
||||
dependency-cruiser uses) knows to also look there. You can also do
|
||||
this if you're not sure, but still use TypeScript. In a future version
|
||||
of dependency-cruiser this will likely become the default.
|
||||
*/
|
||||
mainFields: ["main", "types"],
|
||||
},
|
||||
reporterOptions: {
|
||||
dot: {
|
||||
/* pattern of modules that can be consolidated in the detailed
|
||||
graphical dependency graph. The default pattern in this configuration
|
||||
collapses everything in node_modules to one folder deep so you see
|
||||
the external modules, but not the innards your app depends upon.
|
||||
*/
|
||||
collapsePattern: 'node_modules/(@[^/]+/[^/]+|[^/]+)',
|
||||
|
||||
/* Options to tweak the appearance of your graph.See
|
||||
https://github.com/sverweij/dependency-cruiser/blob/master/doc/options-reference.md#reporteroptions
|
||||
for details and some examples. If you don't specify a theme
|
||||
don't worry - dependency-cruiser will fall back to the default one.
|
||||
*/
|
||||
// theme: {
|
||||
// graph: {
|
||||
// /* use splines: "ortho" for straight lines. Be aware though
|
||||
// graphviz might take a long time calculating ortho(gonal)
|
||||
// routings.
|
||||
// */
|
||||
// splines: "true"
|
||||
// },
|
||||
// modules: [
|
||||
// {
|
||||
// criteria: { matchesFocus: true },
|
||||
// attributes: {
|
||||
// fillcolor: "lime",
|
||||
// penwidth: 2,
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// criteria: { matchesFocus: false },
|
||||
// attributes: {
|
||||
// fillcolor: "lightgrey",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// criteria: { matchesReaches: true },
|
||||
// attributes: {
|
||||
// fillcolor: "lime",
|
||||
// penwidth: 2,
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// criteria: { matchesReaches: false },
|
||||
// attributes: {
|
||||
// fillcolor: "lightgrey",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// criteria: { source: "^src/model" },
|
||||
// attributes: { fillcolor: "#ccccff" }
|
||||
// },
|
||||
// {
|
||||
// criteria: { source: "^src/view" },
|
||||
// attributes: { fillcolor: "#ccffcc" }
|
||||
// },
|
||||
// ],
|
||||
// dependencies: [
|
||||
// {
|
||||
// criteria: { "rules[0].severity": "error" },
|
||||
// attributes: { fontcolor: "red", color: "red" }
|
||||
// },
|
||||
// {
|
||||
// criteria: { "rules[0].severity": "warn" },
|
||||
// attributes: { fontcolor: "orange", color: "orange" }
|
||||
// },
|
||||
// {
|
||||
// criteria: { "rules[0].severity": "info" },
|
||||
// attributes: { fontcolor: "blue", color: "blue" }
|
||||
// },
|
||||
// {
|
||||
// criteria: { resolved: "^src/model" },
|
||||
// attributes: { color: "#0000ff77" }
|
||||
// },
|
||||
// {
|
||||
// criteria: { resolved: "^src/view" },
|
||||
// attributes: { color: "#00770077" }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
archi: {
|
||||
/* pattern of modules that can be consolidated in the high level
|
||||
graphical dependency graph. If you use the high level graphical
|
||||
dependency graph reporter (`archi`) you probably want to tweak
|
||||
this collapsePattern to your situation.
|
||||
*/
|
||||
collapsePattern: '^(packages|src|lib|app|bin|test(s?)|spec(s?))/[^/]+|node_modules/(@[^/]+/[^/]+|[^/]+)',
|
||||
|
||||
/* Options to tweak the appearance of your graph.See
|
||||
https://github.com/sverweij/dependency-cruiser/blob/master/doc/options-reference.md#reporteroptions
|
||||
for details and some examples. If you don't specify a theme
|
||||
for 'archi' dependency-cruiser will use the one specified in the
|
||||
dot section (see above), if any, and otherwise use the default one.
|
||||
*/
|
||||
// theme: {
|
||||
// },
|
||||
},
|
||||
"text": {
|
||||
"highlightFocused": true
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
// generated: dependency-cruiser@12.11.0 on 2023-03-24T14:11:38.647Z
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 激活仅限管理员 ([1c17970](https://github.com/certd/certd/commit/1c17970b981f0987c506744ee6b2283fd5e40493))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/certd/certd/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
* 支持阿里云 DCDN ([98b77f8](https://github.com/certd/certd/commit/98b77f80843834616fb26f83b4c42245326abd06))
|
||||
* 支持已跳过的步骤重新运行 ([ea775ad](https://github.com/certd/certd/commit/ea775adae18d57a04470cfba6b9460d761d74035))
|
||||
* 支持cdnfly ([724a850](https://github.com/certd/certd/commit/724a85028b4a7146c9e3b4df4497dcf2a7bf7c67))
|
||||
* 支持ftp上传 ([b9bddbf](https://github.com/certd/certd/commit/b9bddbfabb5664365f1232e9432532187c98006c))
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -23,13 +23,14 @@
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.0.2",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@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",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||
"@vue-js-cron/light": "^4.0.5",
|
||||
"ant-design-vue": "^4.1.2",
|
||||
"axios": "^1.7.2",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
@@ -57,7 +58,7 @@
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "^1.24.0",
|
||||
"@certd/pipeline": "^1.24.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
@@ -104,7 +105,7 @@
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "5.4.2",
|
||||
"unplugin-vue-define-options": "^1.4.2",
|
||||
"vite": "^5.1.6",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-windicss": "^1.9.3",
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="cron-editor">
|
||||
<div class="flex-o">
|
||||
<cron-light
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:period="period"
|
||||
class="flex-o cron-ant"
|
||||
locale="zh-CN"
|
||||
format="quartz"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="onUpdate"
|
||||
@error="onError"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
|
||||
</div>
|
||||
<div class="fs-helper">{{ errorMessage }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
const props = defineProps<{
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
readonly?: boolean;
|
||||
}>();
|
||||
|
||||
const period = ref<string>("day");
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": any;
|
||||
}>();
|
||||
|
||||
const errorMessage = ref<string | null>(null);
|
||||
|
||||
const onUpdate = (value: string) => {
|
||||
if (value === props.modelValue) {
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", value);
|
||||
errorMessage.value = undefined;
|
||||
};
|
||||
|
||||
const onPeriod = (value: string) => {
|
||||
period.value = value;
|
||||
};
|
||||
|
||||
const onChange = (e: any) => {
|
||||
const value = e.target.value;
|
||||
onUpdate(value);
|
||||
};
|
||||
const onError = (error: any) => {
|
||||
errorMessage.value = error;
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.cron-editor {
|
||||
.cron-ant {
|
||||
flex-wrap: wrap;
|
||||
&* > {
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.vcron-select-list {
|
||||
min-width: 56px;
|
||||
}
|
||||
.vcron-select-input {
|
||||
min-height: 22px;
|
||||
}
|
||||
.vcron-select-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,9 @@ import PiOutputSelector from "../views/certd/pipeline/pipeline/component/output-
|
||||
import PiEditable from "./editable.vue";
|
||||
import VipButton from "./vip-button/index.vue";
|
||||
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
|
||||
import CronEditor from "./cron-editor/index.vue";
|
||||
import { CronLight } from "@vue-js-cron/light";
|
||||
import "@vue-js-cron/light/dist/light.css";
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.component("PiContainer", PiContainer);
|
||||
@@ -13,6 +16,8 @@ export default {
|
||||
app.component("PiOutputSelector", PiOutputSelector);
|
||||
app.component("PiDnsProviderSelector", PiDnsProviderSelector);
|
||||
app.component("VipButton", VipButton);
|
||||
app.component("CronLight", CronLight);
|
||||
app.component("CronEditor", CronEditor);
|
||||
|
||||
app.component("CheckCircleOutlined", CheckCircleOutlined);
|
||||
app.component("InfoCircleOutlined", InfoCircleOutlined);
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
<template>
|
||||
<div class="layout-vip isPlus">
|
||||
<div class="layout-vip isPlus" @click="openUpgrade">
|
||||
<contextHolder />
|
||||
<fs-icon icon="mingcute:vip-1-line"></fs-icon>
|
||||
<div class="text">
|
||||
<template v-if="userStore.isPlus">
|
||||
<a-tooltip>
|
||||
<template #title> 到期时间:{{ expireTime }} </template>
|
||||
<span @click="openUpgrade">{{ texts.plus }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tooltip>
|
||||
<template #title> 升级专业版,享受更多VIP特权 </template>
|
||||
<span @click="openUpgrade"> {{ texts.free }} {{ expiredDays }} </span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
|
||||
|
||||
<div v-if="mode !== 'icon'" class="text">
|
||||
<a-tooltip>
|
||||
<template #title> {{ text.title }}</template>
|
||||
<span>{{ text.name }}</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,24 +19,53 @@ import { message, Modal } from "ant-design-vue";
|
||||
import * as api from "./api";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
const props = defineProps<{
|
||||
mode?: "button" | "nav";
|
||||
}>();
|
||||
type Texts = {
|
||||
plus: string;
|
||||
free: string;
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
mode?: "button" | "nav" | "icon";
|
||||
}>(),
|
||||
{
|
||||
mode: "button"
|
||||
}
|
||||
);
|
||||
type Text = {
|
||||
name: string;
|
||||
title?: string;
|
||||
};
|
||||
const texts = computed<Texts>(() => {
|
||||
if (props.mode === "button") {
|
||||
return {
|
||||
plus: "专业版已开通",
|
||||
free: "此为专业版功能"
|
||||
};
|
||||
const text = computed<Text>(() => {
|
||||
const map = {
|
||||
isPlus: {
|
||||
button: {
|
||||
name: "专业版已开通",
|
||||
title: "到期时间:" + expireTime.value
|
||||
},
|
||||
icon: {
|
||||
name: "",
|
||||
title: "专业版已开通"
|
||||
},
|
||||
nav: {
|
||||
name: "专业版",
|
||||
title: "到期时间:" + expireTime.value
|
||||
}
|
||||
},
|
||||
free: {
|
||||
button: {
|
||||
name: "此为专业版功能",
|
||||
title: "升级专业版,享受更多VIP特权"
|
||||
},
|
||||
icon: {
|
||||
name: "",
|
||||
title: "此为专业版功能"
|
||||
},
|
||||
nav: {
|
||||
name: "免费版",
|
||||
title: "升级专业版,享受更多VIP特权"
|
||||
}
|
||||
}
|
||||
};
|
||||
if (userStore.isPlus) {
|
||||
return map.isPlus[props.mode];
|
||||
} else {
|
||||
return {
|
||||
plus: "专业版",
|
||||
free: "免费版"
|
||||
};
|
||||
return map.free[props.mode];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,10 +108,16 @@ async function doActive() {
|
||||
const settingStore = useSettingStore();
|
||||
const computedSiteId = computed(() => settingStore.installInfo?.siteId);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
function openUpgrade() {
|
||||
if (!userStore.isAdmin) {
|
||||
message.info("仅限管理员操作");
|
||||
return;
|
||||
}
|
||||
const placeholder = "请输入激活码";
|
||||
const isPlus = userStore.isPlus;
|
||||
modal.confirm({
|
||||
title: "升级/续期专业版",
|
||||
title: isPlus ? "续期专业版" : "激活专业版",
|
||||
async onOk() {
|
||||
return await doActive();
|
||||
},
|
||||
@@ -107,7 +135,8 @@ function openUpgrade() {
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="block-header">立刻激活/续期</h3>
|
||||
<h3 class="block-header">{isPlus ? "续期" : "立刻激活"}</h3>
|
||||
<div>{isPlus ? "当前专业版已激活,到期时间" + dayjs(userStore.plusInfo.expireTime).format("YYYY-MM-DD") : ""}</div>
|
||||
<div class="mt-10">
|
||||
<div class="flex-o w-100">
|
||||
<span>站点ID:</span>
|
||||
@@ -137,6 +166,7 @@ function openUpgrade() {
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
&.isPlus {
|
||||
color: #c5913f;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<MenuFoldOutlined v-else />
|
||||
</div>
|
||||
<fs-menu class="header-menu" mode="horizontal" :expand-selected="false" :selectable="false" :menus="frameworkMenus" />
|
||||
<vip-button class="flex-center header-btn"></vip-button>
|
||||
<vip-button class="flex-center header-btn" mode="nav" />
|
||||
</div>
|
||||
<div class="header-right header-buttons">
|
||||
<!-- <button-->
|
||||
|
||||
@@ -45,6 +45,10 @@ h1, h2, h3, h4, h5, h6 {
|
||||
vertical-align: 0 !important;
|
||||
}
|
||||
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flex-center{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -64,7 +68,9 @@ h1, h2, h3, h4, h5, h6 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.mb-2{
|
||||
margin-bottom:2px;
|
||||
}
|
||||
.ml-5{
|
||||
margin-left:5px;
|
||||
}
|
||||
@@ -84,6 +90,9 @@ h1, h2, h3, h4, h5, h6 {
|
||||
.mr-15{
|
||||
margin-right: 15px;
|
||||
}
|
||||
.mt-5{
|
||||
margin-top:5px;
|
||||
}
|
||||
.mt-10{
|
||||
margin-top:10px;
|
||||
}
|
||||
@@ -117,3 +126,8 @@ h1, h2, h3, h4, h5, h6 {
|
||||
padding-bottom:3px;
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
|
||||
|
||||
.color-blue{
|
||||
color: #1890ff;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any) {
|
||||
}
|
||||
};
|
||||
|
||||
function buildDefineFields(define: any) {
|
||||
function buildDefineFields(define: any, form: any) {
|
||||
const formWrapperRef = crudExpose.getFormWrapperRef();
|
||||
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
|
||||
|
||||
@@ -32,7 +32,12 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any) {
|
||||
...value,
|
||||
key
|
||||
};
|
||||
columnsRef.value[key] = _.merge({ title: key }, defaultPluginConfig, field);
|
||||
const column = _.merge({ title: key }, defaultPluginConfig, field);
|
||||
if (column.value != null && _.get(form, key) == null) {
|
||||
//设置默认值
|
||||
_.set(form, key, column.value);
|
||||
}
|
||||
columnsRef.value[key] = column;
|
||||
console.log("form", columnsRef.value);
|
||||
});
|
||||
}
|
||||
@@ -55,13 +60,16 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any) {
|
||||
rules: [{ required: true, message: "请选择类型" }],
|
||||
valueChange: {
|
||||
immediate: true,
|
||||
async handle({ value, mode, form }) {
|
||||
async handle({ value, mode, form, immediate }) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
const define = await api.GetProviderDefine(value);
|
||||
console.log("define", define);
|
||||
buildDefineFields(define);
|
||||
if (!immediate) {
|
||||
form.access = {};
|
||||
}
|
||||
buildDefineFields(define, form);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -59,11 +59,11 @@ export function Save(pipelineEntity: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export function Trigger(id: any) {
|
||||
export function Trigger(id: any, stepId?: string) {
|
||||
return request({
|
||||
url: apiPrefix + "/trigger",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id, stepId }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
|
||||
form: {
|
||||
wrapper: {
|
||||
width: "1150px",
|
||||
saveRemind: false
|
||||
saveRemind: false,
|
||||
title: "创建证书申请流水线"
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
@@ -73,6 +74,8 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
|
||||
type: "text",
|
||||
form: {
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *"
|
||||
},
|
||||
helper: "请输入cron表达式, 例如:0 0 4 * * *,每天凌晨4点触发",
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
|
||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||
import { nanoid } from "nanoid";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
@@ -29,9 +29,16 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
if (form.content == null) {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
} else {
|
||||
const content = JSON.parse(form.content);
|
||||
content.title = form.title;
|
||||
form.content = JSON.stringify(content);
|
||||
}
|
||||
|
||||
const res = await api.AddObj(form);
|
||||
lastResRef.value = res;
|
||||
return res;
|
||||
@@ -112,7 +119,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||
},
|
||||
addCertd: {
|
||||
order: 1,
|
||||
text: "添加证书流水线",
|
||||
text: "创建证书流水线",
|
||||
type: "primary",
|
||||
click() {
|
||||
addCertdPipeline();
|
||||
@@ -136,6 +143,18 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
click: async (context) => {
|
||||
const { ui } = useUi();
|
||||
// @ts-ignore
|
||||
const row = context[ui.tableColumn.row];
|
||||
row.title = row.title + "_copy";
|
||||
await crudExpose.openCopy({
|
||||
row: row,
|
||||
index: context.index
|
||||
});
|
||||
}
|
||||
},
|
||||
config: {
|
||||
order: 1,
|
||||
title: null,
|
||||
@@ -158,7 +177,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||
const files = await api.GetFiles(row.id);
|
||||
Modal.success({
|
||||
title: "文件下载",
|
||||
okText: "↑↑↑ 点击链接下载",
|
||||
okText: "↑↑↑ 点击上面链接下载",
|
||||
content: () => {
|
||||
const children = [];
|
||||
for (const file of files) {
|
||||
|
||||
@@ -54,9 +54,9 @@ export default defineComponent({
|
||||
content: JSON.stringify(pipelineConfig)
|
||||
});
|
||||
},
|
||||
async doTrigger(options: { pipelineId: number }) {
|
||||
const { pipelineId } = options;
|
||||
await api.Trigger(pipelineId);
|
||||
async doTrigger(options: { pipelineId: number; stepId?: string }) {
|
||||
const { pipelineId, stepId } = options;
|
||||
await api.Trigger(pipelineId, stepId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</template>
|
||||
<p>
|
||||
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
|
||||
<a-tag class="ml-1" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
|
||||
<a-tag class="ml-5" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
|
||||
{{ status.label }}
|
||||
</a-tag>
|
||||
<a-tag v-if="isCurrent" class="pointer" color="green" :closable="true" @close="cancel">当前</a-tag>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span v-if="statusRef" class="pi-status-show">
|
||||
<span v-if="statusRef" class="pi-status-show flex-o">
|
||||
<template v-if="type === 'icon'">
|
||||
<fs-icon class="status-icon" v-bind="statusRef" :style="{ color: statusRef.color }" />
|
||||
</template>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<template #title>
|
||||
<a-avatar :src="item.icon || '/images/plugin.png'" />
|
||||
<span class="title">{{ item.title }}</span>
|
||||
<vip-button v-if="item.needPlus" mode="icon" />
|
||||
</template>
|
||||
<template #description>
|
||||
<span :title="item.desc">{{ item.desc }}</span>
|
||||
@@ -81,6 +82,7 @@ import _ from "lodash-es";
|
||||
import { nanoid } from "nanoid";
|
||||
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||
import { PluginGroups } from "/@/views/certd/pipeline/pipeline/type";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
|
||||
export default {
|
||||
name: "PiStepForm",
|
||||
@@ -98,6 +100,7 @@ export default {
|
||||
* @returns
|
||||
*/
|
||||
function useStepForm() {
|
||||
const useStore = useUserStore();
|
||||
const getPluginGroups: any = inject("getPluginGroups");
|
||||
const pluginGroups: PluginGroups = getPluginGroups();
|
||||
const mode: Ref = ref("add");
|
||||
@@ -117,6 +120,10 @@ export default {
|
||||
});
|
||||
|
||||
const stepTypeSelected = (item: any) => {
|
||||
if (item.needPlus && !useStore.isPlus) {
|
||||
message.warn("此插件需要开通专业版才能使用");
|
||||
throw new Error("此插件需要开通专业版才能使用");
|
||||
}
|
||||
currentStep.value.type = item.name;
|
||||
currentStep.value.title = item.title;
|
||||
console.log("currentStepTypeChanged:", currentStep.value);
|
||||
@@ -128,6 +135,7 @@ export default {
|
||||
message.warn("请先选择类型");
|
||||
return;
|
||||
}
|
||||
|
||||
// 给step的input设置默认值
|
||||
changeCurrentPlugin(currentStep.value);
|
||||
|
||||
@@ -347,7 +355,7 @@ export default {
|
||||
overflow-y: hidden;
|
||||
|
||||
.ant-card-meta-description {
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
height: 40px;
|
||||
color: #7f7f7f;
|
||||
|
||||
@@ -15,13 +15,7 @@
|
||||
</template>
|
||||
<template v-if="currentTrigger">
|
||||
<pi-container>
|
||||
<a-form
|
||||
ref="triggerFormRef"
|
||||
class="trigger-form"
|
||||
:model="currentTrigger"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.title"
|
||||
:item="{
|
||||
@@ -59,8 +53,8 @@
|
||||
key: 'props.cron',
|
||||
component: {
|
||||
disabled: !editMode,
|
||||
name: 'a-input',
|
||||
vModel: 'value'
|
||||
name: 'cron-editor',
|
||||
vModel: 'modelValue'
|
||||
},
|
||||
helper: 'cron表达式,例如: 0 0 3 * * * ,表示每天凌晨3点触发',
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
|
||||
@@ -82,10 +82,20 @@
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
<span class="flex-o w-100">
|
||||
<span class="ellipsis flex-1" :class="{ 'mr-15': editMode }">{{ task.title }}</span>
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</span>
|
||||
<a-popover title="步骤">
|
||||
<!-- :open="true"-->
|
||||
<template #content>
|
||||
<div v-for="(item, index) of task.steps" class="flex-o w-100">
|
||||
<span class="ellipsis flex-1">{{ index + 1 }}. {{ item.title }} </span>
|
||||
<pi-status-show :status="item.status?.result"></pi-status-show>
|
||||
<fs-icon class="pointer color-blue" title="重新运行此步骤" icon="SyncOutlined" @click="run(item.id)"></fs-icon>
|
||||
</div>
|
||||
</template>
|
||||
<span class="flex-o w-100">
|
||||
<span class="ellipsis flex-1" :class="{ 'mr-15': editMode }">{{ task.title }}</span>
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</span>
|
||||
</a-popover>
|
||||
</a-button>
|
||||
<fs-icon v-if="editMode" class="copy" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
</div>
|
||||
@@ -226,10 +236,11 @@ import { nanoid } from "nanoid";
|
||||
import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./type";
|
||||
import type { Runnable } from "@certd/pipeline";
|
||||
import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue";
|
||||
import { FsIcon } from "@fast-crud/fast-crud";
|
||||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm },
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm },
|
||||
props: {
|
||||
pipelineId: {
|
||||
type: [Number, String],
|
||||
@@ -529,7 +540,7 @@ export default defineComponent({
|
||||
|
||||
function useActions() {
|
||||
const saveLoading = ref();
|
||||
const run = async () => {
|
||||
const run = async (stepId?: string) => {
|
||||
if (props.editMode) {
|
||||
message.warn("请先保存,再运行管道");
|
||||
return;
|
||||
@@ -549,11 +560,12 @@ export default defineComponent({
|
||||
//@ts-ignore
|
||||
await changeCurrentHistory(null);
|
||||
watchNewHistoryList();
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id });
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id, stepId: stepId });
|
||||
notification.success({ message: "管道已经开始运行" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function toggleEditMode(editMode: boolean) {
|
||||
ctx.emit("update:editMode", editMode);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export class PluginGroups {
|
||||
}
|
||||
|
||||
export type PipelineOptions = {
|
||||
doTrigger(options: { pipelineId: number }): Promise<void>;
|
||||
doTrigger(options: { pipelineId: number; stepId?: string }): Promise<void>;
|
||||
doSave(pipelineConfig: Pipeline): Promise<void>;
|
||||
getPipelineDetail(query: { pipelineId: number }): Promise<PipelineDetail>;
|
||||
getHistoryList(query: { pipelineId: number }): Promise<RunHistory[]>;
|
||||
|
||||
@@ -16,4 +16,4 @@ run/
|
||||
/data/db.sqlite
|
||||
*/node_modules
|
||||
certd-server/tools/windows/
|
||||
|
||||
.clinic
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
koa:
|
||||
port: 7001
|
||||
|
||||
#plus:
|
||||
# server:
|
||||
# baseUrl: 'http://127.0.0.1:11007'
|
||||
plus:
|
||||
server:
|
||||
baseUrl: 'http://127.0.0.1:11007'
|
||||
baseUrl: 'https://api.ai.handsfree.work'
|
||||
|
||||
1
packages/ui/certd-server/.gitignore
vendored
1
packages/ui/certd-server/.gitignore
vendored
@@ -16,3 +16,4 @@ run/
|
||||
/test/setup.js
|
||||
/test/setup.ts
|
||||
/data/
|
||||
.clinic
|
||||
|
||||
@@ -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.24.1](https://github.com/certd/certd/compare/v1.24.0...v1.24.1) (2024-09-02)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署插件支持宝塔、易盾云等 ([ee61709](https://github.com/certd/certd/commit/ee617095efa1171548cf52fd45f0f98a368555a3))
|
||||
* 授权配置支持加密 ([42a56b5](https://github.com/certd/certd/commit/42a56b581d754c3e5f9838179d19ab0d004ef2eb))
|
||||
* 优化内存占用 ([db61033](https://github.com/certd/certd/commit/db6103363364440b650bc10bb334834e4a9470c7))
|
||||
* 支持阿里云 DCDN ([98b77f8](https://github.com/certd/certd/commit/98b77f80843834616fb26f83b4c42245326abd06))
|
||||
* 支持已跳过的步骤重新运行 ([ea775ad](https://github.com/certd/certd/commit/ea775adae18d57a04470cfba6b9460d761d74035))
|
||||
* 支持cdnfly ([724a850](https://github.com/certd/certd/commit/724a85028b4a7146c9e3b4df4497dcf2a7bf7c67))
|
||||
* 支持ftp上传 ([b9bddbf](https://github.com/certd/certd/commit/b9bddbfabb5664365f1232e9432532187c98006c))
|
||||
|
||||
# [1.24.0](https://github.com/certd/certd/compare/v1.23.1...v1.24.0) (2024-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
alter table cd_access add "encrypt_setting" text;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table cd_access add COLUMN "encrypt_setting" text;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.24.0",
|
||||
"version": "1.24.1",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -15,19 +15,20 @@
|
||||
"ci": "npm run cov",
|
||||
"build": "mwtsc --cleanOutDir --skipLibCheck",
|
||||
"build-on-docker": "node ./before-build.js && npm run build",
|
||||
"up-mw-deps": "npx midway-version -u -w"
|
||||
"up-mw-deps": "npx midway-version -u -w",
|
||||
"clinic": "clinic heapprofiler -- node ./bootstrap.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/cs20151215": "^3.0.3",
|
||||
"@alicloud/openapi-client": "^0.4.0",
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/acme-client": "^1.24.0",
|
||||
"@certd/lib-huawei": "^1.22.1",
|
||||
"@certd/lib-k8s": "^1.24.0",
|
||||
"@certd/acme-client": "^1.24.1",
|
||||
"@certd/lib-huawei": "^1.24.1",
|
||||
"@certd/lib-k8s": "^1.24.1",
|
||||
"@certd/midway-flyway-js": "^1.22.6",
|
||||
"@certd/pipeline": "^1.24.0",
|
||||
"@certd/plugin-cert": "^1.24.0",
|
||||
"@koa/cors": "^3.4.3",
|
||||
"@certd/pipeline": "^1.24.1",
|
||||
"@certd/plugin-cert": "^1.24.1",
|
||||
"@certd/plugin-plus": "^1.24.1",
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@midwayjs/bootstrap": "^3.16.2",
|
||||
"@midwayjs/cache": "^3.14.0",
|
||||
"@midwayjs/core": "^3.16.2",
|
||||
@@ -39,6 +40,7 @@
|
||||
"@midwayjs/typeorm": "^3.16.4",
|
||||
"@midwayjs/validate": "^3.16.4",
|
||||
"axios": "^1.7.2",
|
||||
"basic-ftp": "^5.0.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"cache-manager": "^3.6.3",
|
||||
@@ -48,7 +50,7 @@
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"koa-send": "^5.0.1",
|
||||
"kubernetes-client": "^9.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -63,9 +65,7 @@
|
||||
"ssh2": "^1.15.0",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.44",
|
||||
"tencentcloud-sdk-nodejs-dnspod": "^4.0.866",
|
||||
"tencentcloud-sdk-nodejs-teo": "^4.0.919",
|
||||
"typeorm": "^0.3.11"
|
||||
"typeorm": "^0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@midwayjs/mock": "^3.16.4",
|
||||
|
||||
@@ -17,7 +17,7 @@ export abstract class BaseService<T> {
|
||||
|
||||
async transaction(callback: (entityManager: EntityManager) => Promise<any>) {
|
||||
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||
await dataSource.transaction(callback);
|
||||
await dataSource.transaction(callback as any);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,13 +12,11 @@ import { PipelineEntity } from '../modules/pipeline/entity/pipeline.js';
|
||||
//import { logger } from '../utils/logger';
|
||||
// load .env file in process.cwd
|
||||
import { mergeConfig } from './loader.js';
|
||||
import { Keys } from './keys.js';
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
const keys = Keys.load();
|
||||
const development = {
|
||||
keys: keys.cookieKeys,
|
||||
keys: 'certd',
|
||||
koa: {
|
||||
port: 7001,
|
||||
},
|
||||
@@ -78,7 +76,6 @@ const development = {
|
||||
|
||||
auth: {
|
||||
jwt: {
|
||||
secret: keys.jwtKey,
|
||||
expire: 7 * 24 * 60 * 60, //单位秒
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import * as _ from 'lodash-es';
|
||||
import { nanoid } from 'nanoid';
|
||||
import path from 'path';
|
||||
const KEYS_FILE = './data/keys.yaml';
|
||||
export class Keys {
|
||||
jwtKey: string = nanoid();
|
||||
cookieKeys: string[] = [nanoid()];
|
||||
|
||||
static load(): Keys {
|
||||
const keys = new Keys();
|
||||
if (fs.existsSync(KEYS_FILE)) {
|
||||
const content = fs.readFileSync(KEYS_FILE, 'utf8');
|
||||
const json = yaml.load(content);
|
||||
_.merge(keys, json);
|
||||
}
|
||||
keys.save();
|
||||
return keys;
|
||||
}
|
||||
|
||||
save() {
|
||||
const parent = path.dirname(KEYS_FILE);
|
||||
if (!fs.existsSync(parent)) {
|
||||
fs.mkdirSync(parent, {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
fs.writeFileSync(KEYS_FILE, yaml.dump(this));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,31 @@
|
||||
import { Config, Inject, MidwayWebRouterService, Provide } from '@midwayjs/core';
|
||||
import { Init, Inject, MidwayWebRouterService, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Constants } from '../basic/constants.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { AuthService } from '../modules/authority/service/auth-service.js';
|
||||
import { SysSettingsService } from '../modules/system/service/sys-settings-service.js';
|
||||
import { SysPrivateSettings } from '../modules/system/service/models.js';
|
||||
|
||||
/**
|
||||
* 权限校验
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class AuthorityMiddleware implements IWebMiddleware {
|
||||
@Config('auth.jwt.secret')
|
||||
private secret: string;
|
||||
@Inject()
|
||||
webRouterService: MidwayWebRouterService;
|
||||
@Inject()
|
||||
authService: AuthService;
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
secret: string;
|
||||
@Init()
|
||||
async init() {
|
||||
const setting: SysPrivateSettings = await this.sysSettingsService.getSetting(SysPrivateSettings);
|
||||
this.secret = setting.jwtKey;
|
||||
}
|
||||
|
||||
resolve() {
|
||||
return async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||
|
||||
@@ -49,7 +49,7 @@ export class PermissionController extends CrudController<PermissionService> {
|
||||
@Post('/delete', { summary: 'sys:auth:per:remove' })
|
||||
async delete(
|
||||
@Query('id')
|
||||
id
|
||||
id : number
|
||||
) {
|
||||
return await super.delete(id);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
ALL,
|
||||
Body,
|
||||
Controller,
|
||||
Inject,
|
||||
Post,
|
||||
Provide,
|
||||
Query,
|
||||
} from '@midwayjs/core';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { CrudController } from '../../../basic/crud-controller.js';
|
||||
import { RoleService } from '../service/role-service.js';
|
||||
|
||||
@@ -55,7 +47,7 @@ export class RoleController extends CrudController<RoleService> {
|
||||
@Post('/delete', { summary: 'sys:auth:role:remove' })
|
||||
async delete(
|
||||
@Query('id')
|
||||
id
|
||||
id: number
|
||||
) {
|
||||
return await super.delete(id);
|
||||
}
|
||||
@@ -63,7 +55,7 @@ export class RoleController extends CrudController<RoleService> {
|
||||
@Post('/getPermissionTree', { summary: 'sys:auth:role:view' })
|
||||
async getPermissionTree(
|
||||
@Query('id')
|
||||
id
|
||||
id: number
|
||||
) {
|
||||
const ret = await this.service.getPermissionTreeByRoleId(id);
|
||||
return this.ok(ret);
|
||||
@@ -72,7 +64,7 @@ export class RoleController extends CrudController<RoleService> {
|
||||
@Post('/getPermissionIds', { summary: 'sys:auth:role:view' })
|
||||
async getPermissionIds(
|
||||
@Query('id')
|
||||
id
|
||||
id: number
|
||||
) {
|
||||
const ret = await this.service.getPermissionIdsByRoleId(id);
|
||||
return this.ok(ret);
|
||||
|
||||
@@ -73,7 +73,7 @@ export class UserController extends CrudController<UserService> {
|
||||
@Post('/delete', { summary: 'sys:auth:user:remove' })
|
||||
async delete(
|
||||
@Query('id')
|
||||
id
|
||||
id : number
|
||||
) {
|
||||
return await super.delete(id);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import { PermissionService } from './permission-service.js';
|
||||
import { UserRoleService } from './user-role-service.js';
|
||||
import { Constants } from '../../../basic/constants.js';
|
||||
import { UserRoleEntity } from '../entity/user-role.js';
|
||||
import { randomText } from 'svg-captcha';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
|
||||
import { SysInstallInfo } from '../../system/service/models.js';
|
||||
import { RandomUtil } from '../../../utils/random.js';
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
@@ -64,7 +64,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
if (!_.isEmpty(exists)) {
|
||||
throw new CommonException('用户名已经存在');
|
||||
}
|
||||
const plainPassword = param.password ?? randomText(6);
|
||||
const plainPassword = param.password ?? RandomUtil.randomStr(6);
|
||||
param.passwordVersion = 2;
|
||||
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
|
||||
await super.add(param);
|
||||
@@ -156,7 +156,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
passwordVersion: 2,
|
||||
});
|
||||
if (!newUser.password) {
|
||||
newUser.password = randomText(6);
|
||||
newUser.password = RandomUtil.randomStr(6);
|
||||
}
|
||||
newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { UserService } from '../authority/service/user-service.js';
|
||||
import { SysSettingsService } from '../system/service/sys-settings-service.js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { SysInstallInfo, SysLicenseInfo } from '../system/service/models.js';
|
||||
import { SysInstallInfo, SysLicenseInfo, SysPrivateSettings } from '../system/service/models.js';
|
||||
import { verify } from '@certd/pipeline';
|
||||
|
||||
import crypto from 'crypto';
|
||||
export type InstallInfo = {
|
||||
installTime: number;
|
||||
instanceId?: string;
|
||||
@@ -17,12 +17,17 @@ export class AutoInitSite {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
@Config('typeorm.dataSource.default.type')
|
||||
dbType: string;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
logger.info('初始化站点开始');
|
||||
await this.startOptimizeDb();
|
||||
//安装信息
|
||||
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
if (!installInfo.siteId) {
|
||||
installInfo.siteId = nanoid();
|
||||
@@ -33,6 +38,19 @@ export class AutoInitSite {
|
||||
await this.sysSettingsService.saveSetting(installInfo);
|
||||
}
|
||||
|
||||
//private信息
|
||||
const privateInfo = await this.sysSettingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
|
||||
if (!privateInfo.jwtKey) {
|
||||
privateInfo.jwtKey = nanoid();
|
||||
await this.sysSettingsService.saveSetting(privateInfo);
|
||||
}
|
||||
|
||||
if (!privateInfo.encryptSecret) {
|
||||
const secretKey = crypto.randomBytes(32);
|
||||
privateInfo.encryptSecret = secretKey.toString('base64');
|
||||
await this.sysSettingsService.saveSetting(privateInfo);
|
||||
}
|
||||
|
||||
// 授权许可
|
||||
const licenseInfo: SysLicenseInfo = await this.sysSettingsService.getSetting(SysLicenseInfo);
|
||||
const req = {
|
||||
@@ -43,4 +61,25 @@ export class AutoInitSite {
|
||||
|
||||
logger.info('初始化站点完成');
|
||||
}
|
||||
|
||||
async startOptimizeDb() {
|
||||
//优化数据库
|
||||
//检查当前数据库类型为sqlite
|
||||
if (this.dbType === 'better-sqlite3') {
|
||||
const res = await this.userService.repository.query('PRAGMA auto_vacuum;');
|
||||
if (!(res && res.length > 0 && res[0].auto_vacuum > 0)) {
|
||||
//未开启自动优化
|
||||
await this.userService.repository.query('PRAGMA auto_vacuum = INCREMENTAL;');
|
||||
logger.info('sqlite数据库自动优化已开启');
|
||||
}
|
||||
|
||||
const optimizeDb = async () => {
|
||||
logger.info('sqlite数据库空间优化开始');
|
||||
await this.userService.repository.query('VACUUM');
|
||||
logger.info('sqlite数据库空间优化完成');
|
||||
};
|
||||
await optimizeDb();
|
||||
setInterval(optimizeDb, 1000 * 60 * 60 * 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import { CacheManager } from '@midwayjs/cache';
|
||||
import svgCaptcha from 'svg-captcha';
|
||||
|
||||
// {data: '<svg.../svg>', text: 'abcd'}
|
||||
/**
|
||||
@@ -14,6 +13,7 @@ export class CodeService {
|
||||
*/
|
||||
async generateCaptcha(randomStr) {
|
||||
console.assert(randomStr < 10, 'randomStr 过长');
|
||||
const svgCaptcha = await import('svg-captcha');
|
||||
const c = svgCaptcha.create();
|
||||
//{data: '<svg.../svg>', text: 'abcd'}
|
||||
const imgCode = c.text; // = RandomUtil.randomStr(4, true);
|
||||
|
||||
@@ -14,7 +14,7 @@ export class PlusService {
|
||||
@Config('plus.server.baseUrl')
|
||||
plusServerBaseUrl;
|
||||
|
||||
async requestWithoutSign(config: any) {
|
||||
async requestWithoutSign(config: any): Promise<any> {
|
||||
config.baseURL = this.plusServerBaseUrl;
|
||||
return await request(config);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import jwt from 'jsonwebtoken';
|
||||
import { CommonException } from '../../../basic/exception/common-exception.js';
|
||||
import { RoleService } from '../../authority/service/role-service.js';
|
||||
import { UserEntity } from '../../authority/entity/user.js';
|
||||
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
|
||||
import { SysPrivateSettings } from '../../system/service/models.js';
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
@@ -17,6 +19,9 @@ export class LoginService {
|
||||
@Config('auth.jwt')
|
||||
private jwt: any;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
/**
|
||||
* login
|
||||
*/
|
||||
@@ -47,7 +52,11 @@ export class LoginService {
|
||||
roles: roleIds,
|
||||
};
|
||||
const expire = this.jwt.expire;
|
||||
const token = jwt.sign(tokenInfo, this.jwt.secret, {
|
||||
|
||||
const setting = await this.sysSettingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
|
||||
const jwtSecret = setting.jwtKey;
|
||||
|
||||
const token = jwt.sign(tokenInfo, jwtSecret, {
|
||||
expiresIn: expire,
|
||||
});
|
||||
|
||||
|
||||
@@ -49,13 +49,13 @@ export class UserSettingsController extends CrudController<UserSettingsService>
|
||||
return super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: Constants.per.authOnly })
|
||||
async info(@Query('id') id) {
|
||||
async info(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id) {
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.delete(id);
|
||||
}
|
||||
|
||||
@@ -30,10 +30,7 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
};
|
||||
}
|
||||
|
||||
async getByKey(
|
||||
key: string,
|
||||
userId: number
|
||||
): Promise<UserSettingsEntity | null> {
|
||||
async getByKey(key: string, userId: number): Promise<UserSettingsEntity | null> {
|
||||
if (!key || !userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
ALL,
|
||||
Body,
|
||||
Controller,
|
||||
Inject,
|
||||
Post,
|
||||
Provide,
|
||||
Query,
|
||||
} from '@midwayjs/core';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { CrudController } from '../../../basic/crud-controller.js';
|
||||
import { AccessService } from '../service/access-service.js';
|
||||
import { Constants } from '../../../basic/constants.js';
|
||||
@@ -28,7 +20,7 @@ export class AccessController extends CrudController<AccessService> {
|
||||
async page(@Body(ALL) body) {
|
||||
body.query = body.query ?? {};
|
||||
body.query.userId = this.ctx.user.id;
|
||||
return super.page(body);
|
||||
return await super.page(body);
|
||||
}
|
||||
|
||||
@Post('/list', { summary: Constants.per.authOnly })
|
||||
@@ -49,19 +41,19 @@ export class AccessController extends CrudController<AccessService> {
|
||||
return super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: Constants.per.authOnly })
|
||||
async info(@Query('id') id) {
|
||||
async info(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id) {
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.delete(id);
|
||||
}
|
||||
|
||||
@Post('/define', { summary: Constants.per.authOnly })
|
||||
async define(@Query('type') type) {
|
||||
async define(@Query('type') type:string) {
|
||||
const access = this.service.getDefineByType(type);
|
||||
return this.ok(access);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class DnsProviderController extends BaseController {
|
||||
service: DnsProviderService;
|
||||
|
||||
@Post('/list', { summary: Constants.per.authOnly })
|
||||
async list(@Query(ALL) query) {
|
||||
async list(@Query(ALL) query:any) {
|
||||
query.userId = this.ctx.user.id;
|
||||
const list = this.service.getList();
|
||||
return this.ok(list);
|
||||
|
||||
@@ -99,14 +99,14 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id) {
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
await super.delete(id);
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/deleteByIds', { summary: Constants.per.authOnly })
|
||||
async deleteByIds(@Body(ALL) body) {
|
||||
async deleteByIds(@Body(ALL) body: any) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), body.ids);
|
||||
const isAdmin = await this.authService.isAdmin(this.ctx);
|
||||
const userId = isAdmin ? null : this.ctx.user.id;
|
||||
@@ -115,21 +115,21 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||
}
|
||||
|
||||
@Post('/detail', { summary: Constants.per.authOnly })
|
||||
async detail(@Query('id') id) {
|
||||
async detail(@Query('id') id: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
const detail = await this.service.detail(id);
|
||||
return this.ok(detail);
|
||||
}
|
||||
|
||||
@Post('/logs', { summary: Constants.per.authOnly })
|
||||
async logs(@Query('id') id) {
|
||||
async logs(@Query('id') id: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.logService, id);
|
||||
const logInfo = await this.logService.info(id);
|
||||
return this.ok(logInfo);
|
||||
}
|
||||
|
||||
@Post('/files', { summary: Constants.per.authOnly })
|
||||
async files(@Query('pipelineId') pipelineId, @Query('historyId') historyId) {
|
||||
async files(@Query('pipelineId') pipelineId: number, @Query('historyId') historyId: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
|
||||
const files = await this.getFiles(historyId, pipelineId);
|
||||
return this.ok(files);
|
||||
@@ -153,7 +153,7 @@ export class HistoryController extends CrudController<HistoryService> {
|
||||
}
|
||||
|
||||
@Get('/download', { summary: Constants.per.authOnly })
|
||||
async download(@Query('pipelineId') pipelineId, @Query('historyId') historyId, @Query('fileId') fileId) {
|
||||
async download(@Query('pipelineId') pipelineId: number, @Query('historyId') historyId: number, @Query('fileId') fileId: string) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
|
||||
const files = await this.getFiles(historyId, pipelineId);
|
||||
const file = files.find(f => f.id === fileId);
|
||||
|
||||
@@ -73,28 +73,28 @@ export class PipelineController extends CrudController<PipelineService> {
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id) {
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
await this.service.delete(id);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/detail', { summary: Constants.per.authOnly })
|
||||
async detail(@Query('id') id) {
|
||||
async detail(@Query('id') id: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
const detail = await this.service.detail(id);
|
||||
return this.ok(detail);
|
||||
}
|
||||
|
||||
@Post('/trigger', { summary: Constants.per.authOnly })
|
||||
async trigger(@Query('id') id) {
|
||||
async trigger(@Query('id') id: number, @Query('stepId') stepId?: string) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
await this.service.trigger(id);
|
||||
await this.service.trigger(id, stepId);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/cancel', { summary: Constants.per.authOnly })
|
||||
async cancel(@Query('historyId') historyId) {
|
||||
async cancel(@Query('historyId') historyId: number) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.historyService, historyId);
|
||||
await this.service.cancel(historyId);
|
||||
return this.ok({});
|
||||
|
||||
@@ -13,14 +13,14 @@ export class PluginController extends BaseController {
|
||||
service: PluginService;
|
||||
|
||||
@Post('/list', { summary: Constants.per.authOnly })
|
||||
async list(@Query(ALL) query) {
|
||||
async list(@Query(ALL) query: any) {
|
||||
query.userId = this.ctx.user.id;
|
||||
const list = this.service.getList();
|
||||
return this.ok(list);
|
||||
}
|
||||
|
||||
@Post('/groups', { summary: Constants.per.authOnly })
|
||||
async groups(@Query(ALL) query) {
|
||||
async groups(@Query(ALL) query: any) {
|
||||
query.userId = this.ctx.user.id;
|
||||
const group = this.service.getGroups();
|
||||
return this.ok(group);
|
||||
|
||||
@@ -15,9 +15,12 @@ export class AccessEntity {
|
||||
@Column({ comment: '类型', length: 100 })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'setting', comment: '设置', length: 1024, nullable: true })
|
||||
@Column({ name: 'setting', comment: '设置', length: 10240, nullable: true })
|
||||
setting: string;
|
||||
|
||||
@Column({ name: 'encrypt_setting', comment: '已加密设置', length: 10240, nullable: true })
|
||||
encryptSetting: string;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
|
||||
@@ -1,33 +1,128 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService } from '../../../basic/base-service.js';
|
||||
import { AccessEntity } from '../entity/access.js';
|
||||
import { accessRegistry, IAccessService } from '@certd/pipeline';
|
||||
import { AccessDefine, accessRegistry, IAccessService } from '@certd/pipeline';
|
||||
import { EncryptService } from './encrypt-service.js';
|
||||
import { ValidateException } from '../../../basic/exception/validation-exception.js';
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class AccessService
|
||||
extends BaseService<AccessEntity>
|
||||
implements IAccessService
|
||||
{
|
||||
export class AccessService extends BaseService<AccessEntity> implements IAccessService {
|
||||
@InjectEntityModel(AccessEntity)
|
||||
repository: Repository<AccessEntity>;
|
||||
|
||||
@Inject()
|
||||
encryptService: EncryptService;
|
||||
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async page(query, page = { offset: 0, limit: 20 }, order, buildQuery) {
|
||||
const res = await super.page(query, page, order, buildQuery);
|
||||
res.records = res.records.map(item => {
|
||||
delete item.encryptSetting;
|
||||
return item;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
async add(param) {
|
||||
this.encryptSetting(param, null);
|
||||
return await super.add(param);
|
||||
}
|
||||
|
||||
encryptSetting(param: any, oldSettingEntity?: AccessEntity) {
|
||||
const accessType = param.type;
|
||||
const accessDefine: AccessDefine = accessRegistry.getDefine(accessType);
|
||||
if (!accessDefine) {
|
||||
throw new ValidateException(`授权类型${accessType}不存在`);
|
||||
}
|
||||
const setting = param.setting;
|
||||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
const json = JSON.parse(setting);
|
||||
let oldSetting = {};
|
||||
let encryptSetting = {};
|
||||
const firstEncrypt = !oldSettingEntity || !oldSettingEntity.encryptSetting || oldSettingEntity.encryptSetting === '{}';
|
||||
if (oldSettingEntity) {
|
||||
oldSetting = JSON.parse(oldSettingEntity.setting || '{}');
|
||||
encryptSetting = JSON.parse(oldSettingEntity.encryptSetting || '{}');
|
||||
}
|
||||
for (const key in json) {
|
||||
//加密
|
||||
const value = json[key];
|
||||
const accessInputDefine = accessDefine.input[key];
|
||||
if (!accessInputDefine) {
|
||||
throw new ValidateException(`授权类型${accessType}不存在字段${key}`);
|
||||
}
|
||||
if (!accessInputDefine.encrypt || !value || typeof value !== 'string') {
|
||||
//定义无需加密、value为空、不是字符串 这些不需要加密
|
||||
encryptSetting[key] = {
|
||||
value: value,
|
||||
encrypt: false,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstEncrypt || oldSetting[key] !== value) {
|
||||
//星号保护
|
||||
const length = value.length;
|
||||
const subIndex = Math.min(2, length);
|
||||
let starLength = length - subIndex * 2;
|
||||
starLength = Math.max(2, starLength);
|
||||
const starString = '*'.repeat(starLength);
|
||||
json[key] = value.substring(0, subIndex) + starString + value.substring(value.length - subIndex);
|
||||
encryptSetting[key] = {
|
||||
value: this.encryptService.encrypt(value),
|
||||
encrypt: true,
|
||||
};
|
||||
}
|
||||
//未改变情况下,不做修改
|
||||
}
|
||||
param.encryptSetting = JSON.stringify(encryptSetting);
|
||||
param.setting = JSON.stringify(json);
|
||||
}
|
||||
/**
|
||||
* 修改
|
||||
* @param param 数据
|
||||
*/
|
||||
async update(param) {
|
||||
const oldEntity = await this.info(param.id);
|
||||
if (oldEntity == null) {
|
||||
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
|
||||
}
|
||||
this.encryptSetting(param, oldEntity);
|
||||
return await super.update(param);
|
||||
}
|
||||
|
||||
async getById(id: any): Promise<any> {
|
||||
const entity = await this.info(id);
|
||||
if (entity == null) {
|
||||
throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`);
|
||||
}
|
||||
// const access = accessRegistry.get(entity.type);
|
||||
const setting = JSON.parse(entity.setting);
|
||||
let setting = {};
|
||||
if (entity.encryptSetting && entity.encryptSetting !== '{}') {
|
||||
setting = JSON.parse(entity.encryptSetting);
|
||||
for (const key in setting) {
|
||||
//解密
|
||||
const encryptValue = setting[key];
|
||||
let value = encryptValue.value;
|
||||
if (encryptValue.encrypt) {
|
||||
value = this.encryptService.decrypt(value);
|
||||
}
|
||||
setting[key] = value;
|
||||
}
|
||||
} else if (entity.setting) {
|
||||
setting = JSON.parse(entity.setting);
|
||||
}
|
||||
return {
|
||||
id: entity.id,
|
||||
...setting,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { dnsProviderRegistry } from '@certd/plugin-cert';
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class DnsProviderService {
|
||||
getList() {
|
||||
return dnsProviderRegistry.getDefineList();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import crypto from 'crypto';
|
||||
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
|
||||
import { SysPrivateSettings } from '../../system/service/models.js';
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class EncryptService {
|
||||
secretKey: Buffer;
|
||||
|
||||
@Inject()
|
||||
sysSettingService: SysSettingsService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
const privateInfo: SysPrivateSettings = await this.sysSettingService.getSetting(SysPrivateSettings);
|
||||
this.secretKey = Buffer.from(privateInfo.encryptSecret, 'base64');
|
||||
}
|
||||
|
||||
// 加密函数
|
||||
encrypt(text: string) {
|
||||
const iv = crypto.randomBytes(16); // 初始化向量
|
||||
// const secretKey = crypto.randomBytes(32);
|
||||
// const key = Buffer.from(secretKey);
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv);
|
||||
let encrypted = cipher.update(text);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
||||
}
|
||||
|
||||
// 解密函数
|
||||
decrypt(encryptedText: string) {
|
||||
const textParts = encryptedText.split(':');
|
||||
const iv = Buffer.from(textParts.shift(), 'hex');
|
||||
const encrypted = Buffer.from(textParts.join(':'), 'hex');
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv);
|
||||
let decrypted = decipher.update(encrypted);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString();
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async detail(historyId: string) {
|
||||
async detail(historyId: number) {
|
||||
const entity = await this.info(historyId);
|
||||
const log = await this.logService.info(historyId);
|
||||
return new HistoryDetail(entity, log);
|
||||
|
||||
@@ -197,14 +197,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async trigger(id) {
|
||||
async trigger(id: any, stepId?: string) {
|
||||
this.cron.register({
|
||||
name: `pipeline.${id}.trigger.once`,
|
||||
cron: null,
|
||||
job: async () => {
|
||||
logger.info('用户手动启动job');
|
||||
try {
|
||||
await this.run(id, null);
|
||||
await this.run(id, null, stepId);
|
||||
} catch (e) {
|
||||
logger.error('手动job执行失败:', e);
|
||||
}
|
||||
@@ -279,7 +279,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
logger.info('当前定时器数量:', this.cron.getTaskSize());
|
||||
}
|
||||
|
||||
async run(id: number, triggerId: string) {
|
||||
async run(id: number, triggerId: string, stepId?: string) {
|
||||
const entity: PipelineEntity = await this.info(id);
|
||||
|
||||
const pipeline = JSON.parse(entity.content);
|
||||
@@ -333,6 +333,10 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
try {
|
||||
runningTasks.set(historyId, executor);
|
||||
await executor.init();
|
||||
if (stepId) {
|
||||
// 清除该step的状态
|
||||
executor.clearLastStatus(stepId);
|
||||
}
|
||||
await executor.run(historyId, triggerType);
|
||||
} catch (e) {
|
||||
logger.error('执行失败:', e);
|
||||
|
||||
@@ -45,13 +45,13 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
||||
return super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: 'sys:settings:view' })
|
||||
async info(@Query('id') id) {
|
||||
async info(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: 'sys:settings:edit' })
|
||||
async delete(@Query('id') id) {
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.ctx.user.id);
|
||||
return super.delete(id);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export class SysPrivateSettings extends BaseSettings {
|
||||
static __title__ = '系统私有设置';
|
||||
static __access__ = 'private';
|
||||
static __key__ = 'sys.private';
|
||||
jwtKey?: string;
|
||||
encryptSecret?: string;
|
||||
}
|
||||
|
||||
export class SysInstallInfo extends BaseSettings {
|
||||
|
||||
@@ -74,14 +74,15 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
async getSetting<T>(type: any): Promise<T> {
|
||||
const key = type.__key__;
|
||||
const cacheKey = type.getCacheKey();
|
||||
let settings: T = await this.cache.get(cacheKey);
|
||||
let settingInstance: T = new type();
|
||||
if (settings == null) {
|
||||
settings = await this.getSettingByKey(key);
|
||||
settingInstance = _.merge(settingInstance, settings);
|
||||
await this.cache.set(key, settingInstance);
|
||||
const settings: T = await this.cache.get(cacheKey);
|
||||
if (settings) {
|
||||
return settings;
|
||||
}
|
||||
return settingInstance;
|
||||
let newSetting: T = new type();
|
||||
const savedSettings = await this.getSettingByKey(key);
|
||||
newSetting = _.merge(newSetting, savedSettings);
|
||||
await this.cache.set(cacheKey, newSetting);
|
||||
return newSetting;
|
||||
}
|
||||
|
||||
async saveSetting<T extends BaseSettings>(bean: T) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from '@certd/plugin-cert';
|
||||
export * from '@certd/plugin-plus';
|
||||
export * from './plugin-aliyun/index.js';
|
||||
export * from './plugin-tencent/index.js';
|
||||
export * from './plugin-host/index.js';
|
||||
|
||||
@@ -11,7 +11,7 @@ export class AliyunAccess {
|
||||
component: {
|
||||
placeholder: 'accessKeyId',
|
||||
},
|
||||
helper: '注意:证书申请,需要dns解析权限;其他阿里云插件,也需要对应的权限,比如证书上传需要证书管理权限',
|
||||
helper: '登录阿里云控制台->AccessKey管理页面获取。',
|
||||
required: true,
|
||||
})
|
||||
accessKeyId = '';
|
||||
@@ -21,6 +21,8 @@ export class AliyunAccess {
|
||||
placeholder: 'accessKeySecret',
|
||||
},
|
||||
required: true,
|
||||
encrypt: true,
|
||||
helper: '注意:证书申请需要dns解析权限;其他阿里云插件,需要对应的权限,比如证书上传需要证书管理权限;嫌麻烦就用主账号的全量权限的accessKey',
|
||||
})
|
||||
accessKeySecret = '';
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import Core from '@alicloud/pop-core';
|
||||
import {
|
||||
AbstractDnsProvider,
|
||||
CreateRecordOptions,
|
||||
IsDnsProvider,
|
||||
RemoveRecordOptions,
|
||||
} from '@certd/plugin-cert';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { Autowire, ILogger } from '@certd/pipeline';
|
||||
import { AliyunAccess } from '../access/index.js';
|
||||
|
||||
@@ -22,7 +16,8 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
||||
logger!: ILogger;
|
||||
async onInstance() {
|
||||
const access: any = this.access;
|
||||
this.client = new Core({
|
||||
const Core = await import('@alicloud/pop-core');
|
||||
this.client = new Core.default({
|
||||
accessKeyId: access.accessKeyId,
|
||||
accessKeySecret: access.accessKeySecret,
|
||||
endpoint: 'https://alidns.aliyuncs.com',
|
||||
@@ -110,11 +105,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
||||
};
|
||||
|
||||
try {
|
||||
const ret = await this.client.request(
|
||||
'AddDomainRecord',
|
||||
params,
|
||||
requestOption
|
||||
);
|
||||
const ret = await this.client.request('AddDomainRecord', params, requestOption);
|
||||
this.logger.info('添加域名解析成功:', value, value, ret.RecordId);
|
||||
return ret.RecordId;
|
||||
} catch (e: any) {
|
||||
@@ -136,11 +127,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
||||
method: 'POST',
|
||||
};
|
||||
|
||||
const ret = await this.client.request(
|
||||
'DeleteDomainRecord',
|
||||
params,
|
||||
requestOption
|
||||
);
|
||||
const ret = await this.client.request('DeleteDomainRecord', params, requestOption);
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value, ret.RecordId);
|
||||
return ret.RecordId;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './access/index.js';
|
||||
export * from './dns-provider/index.js';
|
||||
export * from './plugin/index.js';
|
||||
export * from './access/index.js';
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
|
||||
// @ts-ignore
|
||||
import { ROAClient } from '@alicloud/pop-core';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
import { K8sClient } from '@certd/lib-k8s';
|
||||
import { appendTimeSuffix } from '../../utils/index.js';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
|
||||
@@ -91,6 +88,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
from: 'CertApply',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@@ -105,17 +103,20 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
async onInstance(): Promise<void> {}
|
||||
K8sClient: any;
|
||||
async onInstance() {
|
||||
const sdk = await import('@certd/lib-k8s');
|
||||
this.K8sClient = sdk.K8sClient;
|
||||
}
|
||||
async execute(): Promise<void> {
|
||||
console.log('开始部署证书到阿里云cdn');
|
||||
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = this;
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access, regionId);
|
||||
const client = await this.getClient(access, regionId);
|
||||
const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
|
||||
|
||||
this.logger.info('kubeconfig已成功获取');
|
||||
const k8sClient = new K8sClient({
|
||||
const k8sClient = new this.K8sClient({
|
||||
kubeConfigStr,
|
||||
logger: this.logger,
|
||||
});
|
||||
@@ -131,7 +132,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
||||
// await this.restartIngress({ k8sClient, props })
|
||||
}
|
||||
|
||||
async restartIngress(options: { k8sClient: K8sClient }) {
|
||||
async restartIngress(options: { k8sClient: any }) {
|
||||
const { k8sClient } = options;
|
||||
const { namespace } = this;
|
||||
|
||||
@@ -168,7 +169,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
async patchNginxCertSecret(options: { cert: CertInfo; k8sClient: K8sClient }) {
|
||||
async patchNginxCertSecret(options: { cert: CertInfo; k8sClient: any }) {
|
||||
const { cert, k8sClient } = options;
|
||||
const crt = cert.crt;
|
||||
const key = cert.key;
|
||||
@@ -198,8 +199,10 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
getClient(aliyunProvider: any, regionId: string) {
|
||||
return new ROAClient({
|
||||
async getClient(aliyunProvider: any, regionId: string) {
|
||||
const Core = await import('@alicloud/pop-core');
|
||||
|
||||
return new Core.default({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: `https://cs.${regionId}.aliyuncs.com`,
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import dayjs from 'dayjs';
|
||||
import Core from '@alicloud/pop-core';
|
||||
import RPCClient from '@alicloud/pop-core';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunCDN',
|
||||
title: '部署证书至阿里云CDN',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '依赖证书申请前置任务,自动部署域名证书至阿里云CDN',
|
||||
desc: '自动部署域名证书至阿里云CDN',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
@@ -34,6 +31,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
from: 'CertApply',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@@ -54,14 +52,16 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
async execute(): Promise<void> {
|
||||
console.log('开始部署证书到阿里云cdn');
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access);
|
||||
const client = await this.getClient(access);
|
||||
const params = await this.buildParams();
|
||||
await this.doRequest(client, params);
|
||||
console.log('部署完成');
|
||||
}
|
||||
|
||||
getClient(access: AliyunAccess) {
|
||||
return new Core({
|
||||
async getClient(access: AliyunAccess) {
|
||||
const Core = await import('@alicloud/pop-core');
|
||||
|
||||
return new Core.default({
|
||||
accessKeyId: access.accessKeyId,
|
||||
accessKeySecret: access.accessKeySecret,
|
||||
endpoint: 'https://cdn.aliyuncs.com',
|
||||
@@ -73,21 +73,20 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
const CertName = (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
|
||||
const cert: any = this.cert;
|
||||
return {
|
||||
RegionId: 'cn-hangzhou',
|
||||
DomainName: this.domainName,
|
||||
ServerCertificateStatus: 'on',
|
||||
SSLProtocol: 'on',
|
||||
CertName: CertName,
|
||||
CertType: 'upload',
|
||||
ServerCertificate: cert.crt,
|
||||
PrivateKey: cert.key,
|
||||
SSLPub: cert.crt,
|
||||
SSLPri: cert.key,
|
||||
};
|
||||
}
|
||||
|
||||
async doRequest(client: RPCClient, params: any) {
|
||||
async doRequest(client: any, params: any) {
|
||||
const requestOption = {
|
||||
method: 'POST',
|
||||
};
|
||||
const ret: any = await client.request('SetDomainServerCertificate', params, requestOption);
|
||||
const ret: any = await client.request('SetCdnDomainSSLCertificate', params, requestOption);
|
||||
this.checkRet(ret);
|
||||
this.logger.info('设置cdn证书成功:', ret.RequestId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import dayjs from 'dayjs';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunDCDN',
|
||||
title: '部署证书至阿里云DCDN',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '依赖证书申请前置任务,自动部署域名证书至阿里云DCDN',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: 'DCDN加速域名',
|
||||
helper: '你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn',
|
||||
required: true,
|
||||
})
|
||||
domainName!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: '证书名称',
|
||||
helper: '上传后将以此名称作为前缀备注',
|
||||
})
|
||||
certName!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
from: 'CertApply',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: 'Access授权',
|
||||
helper: '阿里云授权AccessKeyId、AccessKeySecret',
|
||||
component: {
|
||||
name: 'pi-access-selector',
|
||||
type: 'aliyun',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info('开始部署证书到阿里云DCDN');
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = await this.getClient(access);
|
||||
const params = await this.buildParams();
|
||||
await this.doRequest(client, params);
|
||||
this.logger.info('部署完成');
|
||||
}
|
||||
|
||||
async getClient(access: AliyunAccess) {
|
||||
const sdk = await import('@alicloud/pop-core');
|
||||
return new sdk.default({
|
||||
accessKeyId: access.accessKeyId,
|
||||
accessKeySecret: access.accessKeySecret,
|
||||
endpoint: 'https://dcdn.aliyuncs.com',
|
||||
apiVersion: '2018-01-15',
|
||||
});
|
||||
}
|
||||
|
||||
async buildParams() {
|
||||
const CertName = (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
|
||||
const cert: any = this.cert;
|
||||
return {
|
||||
DomainName: this.domainName,
|
||||
SSLProtocol: 'on',
|
||||
CertName: CertName,
|
||||
CertType: 'upload',
|
||||
SSLPub: cert.crt,
|
||||
SSLPri: cert.key,
|
||||
};
|
||||
}
|
||||
|
||||
async doRequest(client: any, params: any) {
|
||||
const requestOption = {
|
||||
method: 'POST',
|
||||
formatParams: false,
|
||||
};
|
||||
const ret: any = await client.request('SetDcdnDomainSSLCertificate', params, requestOption);
|
||||
this.checkRet(ret);
|
||||
this.logger.info('设置Dcdn证书成功:', ret.RequestId);
|
||||
}
|
||||
|
||||
checkRet(ret: any) {
|
||||
if (ret.code != null) {
|
||||
throw new Error('执行失败:' + ret.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
new DeployCertToAliyunDCDN();
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './deploy-to-cdn/index.js';
|
||||
export * from './deploy-to-dcdn/index.js';
|
||||
export * from './deploy-to-ack-ingress/index.js';
|
||||
export * from './upload-to-aliyun/index.js';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import Core from '@alicloud/pop-core';
|
||||
import { appendTimeSuffix, checkRet } from '../../utils/index.js';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
import { appendTimeSuffix, checkRet, ZoneOptions } from '../../utils/index.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToAliyun',
|
||||
@@ -27,7 +26,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
component: {
|
||||
name: 'a-auto-complete',
|
||||
vModel: 'value',
|
||||
options: ZoneOptions,
|
||||
options: [{ value: 'cn-hangzhou' }, { value: 'eu-central-1' }, { value: 'ap-southeast-1' }],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@@ -38,6 +37,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
from: 'CertApply',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
@@ -62,9 +62,9 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
async onInstance() {}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
console.log('开始部署证书到阿里云cdn');
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access);
|
||||
this.logger.info('开始部署证书到阿里云cdn');
|
||||
const access: AliyunAccess = await this.accessService.getById(this.accessId);
|
||||
const client = await this.getClient(access);
|
||||
const certName = appendTimeSuffix(this.name);
|
||||
const params = {
|
||||
RegionId: this.regionId || 'cn-hangzhou',
|
||||
@@ -77,7 +77,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
method: 'POST',
|
||||
};
|
||||
|
||||
const ret = (await client.request('CreateUserCertificate', params, requestOption)) as any;
|
||||
const ret: any = await client.request('CreateUserCertificate', params, requestOption);
|
||||
checkRet(ret);
|
||||
this.logger.info('证书上传成功:aliyunCertId=', ret.CertId);
|
||||
|
||||
@@ -85,8 +85,9 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
this.aliyunCertId = ret.CertId;
|
||||
}
|
||||
|
||||
getClient(aliyunProvider: AliyunAccess) {
|
||||
return new Core({
|
||||
async getClient(aliyunProvider: AliyunAccess) {
|
||||
const Core = await import('@alicloud/pop-core');
|
||||
return new Core.default({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: 'https://cas.aliyuncs.com',
|
||||
|
||||
@@ -20,6 +20,7 @@ export class CloudflareAccess {
|
||||
},
|
||||
helper: '前往 https://dash.cloudflare.com/profile/api-tokens 获取API令牌, token权限必须包含:[Zone区域-Zone区域-Edit编辑], [Zone区域-DNS-Edit编辑]',
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
apiToken = '';
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ export class CloudflareDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
from: 'CertApply',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user