diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 5f088744e..a8e6c57ec 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -19,6 +19,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + lfs: true - name: get_certd_version id: get_certd_version diff --git a/CHANGELOG.md b/CHANGELOG.md index f7606e515..dea5716e8 100644 --- a/CHANGELOG.md +++ b/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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +### Bug Fixes + +* 修复历史记录不能按名称查询的bug ([6113c38](https://github.com/certd/certd/commit/6113c388b7fc58b11ca19ff05cc1286d096c8d28)) +* pfx兼容windows server 2016 ([e5e468a](https://github.com/certd/certd/commit/e5e468a463f66d02f235de54b7c1e09ace5f1cb1)) + +### Features + +* 首页全新改版 ([63ec5b5](https://github.com/certd/certd/commit/63ec5b5519c760a3330569c0da6dac157302a330)) + +### Performance Improvements + +* 管理控制台数据统计 ([babd589](https://github.com/certd/certd/commit/babd5897ae013ff7c04ebfcbfac8a00d84dd627c)) +* 增加向导 ([6d9ef26](https://github.com/certd/certd/commit/6d9ef26ecab71d752c2c55d75aed4fb5f6c05a39)) +* lego 升级到 4.19.2 ([129bf53](https://github.com/certd/certd/commit/129bf53edc9bbb001fe49fbd7e239bd1d09cc128)) + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) ### Bug Fixes diff --git a/build.trigger b/build.trigger index b1aaae9bd..0cfbf0888 100644 --- a/build.trigger +++ b/build.trigger @@ -1 +1 @@ -11:14 +2 diff --git a/docker/run/docker-compose.yaml b/docker/run/docker-compose.yaml index 420cf0b1c..e73ee5840 100644 --- a/docker/run/docker-compose.yaml +++ b/docker/run/docker-compose.yaml @@ -23,19 +23,13 @@ services: # extra_hosts: # ↓↓↓↓ ---------------------------------------------------------- 这里可以配置自定义hosts,外网域名可以指向本地局域网ip地址 # - "localdomain.comm:192.168.1.3" - environment: # 环境变量 + environment: - TZ=Asia/Shanghai # 设置环境变量即可自定义certd配置 # 配置项见: packages/ui/certd-server/src/config/config.default.ts # 配置规则: certd_ + 配置项, 点号用_代替 # ↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,重启之后,管理员密码将改成123456,然后请及时修改回false - certd_system_resetAdminPasswd=false - # ↓↓↓↓ -------------------------------- 默认同时启动https,https访问地址https://your.domain:7002 - #- certd_https_key=./data/ssl/cert.key - #- certd_https_cert=./data/ssl/cert.crt - #- certd_https_enabled=true - #- certd_https_port=7002 - - # ↓↓↓↓ ------------------------------- 使用postgresql数据库 # - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录 # - certd_typeorm_dataSource_default_type=postgres # 数据库类型 diff --git a/lerna.json b/lerna.json index f700f106d..638b4a7c1 100644 --- a/lerna.json +++ b/lerna.json @@ -9,5 +9,5 @@ } }, "npmClient": "pnpm", - "version": "1.26.16" + "version": "1.27.0" } diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md index 39016ad83..796247b0e 100644 --- a/packages/core/acme-client/CHANGELOG.md +++ b/packages/core/acme-client/CHANGELOG.md @@ -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.27.0](https://github.com/publishlab/node-acme-client/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/acme-client + ## [1.26.16](https://github.com/publishlab/node-acme-client/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/acme-client diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index 1eff07250..51a93b73a 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -3,7 +3,7 @@ "description": "Simple and unopinionated ACME client", "private": false, "author": "nmorsman", - "version": "1.26.16", + "version": "1.27.0", "main": "src/index.js", "types": "types/index.d.ts", "license": "MIT", @@ -60,5 +60,5 @@ "bugs": { "url": "https://github.com/publishlab/node-acme-client/issues" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/core/acme-client/src/agents.js b/packages/core/acme-client/src/agents.js index ab536d98d..ab220dff6 100644 --- a/packages/core/acme-client/src/agents.js +++ b/packages/core/acme-client/src/agents.js @@ -42,19 +42,20 @@ function setGlobalProxy(opts) { } class HttpError extends Error { + // eslint-disable-next-line constructor-super constructor(error) { - super(error || error.message); if (!error) { return; } + super(error.message); - if (error.message.indexOf('ssl3_get_record:wrong version number') >= 0) { + this.message = error.message; + if (this.message && this.message.indexOf('ssl3_get_record:wrong version number') >= 0) { this.message = 'http协议错误,服务端要求http协议,请检查是否使用了https请求'; } this.name = error.name; this.code = error.code; - this.cause = error.cause; if (error.response) { this.status = error.response.status; @@ -62,6 +63,9 @@ class HttpError extends Error { this.response = { data: error.response.data, }; + if (!this.message) { + this.message = this.statusText; + } } let url = ''; @@ -73,12 +77,18 @@ class HttpError extends Error { params: error.config.params, data: error.config.data, }; - url = error.config.baseURL + error.config.url; + url = (error.config.baseURL || '') + error.config.url; } if (url) { this.message = `${this.message}:${url}`; } - + // const { stack, cause } = error; + delete this.cause; + delete this.stack; + // this.cause = cause; + // this.stack = stack; + delete error.stack; + delete error.cause; delete error.response; delete error.config; delete error.request; diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index dcb47ac4b..7e1926169 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -5,6 +5,7 @@ const { readCsrDomains } = require('./crypto'); const { log } = require('./logger'); const { wait } = require('./wait'); +const { CancelError } = require('./error'); const defaultOpts = { csr: null, @@ -250,7 +251,7 @@ module.exports = async (client, userOpts) => { i += 1; log(`开始第${i}组`); if (opts.signal && opts.signal.aborted) { - throw new Error('用户取消'); + throw new CancelError('用户取消'); } try { diff --git a/packages/core/acme-client/src/axios.js b/packages/core/acme-client/src/axios.js index 2c54c0f5a..ed3313a23 100644 --- a/packages/core/acme-client/src/axios.js +++ b/packages/core/acme-client/src/axios.js @@ -114,17 +114,19 @@ instance.interceptors.response.use(null, async (error) => { const code = response ? `HTTP ${response.status}` : error.code; log(`Caught ${code}, retry attempt ${config.retryAttempt}/${retryMaxAttempts} to URL ${config.url}`); + const retryAfter = (retryDefaultDelay * config.retryAttempt); /* Attempt to parse Retry-After header, fallback to default delay */ - let retryAfter = response ? parseRetryAfterHeader(response.headers['retry-after']) : 0; + const headerRetryAfter = response ? parseRetryAfterHeader(response.headers['retry-after']) : 0; - if (retryAfter > 0) { - log(`Found retry-after response header with value: ${response.headers['retry-after']}, waiting ${retryAfter} seconds`); - } - else { - retryAfter = (retryDefaultDelay * config.retryAttempt); - log(`Unable to locate or parse retry-after response header, waiting ${retryAfter} seconds`); + if (headerRetryAfter > 0) { + const waitMinutes = (headerRetryAfter / 60).toFixed(1); + log(`Found retry-after response header with value: ${response.headers['retry-after']}, waiting ${waitMinutes} minutes`); + log(JSON.stringify(response.data)); + return Promise.reject(new Agents.HttpError(error)); } + log(`waiting ${retryAfter} seconds`); + /* Wait and retry the request */ await new Promise((resolve) => { setTimeout(resolve, (retryAfter * 1000)); }); return instance(config); diff --git a/packages/core/acme-client/src/client.js b/packages/core/acme-client/src/client.js index f96adc370..d5b9b8076 100644 --- a/packages/core/acme-client/src/client.js +++ b/packages/core/acme-client/src/client.js @@ -12,6 +12,7 @@ const AcmeApi = require('./api'); const verify = require('./verify'); const util = require('./util'); const auto = require('./auto'); +const { CancelError } = require('./error'); /** * ACME states @@ -490,9 +491,10 @@ class AcmeClient { const keyAuthorization = await this.getChallengeKeyAuthorization(challenge); - const verifyFn = async () => { + const verifyFn = async (abort) => { if (this.opts.signal && this.opts.signal.aborted) { - throw new Error('用户取消'); + abort(); + throw new CancelError('用户取消'); } await verify[challenge.type](authz, challenge, keyAuthorization); }; @@ -518,7 +520,7 @@ class AcmeClient { async completeChallenge(challenge) { if (this.opts.signal && this.opts.signal.aborted) { - throw new Error('用户取消'); + throw new CancelError('用户取消'); } const resp = await this.api.completeChallenge(challenge.url, {}); return resp.data; @@ -559,7 +561,7 @@ class AcmeClient { const verifyFn = async (abort) => { if (this.opts.signal && this.opts.signal.aborted) { abort(); - throw new Error('用户取消'); + throw new CancelError('用户取消'); } const resp = await this.api.apiRequest(item.url, null, [200]); diff --git a/packages/core/acme-client/src/error.js b/packages/core/acme-client/src/error.js new file mode 100644 index 000000000..cc23f49ae --- /dev/null +++ b/packages/core/acme-client/src/error.js @@ -0,0 +1,10 @@ +class CancelError extends Error { + constructor(message) { + super(message); + this.name = 'CancelError'; + } +} + +module.exports = { + CancelError, +}; diff --git a/packages/core/acme-client/src/index.js b/packages/core/acme-client/src/index.js index b0a58842c..63019f2d8 100644 --- a/packages/core/acme-client/src/index.js +++ b/packages/core/acme-client/src/index.js @@ -48,3 +48,4 @@ exports.agents = require('./agents'); exports.setLogger = require('./logger').setLogger; exports.walkTxtRecord = require('./verify').walkTxtRecord; +exports.CancelError = require('./error').CancelError; diff --git a/packages/core/acme-client/src/wait.js b/packages/core/acme-client/src/wait.js index 307d796c1..f39e53c3a 100644 --- a/packages/core/acme-client/src/wait.js +++ b/packages/core/acme-client/src/wait.js @@ -5,5 +5,5 @@ async function wait(ms) { } module.exports = { - wait + wait, }; diff --git a/packages/core/acme-client/types/index.d.ts b/packages/core/acme-client/types/index.d.ts index 56e4cf110..e53aabbc2 100644 --- a/packages/core/acme-client/types/index.d.ts +++ b/packages/core/acme-client/types/index.d.ts @@ -198,6 +198,8 @@ export const agents: any; * Logger */ -export function setLogger(fn: (msg: string) => void): void; +export function setLogger(fn: (message: any, ...args: any[]) => void): void; export function walkTxtRecord(record: any): Promise; + +export const CancelError: Error; diff --git a/packages/core/basic/CHANGELOG.md b/packages/core/basic/CHANGELOG.md index 9a382c717..8b10f1d80 100644 --- a/packages/core/basic/CHANGELOG.md +++ b/packages/core/basic/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/basic + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) ### Performance Improvements diff --git a/packages/core/basic/build.md b/packages/core/basic/build.md index 6f734051f..2a38d7b2d 100644 --- a/packages/core/basic/build.md +++ b/packages/core/basic/build.md @@ -1 +1 @@ -11:12 +02:21 diff --git a/packages/core/basic/package.json b/packages/core/basic/package.json index 7ca80dc13..720aaba09 100644 --- a/packages/core/basic/package.json +++ b/packages/core/basic/package.json @@ -1,7 +1,7 @@ { "name": "@certd/basic", "private": false, - "version": "1.26.16", + "version": "1.27.0", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -64,5 +64,5 @@ "vite": "^4.3.8", "vue-tsc": "^1.6.5" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/core/basic/src/utils/index.ts b/packages/core/basic/src/utils/index.ts index 1d9abbd6b..ba12f73a2 100644 --- a/packages/core/basic/src/utils/index.ts +++ b/packages/core/basic/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './util.request.js'; +export * from './util.env.js'; export * from './util.log.js'; export * from './util.file.js'; export * from './util.sp.js'; diff --git a/packages/core/basic/src/utils/util.env.ts b/packages/core/basic/src/utils/util.env.ts new file mode 100644 index 000000000..1427b8821 --- /dev/null +++ b/packages/core/basic/src/utils/util.env.ts @@ -0,0 +1,3 @@ +export function isDev() { + return process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'local'; +} diff --git a/packages/core/basic/src/utils/util.hash.ts b/packages/core/basic/src/utils/util.hash.ts index d21f5fecd..44d2f97da 100644 --- a/packages/core/basic/src/utils/util.hash.ts +++ b/packages/core/basic/src/utils/util.hash.ts @@ -1,7 +1,7 @@ -import crypto from "crypto"; +import crypto from 'crypto'; function md5(data: string) { - return crypto.createHash("md5").update(data).digest("hex"); + return crypto.createHash('md5').update(data).digest('hex'); } export const hashUtils = { diff --git a/packages/core/basic/src/utils/util.request.ts b/packages/core/basic/src/utils/util.request.ts index 227bfbfb9..10b8b16c2 100644 --- a/packages/core/basic/src/utils/util.request.ts +++ b/packages/core/basic/src/utils/util.request.ts @@ -17,15 +17,14 @@ export class HttpError extends Error { if (!error) { return; } - super(error.message); + super(error.message || error.response?.statusText); - if (error?.message?.indexOf('ssl3_get_record:wrong version number') >= 0) { + if (error?.message?.indexOf && error?.message?.indexOf('ssl3_get_record:wrong version number') >= 0) { this.message = 'http协议错误,服务端要求http协议,请检查是否使用了https请求'; } this.name = error.name; this.code = error.code; - this.cause = error.cause; this.status = error.response?.status; this.statusText = error.response?.statusText; @@ -38,7 +37,7 @@ export class HttpError extends Error { }; let url = error.config?.url; if (error.config?.baseURL) { - url = error.config?.baseURL + url; + url = (error.config?.baseURL || '') + url; } if (url) { this.message = `${this.message} : url=${url}`; @@ -48,6 +47,9 @@ export class HttpError extends Error { data: error.response?.data, }; + // const { stack, cause } = error; + // this.cause = cause; + // this.stack = stack; delete error.response; delete error.config; delete error.request; diff --git a/packages/core/pipeline/CHANGELOG.md b/packages/core/pipeline/CHANGELOG.md index 45034875a..bad4d177c 100644 --- a/packages/core/pipeline/CHANGELOG.md +++ b/packages/core/pipeline/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/pipeline + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) ### Performance Improvements diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index 784a4400d..6ebb2c506 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -1,7 +1,7 @@ { "name": "@certd/pipeline", "private": false, - "version": "1.26.16", + "version": "1.27.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -15,8 +15,8 @@ "test": "mocha --loader=ts-node/esm" }, "dependencies": { - "@certd/basic": "^1.26.16", - "@certd/plus-core": "^1.26.16", + "@certd/basic": "^1.27.0", + "@certd/plus-core": "^1.27.0", "axios": "^1.7.2", "dayjs": "^1.11.7", "fix-path": "^4.0.0", @@ -66,5 +66,5 @@ "vite": "^4.3.8", "vue-tsc": "^1.6.5" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/core/pipeline/src/core/exceptions.ts b/packages/core/pipeline/src/core/exceptions.ts new file mode 100644 index 000000000..b68306374 --- /dev/null +++ b/packages/core/pipeline/src/core/exceptions.ts @@ -0,0 +1,5 @@ +export class CancelError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 7f332de8b..b0c77d6fd 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -135,7 +135,12 @@ export class Executor { this.runtime.success(runnable); return ResultType.success; } catch (e: any) { - this.runtime.error(runnable, e); + if (e.name === "CancelError" || this.abort.signal.aborted) { + this.runtime.cancel(runnable); + return ResultType.canceled; + } else { + this.runtime.error(runnable, e); + } throw e; } finally { this.runtime.finally(runnable); diff --git a/packages/core/pipeline/src/core/index.ts b/packages/core/pipeline/src/core/index.ts index 6059e8589..a5ad2a39f 100644 --- a/packages/core/pipeline/src/core/index.ts +++ b/packages/core/pipeline/src/core/index.ts @@ -5,3 +5,4 @@ export * from "./storage.js"; export * from "./file-store.js"; export * from "./license.js"; export * from "./handler.js"; +export * from "./exceptions.js"; diff --git a/packages/core/pipeline/src/core/run-history.ts b/packages/core/pipeline/src/core/run-history.ts index 84bf97593..fe27f8eaa 100644 --- a/packages/core/pipeline/src/core/run-history.ts +++ b/packages/core/pipeline/src/core/run-history.ts @@ -117,7 +117,8 @@ export class RunHistory { } logError(runnable: Runnable, e: Error) { - // @ts-ignore + delete e.stack; + delete e.cause; const errorInfo = runnable.runnableType === "step" ? e : e.message; this._loggers[runnable.id].error(`[${runnable.runnableType}] [${runnable.title}] :`, errorInfo); } diff --git a/packages/core/pipeline/src/d.ts/fast-crud.ts b/packages/core/pipeline/src/d.ts/fast-crud.ts deleted file mode 100644 index 91515a61a..000000000 --- a/packages/core/pipeline/src/d.ts/fast-crud.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * [x]-col的配置 - */ -export type ColProps = { - span?: number; - [props: string]: any; -}; - -export type FormItemProps = { - /** - * 字段label - */ - title?: string; - /** - * 表单字段组件配置 - */ - component?: ComponentProps; - /** - * 表单字段 [a|el|n]-col的配置 - * 一般用来配置跨列:{span:24} 占满一行 - */ - col?: ColProps; - /** - * 默认值 - */ - value?: any; - /** - * 帮助提示配置 - */ - helper?: string | FormItemHelperProps; - /** - * 排序号 - */ - order?: number; - /** - * 是否显示此字段 - */ - show?: boolean; - /** - * 是否是空白占位栏 - */ - blank?: boolean; - - [key: string]: any; -}; - -/** - * 表单字段帮助说明配置 - */ -export type FormItemHelperProps = { - /** - * 自定义渲染帮助说明 - * @param scope - */ - render?: (scope: any) => any; - /** - * 帮助文本 - */ - text?: string; - /** - * 帮助说明所在的位置,[ undefined | label] - */ - position?: string; - /** - * [a|el|n]-tooltip配置 - */ - tooltip?: object; - - [key: string]: any; -}; - -/** - * 组件配置 - */ -export type ComponentProps = { - /** - * 组件的名称 - */ - name?: string | object; - /** - * vmodel绑定的目标属性名 - */ - vModel?: string; - - /** - * 当原始组件名的参数被以上属性名占用时,可以配置在这里 - * 例如:原始组件有一个叫name的属性,你想要配置它,则可以按如下配置 - * ``` - * component:{ - * name:"组件的名称" - * props:{ - * name:"组件的name属性" <----------- - * } - * } - * ``` - */ - props?: { - [key: string]: any; - }; - - /** - * 组件事件监听 - */ - on?: { - [key: string]: (context?: any) => void; - }; - - /** - * 组件其他参数 - * 事件:onXxx:(event)=>void 组件原始事件监听 - * on.onXxx:(context)=>void 组件事件监听(对原始事件包装) - * 样式:style、class等 - */ - [key: string]: any; -}; diff --git a/packages/core/pipeline/src/d.ts/index.ts b/packages/core/pipeline/src/d.ts/index.ts deleted file mode 100644 index 785c76197..000000000 --- a/packages/core/pipeline/src/d.ts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./pipeline"; -export * from "./fast-crud"; diff --git a/packages/core/pipeline/src/d.ts/pipeline.ts b/packages/core/pipeline/src/d.ts/pipeline.ts deleted file mode 100644 index f577e622e..000000000 --- a/packages/core/pipeline/src/d.ts/pipeline.ts +++ /dev/null @@ -1,140 +0,0 @@ -export enum RunStrategy { - AlwaysRun, - SkipWhenSucceed, -} - -export enum ConcurrencyStrategy { - Serial, - Parallel, -} - -export enum NextStrategy { - AllSuccess, - OneSuccess, -} - -export enum HandlerType { - //清空后续任务的状态 - ClearFollowStatus, - SendEmail, -} - -export type EventHandler = { - type: HandlerType; - params: { - [key: string]: any; - }; -}; - -export type RunnableStrategy = { - runStrategy?: RunStrategy; - onSuccess?: EventHandler[]; - onError?: EventHandler[]; -}; - -export type Step = Runnable & { - type: string; //插件类型 - input: { - [key: string]: any; - }; -}; -export type Task = Runnable & { - steps: Step[]; -}; - -export type Stage = Runnable & { - tasks: Task[]; - concurrency: ConcurrencyStrategy; - next: NextStrategy; -}; - -export type Trigger = { - id: string; - title: string; - cron: string; - type: string; -}; - -export type FileItem = { - id: string; - filename: string; - path: string; -}; -export type Runnable = { - id: string; - title: string; - strategy?: RunnableStrategy; - runnableType?: string; // pipeline, stage, task , step - status?: HistoryResult; - timeout?: number; - default?: { - [key: string]: any; - }; - context?: Context; -}; - -export type EmailOptions = { - receivers: string[]; -}; -export type NotificationWhen = "error" | "success" | "turnToSuccess" | "start"; -export type NotificationType = "email" | "url"; -export type Notification = { - type: NotificationType; - when: NotificationWhen[]; - options: EmailOptions; -}; - -export type Pipeline = Runnable & { - version?: number; - userId: any; - stages: Stage[]; - triggers: Trigger[]; - notifications?: Notification[]; -}; - -export type Context = { - [key: string]: any; -}; - -export type Log = { - title: string; - time: number; - level: string; - text: string; -}; - -export enum ResultType { - start = "start", - success = "success", - error = "error", - canceled = "canceled", - skip = "skip", - none = "none", -} - -export type HistoryResultGroup = { - [key: string]: { - runnable: Runnable; - res: HistoryResult; - }; -}; -export type HistoryResult = { - input: any; - output: any; - files?: FileItem[]; - /** - * 任务状态 - */ - status: ResultType; - startTime: number; - endTime?: number; - /** - * 处理结果 - */ - result?: ResultType; //success, error,skip - message?: string; -}; - -export type RunnableMap = { - [id: string]: Runnable; -}; diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index b3c8a5014..21f8fd9f4 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -4,7 +4,7 @@ import { FileStore } from "../core/file-store.js"; import { Logger } from "log4js"; import { IAccessService } from "../access/index.js"; import { ICnameProxyService, IEmailService } from "../service/index.js"; -import { IContext, PluginRequestHandleReq, RunnableCollection } from "../core/index.js"; +import { CancelError, IContext, PluginRequestHandleReq, RunnableCollection } from "../core/index.js"; import { ILogger, logger, utils } from "../utils/index.js"; import { HttpClient } from "../utils/index.js"; import dayjs from "dayjs"; @@ -113,7 +113,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { checkSignal() { if (this.ctx.signal && this.ctx.signal.aborted) { - throw new Error("用户取消"); + throw new CancelError("用户取消"); } } diff --git a/packages/core/pipeline/src/registry/registry.ts b/packages/core/pipeline/src/registry/registry.ts index 91a74ef33..36780c31f 100644 --- a/packages/core/pipeline/src/registry/registry.ts +++ b/packages/core/pipeline/src/registry/registry.ts @@ -1,4 +1,4 @@ -import { logger } from "../utils/index.js"; +import { isDev, logger } from "../utils/index.js"; export type Registrable = { name: string; @@ -71,6 +71,9 @@ export class Registry { if (define?.deprecated) { continue; } + if (!isDev() && define.name.startsWith("demo")) { + continue; + } list.push({ ...define, key }); } } diff --git a/packages/libs/lib-huawei/CHANGELOG.md b/packages/libs/lib-huawei/CHANGELOG.md index f0d670b24..99fbb1310 100644 --- a/packages/libs/lib-huawei/CHANGELOG.md +++ b/packages/libs/lib-huawei/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/lib-huawei + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/lib-huawei diff --git a/packages/libs/lib-huawei/package.json b/packages/libs/lib-huawei/package.json index d2a57086d..815831d12 100644 --- a/packages/libs/lib-huawei/package.json +++ b/packages/libs/lib-huawei/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-huawei", "private": false, - "version": "1.26.16", + "version": "1.27.0", "main": "./dist/bundle.js", "module": "./dist/bundle.js", "types": "./dist/d/index.d.ts", @@ -17,5 +17,5 @@ "rimraf": "^5.0.5", "rollup": "^3.7.4" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/libs/lib-iframe/CHANGELOG.md b/packages/libs/lib-iframe/CHANGELOG.md index 845e989f3..63acedbab 100644 --- a/packages/libs/lib-iframe/CHANGELOG.md +++ b/packages/libs/lib-iframe/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/lib-iframe + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/lib-iframe diff --git a/packages/libs/lib-iframe/package.json b/packages/libs/lib-iframe/package.json index 3d3c3c455..b1278eebe 100644 --- a/packages/libs/lib-iframe/package.json +++ b/packages/libs/lib-iframe/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-iframe", "private": false, - "version": "1.26.16", + "version": "1.27.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -39,5 +39,5 @@ "tslib": "^2.5.2", "typescript": "^5.4.2" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/libs/lib-jdcloud/CHANGELOG.md b/packages/libs/lib-jdcloud/CHANGELOG.md index d37e1fecf..f58ad32ae 100644 --- a/packages/libs/lib-jdcloud/CHANGELOG.md +++ b/packages/libs/lib-jdcloud/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/lib-jdcloud + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/lib-jdcloud diff --git a/packages/libs/lib-jdcloud/package.json b/packages/libs/lib-jdcloud/package.json index c7f0ecc65..d8fe93201 100644 --- a/packages/libs/lib-jdcloud/package.json +++ b/packages/libs/lib-jdcloud/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-jdcloud", "private": false, - "version": "1.26.16", + "version": "1.27.0", "main": "./dist/bundle.mjs", "module": "./dist/bundle.mjs", "types": "./dist/d/index.d.ts", @@ -27,5 +27,5 @@ "rimraf": "^5.0.5", "rollup": "^3.7.4" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/libs/lib-k8s/CHANGELOG.md b/packages/libs/lib-k8s/CHANGELOG.md index 12a631a5d..1bbcc768c 100644 --- a/packages/libs/lib-k8s/CHANGELOG.md +++ b/packages/libs/lib-k8s/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/lib-k8s + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/lib-k8s diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json index 4411d50cb..ef5de94c7 100644 --- a/packages/libs/lib-k8s/package.json +++ b/packages/libs/lib-k8s/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-k8s", "private": false, - "version": "1.26.16", + "version": "1.27.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -18,7 +18,7 @@ "@kubernetes/client-node": "0.21.0" }, "devDependencies": { - "@certd/pipeline": "^1.26.16", + "@certd/pipeline": "^1.27.0", "@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", @@ -40,5 +40,5 @@ "tslib": "^2.5.2", "typescript": "^5.4.2" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/libs/lib-server/CHANGELOG.md b/packages/libs/lib-server/CHANGELOG.md index 0f6dc5145..0eef96215 100644 --- a/packages/libs/lib-server/CHANGELOG.md +++ b/packages/libs/lib-server/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/lib-server + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/lib-server diff --git a/packages/libs/lib-server/package.json b/packages/libs/lib-server/package.json index 7dcb68dd6..d20f6496e 100644 --- a/packages/libs/lib-server/package.json +++ b/packages/libs/lib-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/lib-server", - "version": "1.26.16", + "version": "1.27.0", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -26,9 +26,9 @@ ], "license": "AGPL", "dependencies": { - "@certd/acme-client": "^1.26.16", - "@certd/basic": "^1.26.16", - "@certd/pipeline": "^1.26.16", + "@certd/acme-client": "^1.27.0", + "@certd/basic": "^1.27.0", + "@certd/pipeline": "^1.27.0", "@midwayjs/cache": "~3.14.0", "@midwayjs/core": "~3.17.1", "@midwayjs/i18n": "~3.17.3", @@ -69,5 +69,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/libs/midway-flyway-js/CHANGELOG.md b/packages/libs/midway-flyway-js/CHANGELOG.md index 832ee5a5d..2f78b4cb4 100644 --- a/packages/libs/midway-flyway-js/CHANGELOG.md +++ b/packages/libs/midway-flyway-js/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +**Note:** Version bump only for package @certd/midway-flyway-js + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/midway-flyway-js diff --git a/packages/libs/midway-flyway-js/package.json b/packages/libs/midway-flyway-js/package.json index 8e9f24d31..fe228ca57 100644 --- a/packages/libs/midway-flyway-js/package.json +++ b/packages/libs/midway-flyway-js/package.json @@ -1,6 +1,6 @@ { "name": "@certd/midway-flyway-js", - "version": "1.26.16", + "version": "1.27.0", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -56,5 +56,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/plugins/plugin-cert/CHANGELOG.md b/packages/plugins/plugin-cert/CHANGELOG.md index f8d0d1b9c..09931b08e 100644 --- a/packages/plugins/plugin-cert/CHANGELOG.md +++ b/packages/plugins/plugin-cert/CHANGELOG.md @@ -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.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +### Bug Fixes + +* pfx兼容windows server 2016 ([e5e468a](https://github.com/certd/certd/commit/e5e468a463f66d02f235de54b7c1e09ace5f1cb1)) + +### Features + +* 首页全新改版 ([63ec5b5](https://github.com/certd/certd/commit/63ec5b5519c760a3330569c0da6dac157302a330)) + +### Performance Improvements + +* lego 升级到 4.19.2 ([129bf53](https://github.com/certd/certd/commit/129bf53edc9bbb001fe49fbd7e239bd1d09cc128)) + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) ### Bug Fixes diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index 62f132249..9cc207365 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-cert", "private": false, - "version": "1.26.16", + "version": "1.27.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -15,9 +15,9 @@ "preview": "vite preview" }, "dependencies": { - "@certd/acme-client": "^1.26.16", - "@certd/basic": "^1.26.16", - "@certd/pipeline": "^1.26.16", + "@certd/acme-client": "^1.27.0", + "@certd/basic": "^1.27.0", + "@certd/pipeline": "^1.27.0", "@google-cloud/publicca": "^1.3.0", "dayjs": "^1.11.7", "jszip": "^3.10.1", @@ -57,5 +57,5 @@ "vite": "^3.1.0", "vue-tsc": "^0.38.9" }, - "gitHead": "844fd4358c84251c72c5854ea633f238014244ad" + "gitHead": "c643d7edc3721a6d2ac701a35ef600f6b6ff4b34" } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index a1e87ff2e..64c108161 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -60,8 +60,8 @@ export class AcmeService { this.sslProvider = options.sslProvider || "letsencrypt"; this.eab = options.eab; this.skipLocalVerify = options.skipLocalVerify ?? false; - acme.setLogger((text: string) => { - this.logger.info(text); + acme.setLogger((message: any, ...args: any[]) => { + this.logger.info(message, ...args); }); } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts index 2e0377caa..1861e8755 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts @@ -30,7 +30,7 @@ export class CertConverter { // 转der derPath = await this.convertDer(ctx); - //jksPath = await this.convertJks(ctx, pfxPath, opts.pfxPassword); + //jksPath = await this.convertJks(ctx, opts.pfxPassword); }; await certReader.readCertFile({ logger: this.logger, handle }); @@ -64,7 +64,10 @@ export class CertConverter { if (pfxPassword) { passwordArg = `-password pass:${pfxPassword}`; } - await this.exec(`openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`); + // 兼容server 2016,旧版本不能用sha256 + const oldPfxCmd = `openssl pkcs12 -macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`; + // const newPfx = `openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`; + await this.exec(oldPfxCmd); return pfxPath; // const fileBuffer = fs.readFileSync(pfxPath); // this.pfxCert = fileBuffer.toString("base64"); @@ -95,18 +98,18 @@ export class CertConverter { // this.saveFile(filename, fileBuffer); } - async convertJks(opts: CertReaderHandleContext, pfxPath: string, pfxPassword = "") { + async convertJks(opts: CertReaderHandleContext, pfxPassword = "") { const jksPassword = pfxPassword || "123456"; try { const randomStr = Math.floor(Math.random() * 1000000) + ""; - // const p12Path = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.p12`); - // const { tmpCrtPath, tmpKeyPath } = opts; - // let passwordArg = "-passout pass:"; - // if (pfxPassword) { - // passwordArg = `-password pass:${pfxPassword}`; - // } - // await this.exec(`openssl pkcs12 -export -in ${tmpCrtPath} -inkey ${tmpKeyPath} -out ${p12Path} -name certd ${passwordArg}`); + const p12Path = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.p12`); + const { tmpCrtPath, tmpKeyPath } = opts; + let passwordArg = "-passout pass:"; + if (pfxPassword) { + passwordArg = `-password pass:${pfxPassword}`; + } + await this.exec(`openssl pkcs12 -export -in ${tmpCrtPath} -inkey ${tmpKeyPath} -out ${p12Path} -name certd ${passwordArg}`); const jksPath = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.jks`); const dir = path.dirname(jksPath); @@ -114,8 +117,9 @@ export class CertConverter { fs.mkdirSync(dir, { recursive: true }); } await this.exec( - `keytool -importkeystore -srckeystore ${pfxPath} -srcstoretype PKCS12 -srcstorepass "${pfxPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" ` + `keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${pfxPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" ` ); + fs.unlinkSync(p12Path); return jksPath; } catch (e) { this.logger.error("转换jks失败", e); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 4576c6b97..4be4a86bb 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -7,6 +7,7 @@ import { CertReader } from "./cert-reader.js"; import { CertApplyBasePlugin } from "./base.js"; import { GoogleClient } from "../../libs/google.js"; import { EabAccess } from "../../access"; +import { CancelError } from "@certd/pipeline"; export type { CertInfo }; export * from "./cert-reader.js"; @@ -293,6 +294,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { useMappingProxy: this.useProxy, reverseProxy: this.reverseProxy, privateKeyType: this.privateKeyType, + signal: this.ctx.signal, // cnameProxyService: this.ctx.cnameProxyService, // dnsProviderCreator: this.createDnsProvider.bind(this), }); @@ -347,6 +349,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin { this.logger.error(e); throw new Error(`通配符域名已经包含了普通域名,请删除其中一个(${message})`); } + if (e.name === "CancelError") { + throw new CancelError(e.message); + } throw e; } } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts index b3a2bd220..a37fa1d32 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts @@ -5,6 +5,8 @@ import { CertApplyBasePlugin } from "../base.js"; import fs from "fs"; import { EabAccess } from "../../../access/index.js"; import path from "path"; +import { utils } from "@certd/basic"; +import JSZip from "jszip"; export { CertReader }; export type { CertInfo }; @@ -126,6 +128,31 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin { const savePathArgs = `--path "${saveDir}"`; const os_type = process.platform === "win32" ? "windows" : "linux"; const legoPath = path.resolve("./tools", os_type, "lego"); + if (!fs.existsSync(legoPath)) { + //解压缩 + if (os_type === "linux") { + //判断当前是arm64 还是amd64 + const arch = process.arch; + let platform = "amd64"; + if (arch === "arm64" || arch === "arm") { + platform = "arm64"; + } + await utils.sp.spawn({ + cmd: `tar -zxvf ./tools/linux/lego_linux_${platform}.tar.gz -C ./tools/linux/`, + }); + this.logger.info("解压lego成功"); + } else { + const zip = new JSZip(); + const data = fs.readFileSync("./tools/windows/lego_windows_amd64.zip"); + const zipData = await zip.loadAsync(data); + const files = Object.keys(zipData.files); + for (const file of files) { + const content = await zipData.files[file].async("nodebuffer"); + fs.writeFileSync(`./tools/windows/${file}`, content); + } + this.logger.info("解压lego成功"); + } + } let serverArgs = ""; if (this.acmeServer) { serverArgs = ` --server ${this.acmeServer}`; diff --git a/packages/ui/Dockerfile b/packages/ui/Dockerfile index 2eb71680b..fe7bd2e8a 100644 --- a/packages/ui/Dockerfile +++ b/packages/ui/Dockerfile @@ -13,10 +13,12 @@ RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker FROM node:18-alpine RUN apk add --no-cache openssl -RUN #apk add --no-cache openjdk11-jdk +# RUN apk add --no-cache openjdk11-jdk WORKDIR /app/ COPY --from=builder /workspace/certd-server/ /app/ +#RUN cd /app/tools/linux/ && ls -lh && tar -zxvf lego_linux_amd64.tar.gz RUN chmod +x /app/tools/linux/* + ENV TZ=Asia/Shanghai ENV NODE_ENV=production ENV MIDWAY_SERVER_ENV=production diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md index 83c043044..6ce33ac53 100644 --- a/packages/ui/certd-client/CHANGELOG.md +++ b/packages/ui/certd-client/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +### Bug Fixes + +* 修复历史记录不能按名称查询的bug ([6113c38](https://github.com/certd/certd/commit/6113c388b7fc58b11ca19ff05cc1286d096c8d28)) + +### Features + +* 首页全新改版 ([63ec5b5](https://github.com/certd/certd/commit/63ec5b5519c760a3330569c0da6dac157302a330)) + +### Performance Improvements + +* 管理控制台数据统计 ([babd589](https://github.com/certd/certd/commit/babd5897ae013ff7c04ebfcbfac8a00d84dd627c)) +* 增加向导 ([6d9ef26](https://github.com/certd/certd/commit/6d9ef26ecab71d752c2c55d75aed4fb5f6c05a39)) + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) **Note:** Version bump only for package @certd/ui-client diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 2370e6603..a9e8da715 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-client", - "version": "1.26.16", + "version": "1.27.0", "private": true, "scripts": { "dev": "vite --open", @@ -45,6 +45,7 @@ "cron-parser": "^4.9.0", "cropperjs": "^1.6.1", "dayjs": "^1.11.10", + "echarts": "^5.5.1", "highlight.js": "^11.9.0", "humanize-duration": "^3.27.3", "lodash-es": "^4.17.21", @@ -58,13 +59,14 @@ "sortablejs": "^1.15.2", "vue": "^3.4.21", "vue-cropperjs": "^5.0.0", + "vue-echarts": "^7.0.3", "vue-i18n": "^9.10.2", "vue-router": "^4.3.0", "vuedraggable": "^4.1.0" }, "devDependencies": { - "@certd/lib-iframe": "^1.26.16", - "@certd/pipeline": "^1.26.16", + "@certd/lib-iframe": "^1.27.0", + "@certd/pipeline": "^1.27.0", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@types/chai": "^4.3.12", diff --git a/packages/ui/certd-client/public/static/doc/images/1-add.png b/packages/ui/certd-client/public/static/doc/images/1-add.png index 2deaf39a3..65e487c6c 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/1-add.png and b/packages/ui/certd-client/public/static/doc/images/1-add.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/10-1-log.png b/packages/ui/certd-client/public/static/doc/images/10-1-log.png index 507d36dc5..d9f768376 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/10-1-log.png and b/packages/ui/certd-client/public/static/doc/images/10-1-log.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/11-1-error.png b/packages/ui/certd-client/public/static/doc/images/11-1-error.png index dc9f8c0f7..36a29db66 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/11-1-error.png and b/packages/ui/certd-client/public/static/doc/images/11-1-error.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/11-2-error.png b/packages/ui/certd-client/public/static/doc/images/11-2-error.png index 7fb4e895e..46d5b5af8 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/11-2-error.png and b/packages/ui/certd-client/public/static/doc/images/11-2-error.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/12-1-log-success.png b/packages/ui/certd-client/public/static/doc/images/12-1-log-success.png index fd8d2c617..1e858962e 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/12-1-log-success.png and b/packages/ui/certd-client/public/static/doc/images/12-1-log-success.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/12-2-skip-log.png b/packages/ui/certd-client/public/static/doc/images/12-2-skip-log.png index cca26b3c9..3bd46cc91 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/12-2-skip-log.png and b/packages/ui/certd-client/public/static/doc/images/12-2-skip-log.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/13-1-result.png b/packages/ui/certd-client/public/static/doc/images/13-1-result.png index 04feadda1..5cb7ab95a 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/13-1-result.png and b/packages/ui/certd-client/public/static/doc/images/13-1-result.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/13-3-download.png b/packages/ui/certd-client/public/static/doc/images/13-3-download.png index 3a4a0cb7a..c7dcf820d 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/13-3-download.png and b/packages/ui/certd-client/public/static/doc/images/13-3-download.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/14-timer.png b/packages/ui/certd-client/public/static/doc/images/14-timer.png index f4612c563..4f5071273 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/14-timer.png and b/packages/ui/certd-client/public/static/doc/images/14-timer.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/3-add-access.png b/packages/ui/certd-client/public/static/doc/images/3-add-access.png deleted file mode 100644 index 563c95b67..000000000 Binary files a/packages/ui/certd-client/public/static/doc/images/3-add-access.png and /dev/null differ diff --git a/packages/ui/certd-client/public/static/doc/images/3-add-success.png b/packages/ui/certd-client/public/static/doc/images/3-add-success.png new file mode 100644 index 000000000..03467d71f Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/3-add-success.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/4-add-success.png b/packages/ui/certd-client/public/static/doc/images/4-add-success.png deleted file mode 100644 index 57ecf83b6..000000000 Binary files a/packages/ui/certd-client/public/static/doc/images/4-add-success.png and /dev/null differ diff --git a/packages/ui/certd-client/public/static/doc/images/5-1-add-host.png b/packages/ui/certd-client/public/static/doc/images/5-1-add-host.png new file mode 100644 index 000000000..35297a3f7 Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/5-1-add-host.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/5-2-add-host.png b/packages/ui/certd-client/public/static/doc/images/5-2-add-host.png new file mode 100644 index 000000000..875f6956b Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/5-2-add-host.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/5-3-add-host.png b/packages/ui/certd-client/public/static/doc/images/5-3-add-host.png new file mode 100644 index 000000000..02b2003c3 Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/5-3-add-host.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/5-4-add-host.png b/packages/ui/certd-client/public/static/doc/images/5-4-add-host.png new file mode 100644 index 000000000..47464c6a0 Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/5-4-add-host.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/5-5-plugin-list.png b/packages/ui/certd-client/public/static/doc/images/5-5-plugin-list.png new file mode 100644 index 000000000..8de28a537 Binary files /dev/null and b/packages/ui/certd-client/public/static/doc/images/5-5-plugin-list.png differ diff --git a/packages/ui/certd-client/public/static/doc/images/9-start.png b/packages/ui/certd-client/public/static/doc/images/9-start.png index 18028bb5b..fd507acf9 100644 Binary files a/packages/ui/certd-client/public/static/doc/images/9-start.png and b/packages/ui/certd-client/public/static/doc/images/9-start.png differ diff --git a/packages/ui/certd-client/public/static/icons/demo_index.html b/packages/ui/certd-client/public/static/icons/demo_index.html index 2e0185218..cda06fa12 100644 --- a/packages/ui/certd-client/public/static/icons/demo_index.html +++ b/packages/ui/certd-client/public/static/icons/demo_index.html @@ -6,9 +6,9 @@ - - - + + + @@ -38,7 +38,7 @@

- +

    - + +
  • + +
    华为
    +
    &#xe610;
    +
  • +
  • qiniuyun
    &#xe603;
  • - +
  • aliyun
    &#xe601;
  • - +
  • 腾讯云
    &#xe747;
  • - +
  • doge
    &#xe605;
  • - +
  • bt
    &#xe600;
  • - +

Unicode 引用

@@ -102,7 +108,7 @@
@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.svg?t=1727153857332#iconfont') format('svg');
+  src: url('iconfont.svg?t=1730278432006#iconfont') format('svg');
 }
 

第二步:定义使用 iconfont 的样式

@@ -127,7 +133,16 @@
    - + +
  • + +
    + 华为 +
    +
    .icon-huawei +
    +
  • +
  • @@ -136,7 +151,7 @@
    .icon-qiniuyun
  • - +
  • @@ -145,7 +160,7 @@
    .icon-aliyun
  • - +
  • @@ -154,7 +169,7 @@
    .icon-tencentcloud
  • - +
  • @@ -163,7 +178,7 @@
    .icon-dogecloud
  • - +
  • @@ -172,7 +187,7 @@
    .icon-bt
  • - +

font-class 引用

@@ -199,7 +214,15 @@
    - + +
  • + +
    华为
    +
    #icon-huawei
    +
  • +
  • qiniuyun
    #icon-qiniuyun
  • - +
  • aliyun
    #icon-aliyun
  • - +
  • 腾讯云
    #icon-tencentcloud
  • - +
  • doge
    #icon-dogecloud
  • - +
  • bt
    #icon-bt
  • - +

Symbol 引用

diff --git a/packages/ui/certd-client/public/static/icons/iconfont.css b/packages/ui/certd-client/public/static/icons/iconfont.css index 6c7f524ad..748656a77 100644 --- a/packages/ui/certd-client/public/static/icons/iconfont.css +++ b/packages/ui/certd-client/public/static/icons/iconfont.css @@ -1,6 +1,6 @@ @font-face { font-family: "iconfont"; /* Project id 4688792 */ - src: url('iconfont.svg?t=1727153857332#iconfont') format('svg'); + src: url('iconfont.svg?t=1730278432006#iconfont') format('svg'); } .iconfont { @@ -11,6 +11,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-huawei:before { + content: "\e610"; +} + .icon-qiniuyun:before { content: "\e603"; } diff --git a/packages/ui/certd-client/public/static/icons/iconfont.js b/packages/ui/certd-client/public/static/icons/iconfont.js index 4106c7cea..e99de3372 100644 --- a/packages/ui/certd-client/public/static/icons/iconfont.js +++ b/packages/ui/certd-client/public/static/icons/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4688792='',(e=>{var t=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var l,a,i,n,o,d=function(t,c){c.parentNode.insertBefore(t,c)};if(t&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}l=function(){var t,c=document.createElement("div");c.innerHTML=e._iconfont_svg_string_4688792,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(t=document.body).firstChild?d(c,t.firstChild):t.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(a=function(){document.removeEventListener("DOMContentLoaded",a,!1),l()},document.addEventListener("DOMContentLoaded",a,!1)):document.attachEvent&&(i=l,n=e.document,o=!1,s(),n.onreadystatechange=function(){"complete"==n.readyState&&(n.onreadystatechange=null,h())})}function h(){o||(o=!0,i())}function s(){try{n.documentElement.doScroll("left")}catch(t){return void setTimeout(s,50)}h()}})(window); \ No newline at end of file +window._iconfont_svg_string_4688792='',(e=>{var t=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var l,a,i,n,o,d=function(t,c){c.parentNode.insertBefore(t,c)};if(t&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}l=function(){var t,c=document.createElement("div");c.innerHTML=e._iconfont_svg_string_4688792,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(t=document.body).firstChild?d(c,t.firstChild):t.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(a=function(){document.removeEventListener("DOMContentLoaded",a,!1),l()},document.addEventListener("DOMContentLoaded",a,!1)):document.attachEvent&&(i=l,n=e.document,o=!1,s(),n.onreadystatechange=function(){"complete"==n.readyState&&(n.onreadystatechange=null,h())})}function h(){o||(o=!0,i())}function s(){try{n.documentElement.doScroll("left")}catch(t){return void setTimeout(s,50)}h()}})(window); \ No newline at end of file diff --git a/packages/ui/certd-client/public/static/icons/iconfont.json b/packages/ui/certd-client/public/static/icons/iconfont.json index 4478ceadc..d70e57b16 100644 --- a/packages/ui/certd-client/public/static/icons/iconfont.json +++ b/packages/ui/certd-client/public/static/icons/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "24164616", + "name": "华为", + "font_class": "huawei", + "unicode": "e610", + "unicode_decimal": 58896 + }, { "icon_id": "9612999", "name": "qiniuyun", diff --git a/packages/ui/certd-client/public/static/icons/iconfont.svg b/packages/ui/certd-client/public/static/icons/iconfont.svg index 899cdaf27..0f3f761c3 100644 --- a/packages/ui/certd-client/public/static/icons/iconfont.svg +++ b/packages/ui/certd-client/public/static/icons/iconfont.svg @@ -14,7 +14,9 @@ /> - + + + diff --git a/packages/ui/certd-client/src/api/modules/api.user.ts b/packages/ui/certd-client/src/api/modules/api.user.ts index 735c3bf54..6c757fa38 100644 --- a/packages/ui/certd-client/src/api/modules/api.user.ts +++ b/packages/ui/certd-client/src/api/modules/api.user.ts @@ -18,6 +18,7 @@ export interface UserInfoRes { id: string | number; username: string; nickName: string; + avatar: string; roleIds: number[]; } diff --git a/packages/ui/certd-client/src/api/service.ts b/packages/ui/certd-client/src/api/service.ts index ec55a8408..df000c1f6 100644 --- a/packages/ui/certd-client/src/api/service.ts +++ b/packages/ui/certd-client/src/api/service.ts @@ -27,6 +27,12 @@ function createService() { } // dataAxios 是 axios 返回数据中的 data const dataAxios = response.data; + + if (response.config.unpack === false) { + //如果不需要解包 + return dataAxios; + } + // 这个状态码是和后端约定的 const { code } = dataAxios; // 根据 code 进行判断 @@ -40,10 +46,6 @@ function createService() { case 0: // [ 示例 ] code === 0 代表没有错误 // @ts-ignore - if (response.config.unpack === false) { - //如果不需要解包 - return dataAxios; - } return dataAxios.data; default: // 不是正确的 code diff --git a/packages/ui/certd-client/src/components/cron-editor/index.vue b/packages/ui/certd-client/src/components/cron-editor/index.vue index e53db4d20..d9091185b 100644 --- a/packages/ui/certd-client/src/components/cron-editor/index.vue +++ b/packages/ui/certd-client/src/components/cron-editor/index.vue @@ -65,6 +65,9 @@ const onError = (error: any) => { }; const nextTime = computed(() => { + if (props.modelValue == null) { + return "请先设置正确的cron表达式"; + } try { const interval = parser.parseExpression(props.modelValue); const next = interval.next().getTime(); diff --git a/packages/ui/certd-client/src/components/tutorial/index.vue b/packages/ui/certd-client/src/components/tutorial/index.vue index 844ecb9c7..d7f196e0a 100644 --- a/packages/ui/certd-client/src/components/tutorial/index.vue +++ b/packages/ui/certd-client/src/components/tutorial/index.vue @@ -6,23 +6,16 @@ const openedRef = ref(false); function open() { openedRef.value = true; } -function close() { - openedRef.value = false; -} -function prev() { - console.log("prev"); -} -function next() { - console.log("next"); -} - -const settingStore = useSettingStore(); +const slots = defineSlots(); diff --git a/packages/ui/certd-client/src/views/sys/console/api.ts b/packages/ui/certd-client/src/views/sys/console/api.ts new file mode 100644 index 000000000..b17537507 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/console/api.ts @@ -0,0 +1,8 @@ +import { request } from "/@/api/service"; + +export async function GetStatisticCount() { + return await request({ + url: "/sys/statistic/count", + method: "POST" + }); +} diff --git a/packages/ui/certd-client/src/views/sys/console/index.vue b/packages/ui/certd-client/src/views/sys/console/index.vue new file mode 100644 index 000000000..4651b0128 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/console/index.vue @@ -0,0 +1,77 @@ + + + + diff --git a/packages/ui/certd-server/.gitattributes b/packages/ui/certd-server/.gitattributes index a9167bf8b..000166e5e 100644 --- a/packages/ui/certd-server/.gitattributes +++ b/packages/ui/certd-server/.gitattributes @@ -1 +1,2 @@ -./tools/**/* filter=lfs diff=lfs merge=lfs -text +tools/** filter=lfs diff=lfs merge=lfs -text + diff --git a/packages/ui/certd-server/.gitignore b/packages/ui/certd-server/.gitignore index 31f7870e5..f462ff3e8 100755 --- a/packages/ui/certd-server/.gitignore +++ b/packages/ui/certd-server/.gitignore @@ -19,3 +19,6 @@ run/ .clinic .env.pgpl.yaml + +tools/windows/* +!tools/windows/*.zip diff --git a/packages/ui/certd-server/CHANGELOG.md b/packages/ui/certd-server/CHANGELOG.md index 7198e45cd..cde3262a6 100644 --- a/packages/ui/certd-server/CHANGELOG.md +++ b/packages/ui/certd-server/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31) + +### Bug Fixes + +* 修复历史记录不能按名称查询的bug ([6113c38](https://github.com/certd/certd/commit/6113c388b7fc58b11ca19ff05cc1286d096c8d28)) + +### Features + +* 首页全新改版 ([63ec5b5](https://github.com/certd/certd/commit/63ec5b5519c760a3330569c0da6dac157302a330)) + +### Performance Improvements + +* 管理控制台数据统计 ([babd589](https://github.com/certd/certd/commit/babd5897ae013ff7c04ebfcbfac8a00d84dd627c)) +* lego 升级到 4.19.2 ([129bf53](https://github.com/certd/certd/commit/129bf53edc9bbb001fe49fbd7e239bd1d09cc128)) + ## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30) ### Performance Improvements diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 19cb5f181..57e3306d9 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-server", - "version": "1.26.16", + "version": "1.27.0", "description": "fast-server base midway", "private": true, "type": "module", @@ -27,17 +27,17 @@ }, "dependencies": { "@alicloud/pop-core": "^1.7.10", - "@certd/acme-client": "^1.26.16", - "@certd/commercial-core": "^1.26.16", - "@certd/lib-huawei": "^1.26.16", - "@certd/lib-jdcloud": "^1.26.16", - "@certd/lib-k8s": "^1.26.16", - "@certd/lib-server": "^1.26.16", - "@certd/midway-flyway-js": "^1.26.16", - "@certd/pipeline": "^1.26.16", - "@certd/plugin-cert": "^1.26.16", - "@certd/plugin-plus": "^1.26.16", - "@certd/plus-core": "^1.26.16", + "@certd/acme-client": "^1.27.0", + "@certd/commercial-core": "^1.27.0", + "@certd/lib-huawei": "^1.27.0", + "@certd/lib-jdcloud": "^1.27.0", + "@certd/lib-k8s": "^1.27.0", + "@certd/lib-server": "^1.27.0", + "@certd/midway-flyway-js": "^1.27.0", + "@certd/pipeline": "^1.27.0", + "@certd/plugin-cert": "^1.27.0", + "@certd/plugin-plus": "^1.27.0", + "@certd/plus-core": "^1.27.0", "@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120", "@huaweicloud/huaweicloud-sdk-core": "^3.1.120", "@koa/cors": "^5.0.0", @@ -60,6 +60,7 @@ "cache-manager": "^6.1.0", "cos-nodejs-sdk-v5": "^2.14.6", "cron-parser": "^4.9.0", + "cross-env": "^7.0.3", "dayjs": "^1.11.7", "form-data": "^4.0.0", "glob": "^11.0.0", @@ -73,7 +74,7 @@ "lodash-es": "^4.17.21", "log4js": "^6.7.1", "lru-cache": "^11.0.1", - "md5": "^2.3.0", + "mwts": "^1.3.0", "mwtsc": "^1.4.0", "nanoid": "^5.0.7", "node-forge": "^1.3.1", @@ -89,7 +90,6 @@ "ssh2": "^1.15.0", "strip-ansi": "^7.1.0", "svg-captcha": "^1.4.0", - "syno": "^2.2.0", "tencentcloud-sdk-nodejs": "^4.0.44", "typeorm": "^0.3.20", "uuid": "^10.0.0" @@ -106,9 +106,7 @@ "@types/nodemailer": "^6.4.8", "@types/ssh2": "^1.15.0", "c8": "^10.1.2", - "cross-env": "^7.0.3", "mocha": "^10.2.0", - "mwts": "^1.3.0", "prettier": "^3.3.3", "rimraf": "^5.0.5", "ts-node": "^10.9.2", diff --git a/packages/ui/certd-server/src/controller/basic/app-controller.ts b/packages/ui/certd-server/src/controller/basic/app-controller.ts new file mode 100644 index 000000000..9aafc0a3a --- /dev/null +++ b/packages/ui/certd-server/src/controller/basic/app-controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get, Provide } from '@midwayjs/core'; +import { BaseController, Constants } from '@certd/lib-server'; +import { http, logger } from '@certd/pipeline'; +/** + */ +@Provide() +@Controller('/api/app/') +export class AppController extends BaseController { + @Get('/latest', { summary: Constants.per.authOnly }) + async latest(): Promise { + const res = await http.request({ + url: 'https://registry.npmmirror.com/@certd/pipeline', + method: 'get', + }); + try { + const latest = res['dist-tags'].latest; + return this.ok(latest); + } catch (e: any) { + logger.error(e); + return this.ok(''); + } + } +} diff --git a/packages/ui/certd-server/src/controller/basic/home-controller.ts b/packages/ui/certd-server/src/controller/basic/home-controller.ts index e74723f76..013c6ba54 100644 --- a/packages/ui/certd-server/src/controller/basic/home-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/home-controller.ts @@ -1,5 +1,4 @@ -import { MidwayEnvironmentService } from '@midwayjs/core'; -import { Controller, Get, Inject, Provide } from '@midwayjs/core'; +import { Controller, Get, Inject, MidwayEnvironmentService, Provide } from '@midwayjs/core'; import { logger } from '@certd/pipeline'; import { Constants } from '@certd/lib-server'; diff --git a/packages/ui/certd-server/src/controller/dashboard/statistic-controller.ts b/packages/ui/certd-server/src/controller/dashboard/statistic-controller.ts new file mode 100644 index 000000000..1b68e7495 --- /dev/null +++ b/packages/ui/certd-server/src/controller/dashboard/statistic-controller.ts @@ -0,0 +1,47 @@ +import { Controller, Inject, Post, Provide } from '@midwayjs/core'; +import { BaseController, Constants } from '@certd/lib-server'; +import { UserService } from '../../modules/sys/authority/service/user-service.js'; +import { RoleService } from '../../modules/sys/authority/service/role-service.js'; +import { PipelineService } from '../../modules/pipeline/service/pipeline-service.js'; +import { HistoryService } from '../../modules/pipeline/service/history-service.js'; + +export type ChartItem = { + name: string; + value: number; +}; +export type UserStatisticCount = { + pipelineCount?: number; + pipelineStatusCount?: ChartItem[]; + historyCountPerDay: ChartItem[]; + expiringList: any[]; +}; +/** + */ +@Provide() +@Controller('/api/statistic/') +export class StatisticController extends BaseController { + @Inject() + userService: UserService; + @Inject() + roleService: RoleService; + + @Inject() + pipelineService: PipelineService; + @Inject() + historyService: HistoryService; + + @Post('/count', { summary: Constants.per.authOnly }) + public async count() { + const pipelineCount = await this.pipelineService.count({ userId: this.getUserId() }); + const pipelineStatusCount = await this.pipelineService.statusCount({ userId: this.getUserId() }); + const historyCount = await this.historyService.countPerDay({ userId: this.getUserId(), days: 7 }); + const expiringList = await this.pipelineService.latestExpiringList({ userId: this.getUserId(), count: 5 }); + const count: UserStatisticCount = { + pipelineCount, + pipelineStatusCount, + historyCountPerDay: historyCount, + expiringList, + }; + return this.ok(count); + } +} diff --git a/packages/ui/certd-server/src/controller/pipeline/access-controller.ts b/packages/ui/certd-server/src/controller/pipeline/access-controller.ts index 4b1df6831..004f12a70 100644 --- a/packages/ui/certd-server/src/controller/pipeline/access-controller.ts +++ b/packages/ui/certd-server/src/controller/pipeline/access-controller.ts @@ -1,7 +1,6 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; -import { CrudController } from '@certd/lib-server'; +import { Constants, CrudController } from '@certd/lib-server'; import { AccessService } from '../../modules/pipeline/service/access-service.js'; -import { Constants } from '@certd/lib-server'; /** * 授权 @@ -78,9 +77,6 @@ export class AccessController extends CrudController { const list = this.service.getDefineList(); const dict = []; for (const item of list) { - if (item?.deprecated) { - continue; - } dict.push({ value: item.name, label: item.title, diff --git a/packages/ui/certd-server/src/controller/pipeline/history-controller.ts b/packages/ui/certd-server/src/controller/pipeline/history-controller.ts index f090c13d4..f30500ee0 100644 --- a/packages/ui/certd-server/src/controller/pipeline/history-controller.ts +++ b/packages/ui/certd-server/src/controller/pipeline/history-controller.ts @@ -47,6 +47,7 @@ export class HistoryController extends CrudController { let pipelineIds: any = null; const pipelineTitle = body.query?.pipelineTitle; + delete body.query.pipelineTitle; if (pipelineTitle) { const pipelines = await this.pipelineService.list({ query: pipelineQuery, diff --git a/packages/ui/certd-server/src/controller/pipeline/pipeline-controller.ts b/packages/ui/certd-server/src/controller/pipeline/pipeline-controller.ts index fb0eebc9c..ecbf5f2ad 100644 --- a/packages/ui/certd-server/src/controller/pipeline/pipeline-controller.ts +++ b/packages/ui/certd-server/src/controller/pipeline/pipeline-controller.ts @@ -103,4 +103,10 @@ export class PipelineController extends CrudController { await this.service.cancel(historyId); return this.ok({}); } + + @Post('/count', { summary: Constants.per.authOnly }) + async count() { + const count = await this.service.count({ userId: this.getUserId() }); + return this.ok({ count }); + } } diff --git a/packages/ui/certd-server/src/controller/sys/console/statistic-controller.ts b/packages/ui/certd-server/src/controller/sys/console/statistic-controller.ts new file mode 100644 index 000000000..1b6d39e03 --- /dev/null +++ b/packages/ui/certd-server/src/controller/sys/console/statistic-controller.ts @@ -0,0 +1,51 @@ +import { Controller, Inject, Post, Provide } from '@midwayjs/core'; +import { BaseController } from '@certd/lib-server'; +import { UserService } from '../../../modules/sys/authority/service/user-service.js'; +import { RoleService } from '../../../modules/sys/authority/service/role-service.js'; +import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js'; +import { HistoryService } from '../../../modules/pipeline/service/history-service.js'; + +export type ChartItem = { + name: string; + value: number; +}; +export type SysStatisticCount = { + userCount: number; + pipelineCount?: number; + historyCountPerDay: ChartItem[]; + userRegisterCountPerDay: ChartItem[]; + pipelineCreateCountPerDay: ChartItem[]; +}; +/** + */ +@Provide() +@Controller('/api/sys/statistic/') +export class SysStatisticController extends BaseController { + @Inject() + userService: UserService; + @Inject() + roleService: RoleService; + + @Inject() + pipelineService: PipelineService; + @Inject() + historyService: HistoryService; + + @Post('/count', { summary: 'sys:settings:view' }) + public async count() { + const userCount = await this.userService.count(); + const userRegisterCountPerDay = await this.userService.registerCountPerDay({ days: 7 }); + const pipelineCreateCountPerDay = await this.pipelineService.createCountPerDay({ days: 7 }); + const pipelineCount = await this.pipelineService.count({}); + const historyCountPerDay = await this.historyService.countPerDay({ days: 7 }); + + const count: SysStatisticCount = { + userCount, + userRegisterCountPerDay, + pipelineCount, + pipelineCreateCountPerDay, + historyCountPerDay, + }; + return this.ok(count); + } +} diff --git a/packages/ui/certd-server/src/modules/db/d.ts b/packages/ui/certd-server/src/modules/db/d.ts new file mode 100644 index 000000000..c627f3d27 --- /dev/null +++ b/packages/ui/certd-server/src/modules/db/d.ts @@ -0,0 +1,3 @@ +export interface SqlAdapter { + date(columnName: string): string; +} diff --git a/packages/ui/certd-server/src/modules/db/index.ts b/packages/ui/certd-server/src/modules/db/index.ts new file mode 100644 index 000000000..d226acfd9 --- /dev/null +++ b/packages/ui/certd-server/src/modules/db/index.ts @@ -0,0 +1,34 @@ +import { SqliteAdapter } from './sqlite.js'; +import { PostgresqlAdapter } from './postgresql.js'; +import { Config, Init, Provide, Scope, ScopeEnum } from '@midwayjs/core'; +import { SqlAdapter } from './d.js'; + +@Provide() +@Scope(ScopeEnum.Singleton) +export class DbAdapter implements SqlAdapter { + adapter: SqlAdapter; + @Config('typeorm.dataSource.default.type') + dbType: string; + + @Init() + async init() { + if (this.isSqlite()) { + this.adapter = new SqliteAdapter(); + } else if (this.isPostgresql()) { + this.adapter = new PostgresqlAdapter(); + } else { + throw new Error(`dbType ${this.dbType} not support`); + } + } + + isSqlite() { + return this.dbType === 'better-sqlite3'; + } + isPostgresql() { + return this.dbType === 'postgres'; + } + + date(columnName: string) { + return this.adapter.date(columnName); + } +} diff --git a/packages/ui/certd-server/src/modules/db/postgresql.ts b/packages/ui/certd-server/src/modules/db/postgresql.ts new file mode 100644 index 000000000..b7c05fa0d --- /dev/null +++ b/packages/ui/certd-server/src/modules/db/postgresql.ts @@ -0,0 +1,7 @@ +import { SqlAdapter } from './d.js'; + +export class PostgresqlAdapter implements SqlAdapter { + date(columnName: string) { + return `to_char(${columnName}, 'YYYY-MM-DD')`; + } +} diff --git a/packages/ui/certd-server/src/modules/db/sqlite.ts b/packages/ui/certd-server/src/modules/db/sqlite.ts new file mode 100644 index 000000000..3caedcdc6 --- /dev/null +++ b/packages/ui/certd-server/src/modules/db/sqlite.ts @@ -0,0 +1,7 @@ +import { SqlAdapter } from './d.js'; + +export class SqliteAdapter implements SqlAdapter { + date(columnName: string) { + return `date(${columnName}, 'localtime')`; + } +} diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts index f58db5eac..cae66c990 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts @@ -1,14 +1,14 @@ import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import { In, Repository } from 'typeorm'; +import { In, MoreThan, Repository } from 'typeorm'; import { BaseService, PageReq } from '@certd/lib-server'; import { HistoryEntity } from '../entity/history.js'; import { PipelineEntity } from '../entity/pipeline.js'; import { HistoryDetail } from '../entity/vo/history-detail.js'; import { HistoryLogService } from './history-log-service.js'; -import { FileItem, Pipeline, RunnableCollection } from '@certd/pipeline'; -import { FileStore } from '@certd/pipeline'; -import { logger } from '@certd/pipeline'; +import { FileItem, FileStore, logger, Pipeline, RunnableCollection } from '@certd/pipeline'; +import dayjs from 'dayjs'; +import { DbAdapter } from '../../db/index.js'; /** * 证书申请 @@ -24,6 +24,9 @@ export class HistoryService extends BaseService { @Inject() logService: HistoryLogService; + @Inject() + dbAdapter: DbAdapter; + @Config('certd') private certdConfig: any; @@ -174,4 +177,23 @@ export class HistoryService extends BaseService { logger.error('删除文件失败', e); } } + + async countPerDay(param: { days: number; userId?: any }) { + const todayEnd = dayjs().endOf('day'); + const where: any = { + createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()), + }; + if (param.userId > 0) { + where.userId = param.userId; + } + const result = await this.getRepository() + .createQueryBuilder('main') + .select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期 + .addSelect('COUNT(1) AS count') + .where(where) + .groupBy('date') + .getRawMany(); + + return result; + } } diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index 83ebd30f0..fae388a8e 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -1,6 +1,6 @@ import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import { In, Repository } from 'typeorm'; +import { In, MoreThan, Repository } from 'typeorm'; import { BaseService, NeedVIPException, PageReq, SysPublicSettings, SysSettingsService } from '@certd/lib-server'; import { PipelineEntity } from '../entity/pipeline.js'; import { PipelineDetail } from '../entity/vo/pipeline-detail.js'; @@ -19,6 +19,8 @@ import { AccessGetter } from './access-getter.js'; import { CnameRecordService } from '../../cname/service/cname-record-service.js'; import { CnameProxyService } from './cname-proxy-service.js'; import { PluginConfigGetter } from '../../plugin/service/plugin-config-getter.js'; +import dayjs from 'dayjs'; +import { DbAdapter } from '../../db/index.js'; const runningTasks: Map = new Map(); const freeCount = 10; @@ -59,6 +61,9 @@ export class PipelineService extends BaseService { @Config('certd') private certdConfig: any; + @Inject() + dbAdapter: DbAdapter; + //@ts-ignore getRepository() { return this.repository; @@ -71,11 +76,18 @@ export class PipelineService extends BaseService { async page(pageReq: PageReq) { const result = await super.page(pageReq); + await this.fillLastVars(result.records); + + return result; + } + + private async fillLastVars(records: PipelineEntity[]) { const pipelineIds: number[] = []; const recordMap = {}; - for (const record of result.records) { + for (const record of records) { pipelineIds.push(record.id); recordMap[record.id] = record; + record.title = record.title + ''; } if (pipelineIds?.length > 0) { const vars = await this.storageService.findPipelineVars(pipelineIds); @@ -87,8 +99,6 @@ export class PipelineService extends BaseService { } } } - - return result; } public async registerTriggerById(pipelineId) { @@ -467,4 +477,64 @@ export class PipelineService extends BaseService { logEntity.logs = JSON.stringify(history.logs); await this.historyLogService.addOrUpdate(logEntity); } + + async count(param: { userId?: any }) { + const count = await this.repository.count({ + where: { + userId: param.userId, + }, + }); + return count; + } + + async statusCount(param: { userId?: any } = {}) { + const statusCount = await this.repository + .createQueryBuilder() + .select('status') + .addSelect('count(1)', 'count') + .where({ + userId: param.userId, + }) + .groupBy('status') + .getRawMany(); + return statusCount; + } + + async latestExpiringList({ userId }: any) { + let list = await this.repository.find({ + select: { + id: true, + title: true, + status: true, + }, + where: { + userId, + }, + }); + await this.fillLastVars(list); + list = list.filter(item => { + return item.lastVars?.certExpiresTime != null; + }); + list = list.sort((a, b) => { + return a.lastVars.certExpiresTime - b.lastVars.certExpiresTime; + }); + + return list.slice(0, 5); + } + + async createCountPerDay(param: { days: number } = { days: 7 }) { + const todayEnd = dayjs().endOf('day'); + const result = await this.getRepository() + .createQueryBuilder('main') + .select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期 + .addSelect('COUNT(1) AS count') + .where({ + // 0点 + createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()), + }) + .groupBy('date') + .getRawMany(); + + return result; + } } diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/role-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/role-service.ts index 0af16006e..b3b8379cb 100644 --- a/packages/ui/certd-server/src/modules/sys/authority/service/role-service.ts +++ b/packages/ui/certd-server/src/modules/sys/authority/service/role-service.ts @@ -49,6 +49,9 @@ export class RoleService extends BaseService { } async getPermissionByRoleIds(roleIds: any) { + if (!roleIds || roleIds.length === 0) { + return []; + } return await this.permissionService.repository .createQueryBuilder('permission') .innerJoinAndSelect(RolePermissionEntity, 'rp', 'rp.permissionId = permission.id and rp.roleId in (:...roleIds)', { roleIds }) diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts index 5748a6f42..59d308318 100644 --- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts @@ -1,21 +1,18 @@ import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import { Repository } from 'typeorm'; +import { MoreThan, Repository } from 'typeorm'; import { UserEntity } from '../entity/user.js'; import * as _ from 'lodash-es'; -import md5 from 'md5'; -import { CommonException, FileService } from '@certd/lib-server'; -import { BaseService } from '@certd/lib-server'; +import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server'; import { RoleService } from './role-service.js'; import { PermissionService } from './permission-service.js'; import { UserRoleService } from './user-role-service.js'; -import { Constants } from '@certd/lib-server'; import { UserRoleEntity } from '../entity/user-role.js'; import bcrypt from 'bcryptjs'; -import { SysSettingsService } from '@certd/lib-server'; -import { SysInstallInfo } from '@certd/lib-server'; import { RandomUtil } from '../../../../utils/random.js'; - +import dayjs from 'dayjs'; +import { DbAdapter } from '../../../db/index.js'; +import { utils } from '@certd/pipeline'; /** * 系统用户 */ @@ -36,6 +33,8 @@ export class UserService extends BaseService { @Inject() fileService: FileService; + @Inject() + dbAdapter: DbAdapter; //@ts-ignore getRepository() { @@ -116,7 +115,7 @@ export class UserService extends BaseService { private async genPassword(rawPassword: any, passwordVersion: number) { if (passwordVersion == null || passwordVersion <= 1) { - return md5(rawPassword); + return utils.hash.md5(rawPassword); } const salt = bcrypt.genSaltSync(10); const plainPassword = await this.buildPlainPassword(rawPassword); @@ -245,4 +244,29 @@ export class UserService extends BaseService { status, }); } + + async count(param: { userId?: any } = {}) { + const count = await this.repository.count({ + where: { + id: param.userId, + }, + }); + return count; + } + + async registerCountPerDay(param: { days: number } = { days: 7 }) { + const todayEnd = dayjs().endOf('day'); + const result = await this.getRepository() + .createQueryBuilder('main') + .select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期 + .addSelect('COUNT(1) AS count') + .where({ + // 0点 + createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()), + }) + .groupBy('date') + .getRawMany(); + + return result; + } } diff --git a/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts b/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts index 43576d301..43171d4d7 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts @@ -18,7 +18,8 @@ export class CloudflareAccess extends BaseAccess { component: { placeholder: 'api token,用户 API 令牌', }, - helper: '前往 https://dash.cloudflare.com/profile/api-tokens 获取API令牌, token权限必须包含:[Zone区域-Zone区域-Edit编辑], [Zone区域-DNS-Edit编辑]', + helper: + '前往 [获取API令牌](https://dash.cloudflare.com/profile/api-tokens),注意是令牌,不是密钥。\n token权限必须包含:[Zone区域-Zone区域-Edit编辑], [Zone区域-DNS-Edit编辑]', required: true, encrypt: true, }) diff --git a/packages/ui/certd-server/src/plugins/plugin-demo/access.ts b/packages/ui/certd-server/src/plugins/plugin-demo/access.ts index 4c6aeb869..e269fb2b3 100644 --- a/packages/ui/certd-server/src/plugins/plugin-demo/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-demo/access.ts @@ -1,5 +1,5 @@ import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline'; -import { isDev } from "../../utils/env.js"; +import { isDev } from '../../utils/env.js'; /** * 这个注解将注册一个授权配置 diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts index e64798cc0..f671577d3 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts @@ -6,6 +6,7 @@ import { SshClient } from '../../lib/ssh.js'; title: '执行远程主机脚本命令', icon: 'tabler:brand-powershell', group: pluginGroups.host.key, + desc: '可以执行重启nginx等操作让证书生效', input: {}, default: { strategy: { diff --git a/packages/ui/certd-server/src/plugins/plugin-huawei/plugins/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-huawei/plugins/deploy-to-cdn/index.ts index 677bcbd78..bbc395358 100644 --- a/packages/ui/certd-server/src/plugins/plugin-huawei/plugins/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-huawei/plugins/deploy-to-cdn/index.ts @@ -6,7 +6,7 @@ import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from @IsTaskPlugin({ name: 'HauweiDeployCertToCDN', title: '部署证书至华为云CDN', - icon: 'ant-design:huawei-outlined', + icon: 'svg:icon-huawei', group: pluginGroups.huawei.key, desc: '', default: { diff --git a/packages/ui/certd-server/src/utils/env.ts b/packages/ui/certd-server/src/utils/env.ts index 1427b8821..4757306ea 100644 --- a/packages/ui/certd-server/src/utils/env.ts +++ b/packages/ui/certd-server/src/utils/env.ts @@ -1,3 +1 @@ -export function isDev() { - return process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'local'; -} +export { isDev } from '@certd/pipeline'; diff --git a/packages/ui/certd-server/tools/linux/lego b/packages/ui/certd-server/tools/linux/lego deleted file mode 100644 index 1b3edf281..000000000 Binary files a/packages/ui/certd-server/tools/linux/lego and /dev/null differ diff --git a/packages/ui/certd-server/tools/linux/lego_linux_amd64.tar.gz b/packages/ui/certd-server/tools/linux/lego_linux_amd64.tar.gz new file mode 100644 index 000000000..18b1303d7 --- /dev/null +++ b/packages/ui/certd-server/tools/linux/lego_linux_amd64.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:588e41dfbc2a5ef1596953b605ef0981f95135443a1e66db3e06e1fa2c061eab +size 24746505 diff --git a/packages/ui/certd-server/tools/linux/lego_linux_arm64.tar.gz b/packages/ui/certd-server/tools/linux/lego_linux_arm64.tar.gz new file mode 100644 index 000000000..c0bc0e981 --- /dev/null +++ b/packages/ui/certd-server/tools/linux/lego_linux_arm64.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba47935bd3618ab13f2c10ba8589156e98617ab77f4dd101956cd21e829a6316 +size 23085284 diff --git a/packages/ui/certd-server/tools/windows/lego.exe b/packages/ui/certd-server/tools/windows/lego.exe deleted file mode 100644 index 87f9e07be..000000000 Binary files a/packages/ui/certd-server/tools/windows/lego.exe and /dev/null differ diff --git a/packages/ui/certd-server/tools/windows/lego_windows_amd64.zip b/packages/ui/certd-server/tools/windows/lego_windows_amd64.zip new file mode 100644 index 000000000..71dfe6bc5 --- /dev/null +++ b/packages/ui/certd-server/tools/windows/lego_windows_amd64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b91a1e4f32b3f5e55fa6e455b8a72069cd2983ec7a36d275f0aa4b0d82a0de6 +size 25579960