Compare commits

...

29 Commits

Author SHA1 Message Date
xiaojunnuo
67934cdebd v1.22.8 2024-08-05 13:19:57 +08:00
xiaojunnuo
6765a48706 chore: 2024-08-05 13:04:36 +08:00
xiaojunnuo
b4252033d5 perf: 优化pipeline删除时,删除其他history 2024-08-05 12:57:13 +08:00
xiaojunnuo
f78ae93eed perf: 修复删除历史记录没有删除log的bug,新增history管理页面,演示站点启动时不自动启动非管理员用户的定时任务 2024-08-05 12:49:44 +08:00
xiaojunnuo
0227155ab4 chore: 2024-08-04 22:46:08 +08:00
xiaojunnuo
330b84de33 build: trigger build image 2024-08-04 22:33:08 +08:00
xiaojunnuo
f47f86b669 v1.22.7 2024-08-04 22:32:13 +08:00
xiaojunnuo
95eeb93822 build: prepare to build 2024-08-04 22:30:36 +08:00
xiaojunnuo
367f807313 fix: 修复保存配置报id不能为空的bug 2024-08-04 22:25:51 +08:00
xiaojunnuo
a954629ff9 build: trigger build image 2024-08-04 02:55:27 +08:00
xiaojunnuo
3bbbc41062 v1.22.6 2024-08-04 02:53:50 +08:00
xiaojunnuo
bf63b0d73f build: prepare to build 2024-08-04 02:51:30 +08:00
xiaojunnuo
5362df55f4 chore: 1 2024-08-04 02:49:40 +08:00
xiaojunnuo
59897c4cea perf: 流水线支持名称模糊查询 2024-08-04 02:35:45 +08:00
xiaojunnuo
a9717b9a0d fix: 修复pg下pipeline title 类型问题 2024-08-04 00:04:55 +08:00
xiaojunnuo
680941af11 fix: 修复在相同的cron时偶尔无法触发定时任务的bug 2024-08-03 23:32:50 +08:00
xiaojunnuo
1cf8d4e5e7 chore: 2024-08-02 23:59:08 +08:00
xiaojunnuo
70ce6be0bf chore: 单元测试 2024-08-02 22:58:29 +08:00
xiaojunnuo
9187e87419 chore: 2024-08-02 09:29:26 +08:00
xiaojunnuo
6ed1e18c7d perf: 优化前置任务输出为空的提示 2024-08-02 09:26:54 +08:00
xiaojunnuo
8d27f07213 Merge remote-tracking branch 'origin/v2' into v2 2024-07-31 14:01:30 +08:00
xiaojunnuo
e4f4570b29 perf: 腾讯云clb支持更多大区选择 2024-07-31 14:01:06 +08:00
xiaojunnuo
d86fc9569a chore: 增加dns地址配置 2024-07-31 13:46:48 +08:00
xiaojunnuo
fa7a983bcb chore: 2024-07-26 23:48:15 +08:00
xiaojunnuo
9ac908ebee chore: 2024-07-26 23:44:57 +08:00
xiaojunnuo
6e594ee66e chore: 2024-07-26 23:43:41 +08:00
xiaojunnuo
c26d3e9c38 chore: 2024-07-26 23:38:51 +08:00
xiaojunnuo
5db5607faa chore: 2024-07-26 23:36:33 +08:00
xiaojunnuo
728f27e0a0 build: trigger build image 2024-07-26 23:16:17 +08:00
82 changed files with 1238 additions and 183 deletions

55
.github/workflows/deploy-demo.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: deploy-demo
on:
push:
branches: ['v2']
paths:
- "deploy.trigger"
workflow_run:
workflows: [ "build-image" ]
types:
- completed
# schedule:
# - # 国际时间 19:17 执行北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
# - cron: '17 19 * * *'
permissions:
contents: read
jobs:
deploy-certd-demo:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: get_certd_version
id: get_certd_version
uses: actions/github-script@v6
with:
result-encoding: string
script: |
const fs = require('fs');
const path = require('path');
const jsonFilePath = "./packages/ui/certd-server/package.json";
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
const pkg = JSON.parse(jsonContent)
console.log("certd_version:",pkg.version);
return pkg.version
- uses: GuillaumeFalourd/wait-sleep-action@v1
with:
time: '10' # for 60 seconds
- name: Send HTTP request
id: request
uses: tyrrrz/action-http-request@master
with:
url: http://flow-openapi.aliyun.com/pipeline/webhook/lzCzlGrLCOHQaTMMt0mG
method: POST
headers: |
Content-Type: application/json
body: |
{
"CERTD_VERSION": "${{steps.get_certd_version.outputs.result}}"
}
retry-count: 3
retry-delay: 5000

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
### Bug Fixes
* 修复保存配置报id不能为空的bug ([367f807](https://github.com/certd/certd/commit/367f80731396003416665c22853dfbc09c2c03a0))
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
### Bug Fixes
* 修复在相同的cron时偶尔无法触发定时任务的bug ([680941a](https://github.com/certd/certd/commit/680941af119619006b592e3ab6fb112cb5556a8b))
* 修复pg下pipeline title 类型问题 ([a9717b9](https://github.com/certd/certd/commit/a9717b9a0df7b5a64d4fe03314fecad4f59774cc))
### Performance Improvements
* 流水线支持名称模糊查询 ([59897c4](https://github.com/certd/certd/commit/59897c4ceae992ebe2972ca9e8f9196616ffdfd7))
* 腾讯云clb支持更多大区选择 ([e4f4570](https://github.com/certd/certd/commit/e4f4570b29f26c60f1ee9660a4c507cbeaba3d7e))
* 优化前置任务输出为空的提示 ([6ed1e18](https://github.com/certd/certd/commit/6ed1e18c7d9c46d964ecc6abc90f3908297b7632))
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
### Bug Fixes ### Bug Fixes

View File

@@ -1 +1 @@
6 22:33

1
deploy.trigger Normal file
View File

@@ -0,0 +1 @@
5

8
doc/server/free.md Normal file
View File

@@ -0,0 +1,8 @@
# 免费服务器部署
## 1. 注册koyeb账号
https://app.koyeb.com/
## 2. 创建应用

View File

@@ -11,6 +11,12 @@ services:
ports: # 端口映射 ports: # 端口映射
# ↓↓↓↓ ----------------------------------------------------------3、如果端口有冲突可以修改第一个7001为其他不冲突的端口号【可选】 # ↓↓↓↓ ----------------------------------------------------------3、如果端口有冲突可以修改第一个7001为其他不冲突的端口号【可选】
- "7001:7001" - "7001:7001"
dns:
# 如果出现getaddrinfo ENOTFOUND等错误可以尝试修改或注释dns配置
- 223.5.5.5
- 223.6.6.6
- 8.8.8.8
- 8.8.4.4
environment: # 环境变量 environment: # 环境变量
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- certd_system_resetAdminPassword=false - certd_system_resetAdminPassword=false

View File

@@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.22.5" "version": "1.22.8"
} }

View File

@@ -13,8 +13,9 @@
"start": "lerna bootstrap --hoist", "start": "lerna bootstrap --hoist",
"i-all": "lerna link && lerna exec npm install ", "i-all": "lerna link && lerna exec npm install ",
"publish": "npm run prepublishOnly1 && lerna publish --conventional-commits --create-release github && npm run afterpublishOnly", "publish": "npm run prepublishOnly1 && lerna publish --conventional-commits --create-release github && npm run afterpublishOnly",
"afterpublishOnly": "time /t >build.trigger && git add ./build.md && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && git push", "afterpublishOnly": "time /t >build.trigger && git add ./build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && git push",
"prepublishOnly1": "npm run check && npm run before-build && lerna run build ", "prepublishOnly1": "npm run check && lerna run build ",
"prepublishOnly2": "npm run check && npm run before-build && lerna run build ",
"before-build": "cd ./packages/core/pipeline && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"", "before-build": "cd ./packages/core/pipeline && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules deploy.js ", "deploy1": "node --experimental-json-modules deploy.js ",
"check": "node --experimental-json-modules publish-check.js", "check": "node --experimental-json-modules publish-check.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.6](https://github.com/publishlab/node-acme-client/compare/v1.22.5...v1.22.6) (2024-08-03)
**Note:** Version bump only for package @certd/acme-client
## [1.22.4](https://github.com/publishlab/node-acme-client/compare/v1.22.3...v1.22.4) (2024-07-26) ## [1.22.4](https://github.com/publishlab/node-acme-client/compare/v1.22.3...v1.22.4) (2024-07-26)
### Performance Improvements ### Performance Improvements

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.22.4", "version": "1.22.6",
"main": "src/index.js", "main": "src/index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
"license": "MIT", "license": "MIT",

View File

@@ -24,3 +24,5 @@ dist-ssr
*.sw? *.sw?
test/user.secret.* test/user.secret.*
test/**/*.js
src/**/*.spec.ts

View File

@@ -1,5 +1,4 @@
{ {
"extension": ["ts"], "extension": ["ts"],
"spec": "test/**/*.test.ts", "spec": "src/**/*.spec.ts"
"require": "ts-node/register" }
}

View File

@@ -1,2 +1,3 @@
node_modules node_modules
src src
dist/**/*.spec.*

View File

@@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/pipeline
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
### Bug Fixes
* 修复在相同的cron时偶尔无法触发定时任务的bug ([680941a](https://github.com/certd/certd/commit/680941af119619006b592e3ab6fb112cb5556a8b))
### Performance Improvements
* 流水线支持名称模糊查询 ([59897c4](https://github.com/certd/certd/commit/59897c4ceae992ebe2972ca9e8f9196616ffdfd7))
* 优化前置任务输出为空的提示 ([6ed1e18](https://github.com/certd/certd/commit/6ed1e18c7d9c46d964ecc6abc90f3908297b7632))
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
**Note:** Version bump only for package @certd/pipeline **Note:** Version bump only for package @certd/pipeline

View File

@@ -1 +1 @@
23:14 22:30

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/pipeline", "name": "@certd/pipeline",
"private": false, "private": false,
"version": "1.22.5", "version": "1.22.8",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -10,10 +10,10 @@
"build": "tsc --skipLibCheck", "build": "tsc --skipLibCheck",
"build3": "rollup -c", "build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build", "build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview" "preview": "vite preview",
"test": "mocha --loader=ts-node/esm"
}, },
"dependencies": { "dependencies": {
"@types/lodash-es": "^4.17.12",
"axios": "^1.7.2", "axios": "^1.7.2",
"fix-path": "^4.0.0", "fix-path": "^4.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -28,6 +28,7 @@
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.10", "@types/chai": "^4.3.10",
"@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node-forge": "^1.3.2", "@types/node-forge": "^1.3.2",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",

View File

@@ -213,7 +213,12 @@ export class Executor {
if (contextKey != null) { if (contextKey != null) {
const value = this.runtime.context[contextKey]; const value = this.runtime.context[contextKey];
if (value == null) { if (value == null) {
currentLogger.warn(`[step init] input ${define.title} is null`); currentLogger.warn(`[step init] input ${define.title} is null,前置任务步骤输出值为空,请按如下步骤排查:`);
currentLogger.log(`1、检查前置任务证书申请任务是否是配置了成功后跳过如果是请改为正常执行`);
currentLogger.log(
`2、是否曾经删除过前置任务证书申请任务然后又重新添加了如果是请重新编辑当前任务重新选择一下前置任务输出的参数域名证书那一栏`
);
currentLogger.log(`3、以上都不是联系作者吧`);
} }
step.input[key] = value; step.input[key] = value;
} }
@@ -241,18 +246,26 @@ export class Executor {
await instance.onInstance(); await instance.onInstance();
await instance.execute(); await instance.execute();
//执行结果处理
if (instance._result.clearLastStatus) { if (instance._result.clearLastStatus) {
//是否需要清除所有状态
this.lastStatusMap.clear(); this.lastStatusMap.clear();
} }
//输出到output context //输出上下文变量到output context
_.forEach(define.output, (item: any, key: any) => { _.forEach(define.output, (item: any, key: any) => {
step.status!.output[key] = instance[key]; step.status!.output[key] = instance[key];
const stepOutputKey = `step.${step.id}.${key}`; const stepOutputKey = `step.${step.id}.${key}`;
this.runtime.context[stepOutputKey] = instance[key]; this.runtime.context[stepOutputKey] = instance[key];
}); });
step.status!.files = instance.getFiles(); step.status!.files = instance.getFiles();
//更新pipeline vars
if (Object.keys(instance._result.pipelineVars).length > 0) {
// 判断 pipelineVars 有值时更新
const vars = this.pipelineContext.getObj("vars");
_.merge(vars, instance._result.pipelineVars);
await this.pipelineContext.setObj("vars", vars);
}
} }
async notification(when: NotificationWhen, error?: any) { async notification(when: NotificationWhen, error?: any) {

View File

@@ -18,7 +18,9 @@ export interface IFileStore {
export class FileStore { export class FileStore {
rootDir: string; rootDir: string;
// pipelineId
scope: string; scope: string;
// historyId
parent: string; parent: string;
constructor(options?: FileStoreOptions) { constructor(options?: FileStoreOptions) {
this.rootDir = fileUtils.getFileRootDir(options?.rootDir); this.rootDir = fileUtils.getFileRootDir(options?.rootDir);

View File

@@ -3,3 +3,4 @@ export * from "./run-history.js";
export * from "./context.js"; export * from "./context.js";
export * from "./storage.js"; export * from "./storage.js";
export * from "./file-store.js"; export * from "./file-store.js";
export * from "./license.js";

View File

@@ -0,0 +1,15 @@
import { isPlus, verify } from "./license.js";
import { equal } from "assert";
describe("license", function () {
it("#license", async function () {
const req = {
appKey: "z4nXOeTeSnnpUpnmsV",
subjectId: "999",
license: "",
};
const plus = isPlus();
equal(plus, false);
const res = await verify(req);
equal(res, true);
});
});

View File

@@ -0,0 +1,85 @@
import { createVerify } from "node:crypto";
import { logger } from "../utils/index.js";
const SecreteKey =
"LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXY3TGtMaUp1dGM0NzhTU3RaTExjajVGZXh1YjJwR2NLMGxwa0hwVnlZWjhMY29rRFhuUlAKUGQ5UlJSTVRTaGJsbFl2Mzd4QUhOV1ZIQ0ZsWHkrQklVU001bUlBU1NDQTV0azlJNmpZZ2F4bEFDQm1BY0lGMwozKzBjeGZIYVkrVW9YdVluMkZ6YUt2Ym5GdFZIZ0lkMDg4a3d4clZTZzlCT3BDRVZIR1pxR2I5TWN5MXVHVXhUClFTVENCbmpoTWZlZ0p6cXVPYWVOY0ZPSE5tbmtWRWpLTythbTBPeEhNS1lyS3ZnQnVEbzdoVnFENlBFMUd6V3AKZHdwZUV4QXZDSVJxL2pWTkdRK3FtMkRWOVNJZ3U5bmF4MktmSUtFeU50dUFFS1VpekdqL0VmRFhDM1cxMExhegpKaGNYNGw1SUFZU1o3L3JWVmpGbExWSVl0WDU1T054L1Z3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
const appKey = "z4nXOeTeSnnpUpnmsV";
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;
signature: string;
};
class LicenseHolder {
isPlus = false;
}
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) {
holder.isPlus = value;
return value;
}
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(Buffer.from(license, "hex").toString(), "base64").toString();
const json: License = JSON.parse(licenseJson);
if (json.expireTime < Date.now()) {
logger.warn("授权已过期");
return this.setPlus(false);
}
const content = `${appKey},${this.licenseReq.subjectId},${json.code},${json.secret},${json.activeTime},${json.duration},${json.expireTime},${json.version}`;
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);
}
return this.setPlus(true);
}
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;
}
export async function verify(req: LicenseVerifyReq) {
return await verifier.reVerify(req);
}

View File

@@ -51,6 +51,7 @@ export type ITaskPlugin = {
export type TaskResult = { export type TaskResult = {
clearLastStatus?: boolean; clearLastStatus?: boolean;
files?: FileItem[]; files?: FileItem[];
pipelineVars: Record<string, any>;
}; };
export type TaskInstanceContext = { export type TaskInstanceContext = {
pipeline: Pipeline; pipeline: Pipeline;
@@ -66,7 +67,7 @@ export type TaskInstanceContext = {
}; };
export abstract class AbstractTaskPlugin implements ITaskPlugin { export abstract class AbstractTaskPlugin implements ITaskPlugin {
_result: TaskResult = { clearLastStatus: false, files: [] }; _result: TaskResult = { clearLastStatus: false, files: [], pipelineVars: {} };
ctx!: TaskInstanceContext; ctx!: TaskInstanceContext;
clearLastStatus() { clearLastStatus() {
this._result.clearLastStatus = true; this._result.clearLastStatus = true;
@@ -83,12 +84,6 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
randomFileId() { randomFileId() {
return Math.random().toString(36).substring(2, 9); return Math.random().toString(36).substring(2, 9);
} }
linkFile(file: FileItem) {
this._result.files?.push({
...file,
id: this.randomFileId(),
});
}
saveFile(filename: string, file: Buffer) { saveFile(filename: string, file: Buffer) {
const filePath = this.ctx.fileStore.writeFile(filename, file); const filePath = this.ctx.fileStore.writeFile(filename, file);
logger.info(`saveFile:${filePath}`); logger.info(`saveFile:${filePath}`);

View File

@@ -5,4 +5,3 @@ export default function (timeout: number) {
}, timeout); }, timeout);
}); });
} }

View File

@@ -1,6 +1,6 @@
import { expect } from "chai"; import { expect } from "chai";
import "mocha"; import "mocha";
import { EchoPlugin } from "./echo-plugin"; import { EchoPlugin } from "./echo-plugin.js";
describe("task_plugin", function () { describe("task_plugin", function () {
it("#taskplugin", function () { it("#taskplugin", function () {
console.log("before new plugin"); console.log("before new plugin");

View File

@@ -0,0 +1 @@
license.*

View File

@@ -34,6 +34,7 @@
"exclude": [ "exclude": [
"*.js", "*.js",
"*.ts", "*.ts",
"*.spec.ts",
"dist", "dist",
"node_modules", "node_modules",
"test" "test"

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
**Note:** Version bump only for package @certd/lib-k8s
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/lib-k8s
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
**Note:** Version bump only for package @certd/lib-k8s
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
**Note:** Version bump only for package @certd/lib-k8s **Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-k8s", "name": "@certd/lib-k8s",
"private": false, "private": false,
"version": "1.22.5", "version": "1.22.8",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
"shelljs": "^0.8.5" "shelljs": "^0.8.5"
}, },
"devDependencies": { "devDependencies": {
"@certd/pipeline": "^1.22.5", "@certd/pipeline": "^1.22.8",
"@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.22.3](https://github.com/certd/certd/compare/v1.22.2...v1.22.3) (2024-07-25) ## [1.22.3](https://github.com/certd/certd/compare/v1.22.2...v1.22.3) (2024-07-25)
**Note:** Version bump only for package @certd/midway-flyway-js **Note:** Version bump only for package @certd/midway-flyway-js

View File

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

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
**Note:** Version bump only for package @certd/plugin-cert
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/plugin-cert
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
### Performance Improvements
* 流水线支持名称模糊查询 ([59897c4](https://github.com/certd/certd/commit/59897c4ceae992ebe2972ca9e8f9196616ffdfd7))
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
**Note:** Version bump only for package @certd/plugin-cert **Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-cert", "name": "@certd/plugin-cert",
"private": false, "private": false,
"version": "1.22.5", "version": "1.22.8",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -13,8 +13,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.22.4", "@certd/acme-client": "^1.22.6",
"@certd/pipeline": "^1.22.5", "@certd/pipeline": "^1.22.8",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"node-forge": "^0.10.0", "node-forge": "^0.10.0",
"psl": "^1.9.0" "psl": "^1.9.0"

View File

@@ -138,6 +138,8 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
const cert: CertInfo = certReader.toCertInfo(); const cert: CertInfo = certReader.toCertInfo();
this.cert = cert; this.cert = cert;
this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.validity.notAfter).valueOf();
if (isNew) { if (isNew) {
const applyTime = dayjs(certReader.detail.validity.notBefore).format("YYYYMMDD_HHmmss"); const applyTime = dayjs(certReader.detail.validity.notBefore).format("YYYYMMDD_HHmmss");
await this.zipCert(cert, applyTime); await this.zipCert(cert, applyTime);

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
**Note:** Version bump only for package @certd/ui-client
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
### Bug Fixes
* 修复在相同的cron时偶尔无法触发定时任务的bug ([680941a](https://github.com/certd/certd/commit/680941af119619006b592e3ab6fb112cb5556a8b))
### Performance Improvements
* 流水线支持名称模糊查询 ([59897c4](https://github.com/certd/certd/commit/59897c4ceae992ebe2972ca9e8f9196616ffdfd7))
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
**Note:** Version bump only for package @certd/ui-client **Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-client", "name": "@certd/ui-client",
"version": "1.22.5", "version": "1.22.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite --open",
@@ -57,7 +57,7 @@
"vuedraggable": "^2.24.3" "vuedraggable": "^2.24.3"
}, },
"devDependencies": { "devDependencies": {
"@certd/pipeline": "^1.22.5", "@certd/pipeline": "^1.22.8",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12", "@types/chai": "^4.3.12",

View File

@@ -1,9 +1,9 @@
import { request } from "../service"; import { request } from "../service";
export type SysPublicSetting = { export type SysPublicSetting = {
registerEnabled:boolean registerEnabled: boolean;
} managerOtherUserPipeline: boolean;
};
export async function getSysPublicSettings(): Promise<SysPublicSetting> { export async function getSysPublicSettings(): Promise<SysPublicSetting> {
return await request({ return await request({

View File

@@ -18,6 +18,7 @@ export interface UserInfoRes {
id: string | number; id: string | number;
username: string; username: string;
nickName: string; nickName: string;
roles: number[];
} }
export interface LoginRes { export interface LoginRes {

View File

@@ -61,7 +61,7 @@ function install(app: App, options: any = {}) {
}, },
size: "small", size: "small",
pagination: false, pagination: false,
onResizeColumn: (w: number, col: any) => { onResizeColumn: (w: number | string, col: any) => {
if (crudBinding.value?.table?.columnsMap && crudBinding.value?.table?.columnsMap[col.key]) { if (crudBinding.value?.table?.columnsMap && crudBinding.value?.table?.columnsMap[col.key]) {
crudBinding.value.table.columnsMap[col.key].width = w; crudBinding.value.table.columnsMap[col.key].width = w;
} }
@@ -336,6 +336,23 @@ function install(app: App, options: any = {}) {
return columnProps; return columnProps;
} }
}); });
registerMergeColumnPlugin({
name: "resize-column-plugin",
order: 2,
handle: (columnProps: ColumnCompositionProps) => {
if (!columnProps.column) {
columnProps.column = {};
}
columnProps.column.resizable = true;
if (!columnProps.column.width) {
columnProps.column.width = 100;
} else if (typeof columnProps.column?.width === "string" && columnProps.column.width.indexOf("px") > -1) {
columnProps.column.width = parseInt(columnProps.column.width.replace("px", ""));
}
return columnProps;
}
});
} }
export default { export default {

View File

@@ -27,6 +27,15 @@ export const certdResources = [
isMenu: false isMenu: false
} }
}, },
{
title: "执行历史记录",
name: "pipelineHistory",
path: "/certd/history",
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline"
}
},
{ {
title: "授权管理", title: "授权管理",
name: "access", name: "access",

View File

@@ -4,9 +4,8 @@ import _ from "lodash-es";
// @ts-ignore // @ts-ignore
import { LocalStorage } from "/src/utils/util.storage"; import { LocalStorage } from "/src/utils/util.storage";
import * as basicApi from "/@/api/modules/api.basic";
import { SysPublicSetting } from "/@/api/modules/api.basic"; import { SysPublicSetting } from "/@/api/modules/api.basic";
import * as basicApi from '/@/api/modules/api.basic'
import _ from "lodash-es";
export type ThemeToken = { export type ThemeToken = {
token: { token: {
@@ -21,7 +20,7 @@ export type ThemeConfig = {
export interface SettingState { export interface SettingState {
themeConfig?: ThemeConfig; themeConfig?: ThemeConfig;
themeToken: ThemeToken; themeToken: ThemeToken;
sysPublic?: SysPublicSetting sysPublic?: SysPublicSetting;
} }
const defaultThemeConfig = { const defaultThemeConfig = {
@@ -38,21 +37,22 @@ export const useSettingStore = defineStore({
algorithm: theme.defaultAlgorithm algorithm: theme.defaultAlgorithm
}, },
sysPublic: { sysPublic: {
registerEnabled: false registerEnabled: false,
managerOtherUserPipeline: false
} }
}), }),
getters: { getters: {
getThemeConfig(): any { getThemeConfig(): any {
return this.themeConfig || _.merge({}, defaultThemeConfig, LocalStorage.get(SETTING_THEME_KEY) || {}); return this.themeConfig || _.merge({}, defaultThemeConfig, LocalStorage.get(SETTING_THEME_KEY) || {});
}, },
getSysPublic():SysPublicSetting{ getSysPublic(): SysPublicSetting {
return this.sysPublic return this.sysPublic;
} }
}, },
actions: { actions: {
async loadSysSettings(){ async loadSysSettings() {
const settings = await basicApi.getSysPublicSettings() const settings = await basicApi.getSysPublicSettings();
_.merge(this.sysPublic,settings) _.merge(this.sysPublic, settings);
}, },
persistThemeConfig() { persistThemeConfig() {
LocalStorage.set(SETTING_THEME_KEY, this.getThemeConfig); LocalStorage.set(SETTING_THEME_KEY, this.getThemeConfig);
@@ -92,7 +92,7 @@ export const useSettingStore = defineStore({
}, },
async init() { async init() {
await this.setThemeConfig(this.getThemeConfig); await this.setThemeConfig(this.getThemeConfig);
await this.loadSysSettings() await this.loadSysSettings();
} }
} }
}); });

View File

@@ -34,6 +34,9 @@ export const useUserStore = defineStore({
}, },
getToken(): string { getToken(): string {
return this.token || LocalStorage.get(TOKEN_KEY); return this.token || LocalStorage.get(TOKEN_KEY);
},
isAdmin(): boolean {
return this.getUserInfo?.id === 1;
} }
}, },
actions: { actions: {

View File

@@ -6,12 +6,10 @@ export function useReference(form: any) {
return; return;
} }
for (const reference of form.reference) { for (const reference of form.reference) {
debugger;
_.set( _.set(
form, form,
reference.dest, reference.dest,
compute<any>((scope) => { compute<any>((scope) => {
debugger;
return _.get(scope, reference.src); return _.get(scope, reference.src);
}) })
); );

View File

@@ -0,0 +1,59 @@
import { request } from "/src/api/service";
const apiPrefix = "/pi/history";
export function GetList(query: any) {
return request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export function AddObj(obj: any) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj: any) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id: any) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id: any) {
return request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export function GetDetail(id: any) {
return request({
url: apiPrefix + "/detail",
method: "post",
params: { id }
});
}
export function DeleteBatch(ids: any[]) {
return request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids }
});
}

View File

@@ -0,0 +1,154 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
actionbar: {
buttons: {
add: {
show: false
}
}
},
rowHandle: {
minWidth: 200,
fixed: "right",
buttons: {
edit: {
show: false
}
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100
},
form: {
show: false
}
},
userId: {
title: "用户Id",
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
},
form: {
show: false
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
}
},
pipelineId: {
title: "流水线Id",
type: "number",
search: {
show: true
},
form: {
show: false
}
},
pipelineTitle: {
title: "流水线名称",
type: "link",
search: {
show: true,
component: {
name: "a-input"
}
},
column: {
width: 200
}
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 125,
align: "center"
}
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false
},
column: {
show: true
}
}
}
}
};
}

View File

@@ -0,0 +1,51 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">流水线执行记录</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "/@/views/certd/history/api";
defineOptions({
name: "PipelineHistory"
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
}
});
} else {
message.error("请先勾选记录");
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>

View File

@@ -1,6 +1,6 @@
import * as api from "./api"; import * as api from "./api";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ref } from "vue"; import { computed, ref } from "vue";
import { useRouter } from "vue-router"; 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 } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status"; import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
@@ -8,6 +8,8 @@ import { nanoid } from "nanoid";
import { message, Modal } from "ant-design-vue"; import { message, Modal } from "ant-design-vue";
import { env } from "/@/utils/util.env"; import { env } from "/@/utils/util.env";
import { useUserStore } from "/@/store/modules/user"; import { useUserStore } from "/@/store/modules/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/modules/settings";
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@@ -93,6 +95,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}); });
} }
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@@ -125,6 +128,8 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
} }
}, },
rowHandle: { rowHandle: {
minWidth: 200,
fixed: "right",
buttons: { buttons: {
view: { view: {
click({ row }) { click({ row }) {
@@ -189,6 +194,23 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
show: false show: false
} }
}, },
userId: {
title: "用户Id",
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
},
form: {
show: false
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
})
}
},
title: { title: {
title: "流水线名称", title: "流水线名称",
type: "link", type: "link",
@@ -200,6 +222,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}, },
column: { column: {
width: 300, width: 300,
sorter: true,
component: { component: {
on: { on: {
// 注意必须要on前缀 // 注意必须要on前缀
@@ -210,11 +233,35 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
} }
} }
}, },
lastVars: {
title: "到期剩余",
type: "number",
form: {
show: false
},
column: {
cellRender({ row }) {
if (!row.lastVars?.certExpiresTime) {
return "-";
}
const leftDays = dayjs(row.lastVars.certExpiresTime).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100;
return <a-progress percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />;
},
width: 110
}
},
lastHistoryTime: { lastHistoryTime: {
title: "最后运行", title: "最后运行",
type: "datetime", type: "datetime",
form: { form: {
show: false show: false
},
column: {
sorter: true,
width: 120,
align: "center"
} }
}, },
status: { status: {
@@ -225,6 +272,11 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}), }),
form: { form: {
show: false show: false
},
column: {
sorter: true,
width: 80,
align: "center"
} }
}, },
@@ -242,6 +294,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
show: false show: false
}, },
column: { column: {
sorter: true,
width: 80,
align: "center",
component: { component: {
name: "fs-dict-switch", name: "fs-dict-switch",
vModel: "checked" vModel: "checked"
@@ -254,12 +309,25 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
} }
} }
}, },
keepHistoryCount: { keepHistoryCount: {
title: "历史记录保持数", title: "历史记录保持数",
type: "number", type: "number",
form: { form: {
value: 30, value: 30,
helper: "历史记录保持条数,多余的会被删除" helper: "历史记录保持条数,多余的会被删除"
},
column: {
show: false
}
},
order: {
title: "排序号",
type: "number",
column: {
sorter: true,
align: "center",
width: 80
} }
}, },
createTime: { createTime: {
@@ -267,6 +335,11 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
type: "datetime", type: "datetime",
form: { form: {
show: false show: false
},
column: {
sorter: true,
width: 125,
align: "center"
} }
}, },
updateTime: { updateTime: {
@@ -274,6 +347,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
type: "datetime", type: "datetime",
form: { form: {
show: false show: false
},
column: {
show: false
} }
} }
} }

View File

@@ -1,6 +1,15 @@
<template> <template>
<div class="main"> <div class="main">
<a-form ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" :rules="rules" v-bind="layout" @finish="handleFinish" @finishFailed="handleFinishFailed"> <a-form
ref="formRef"
class="user-layout-login"
name="custom-validation"
:model="formState"
:rules="rules"
v-bind="layout"
@finish="handleFinish"
@finish-failed="handleFinishFailed"
>
<!-- <div class="login-title">登录</div>--> <!-- <div class="login-title">登录</div>-->
<a-tabs :active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }"> <a-tabs :active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
<a-tab-pane key="password" tab="用户名密码登录"> <a-tab-pane key="password" tab="用户名密码登录">
@@ -55,7 +64,13 @@
</a-input> </a-input>
</a-col> </a-col>
<a-col class="gutter-row" :span="8"> <a-col class="gutter-row" :span="8">
<a-button class="getCaptcha" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode" v-text="smsTime <= 0 ? '发送' : smsTime + ' s'"></a-button> <a-button
class="getCaptcha"
tabindex="-1"
:disabled="smsSendBtnDisabled"
@click="sendSmsCode"
v-text="smsTime <= 0 ? '发送' : smsTime + ' s'"
></a-button>
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
@@ -75,13 +90,13 @@
import { defineComponent, reactive, ref, toRaw, computed } from "vue"; import { defineComponent, reactive, ref, toRaw, computed } from "vue";
import { useUserStore } from "/src/store/modules/user"; import { useUserStore } from "/src/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
import {utils} from "@fast-crud/fast-crud"; import { utils } from "@fast-crud/fast-crud";
export default defineComponent({ export default defineComponent({
name: "LoginPage", name: "LoginPage",
setup() { setup() {
const loading = ref(false); const loading = ref(false);
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore() const settingStore = useSettingStore();
const formRef = ref(); const formRef = ref();
const formState = reactive({ const formState = reactive({
username: "", username: "",
@@ -168,7 +183,7 @@ export default defineComponent({
function sendSmsCode() { function sendSmsCode() {
//api.sendSmsCode(); //api.sendSmsCode();
} }
const sysPublicSettings = settingStore.getSysPublic const sysPublicSettings = settingStore.getSysPublic;
return { return {
loading, loading,
formState, formState,

View File

@@ -1,6 +1,16 @@
<template> <template>
<div class="main"> <div class="main">
<a-form ref="formRef" class="user-layout-register" name="custom-validation" :model="formState" :rules="rules" v-bind="layout" @finish="handleFinish" @finishFailed="handleFinishFailed"> <a-form
ref="formRef"
class="user-layout-register"
name="custom-validation"
:model="formState"
:rules="rules"
v-bind="layout"
:label-col="{ span: 5 }"
@finish="handleFinish"
@finish-failed="handleFinishFailed"
>
<a-tabs :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }"> <a-tabs :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
<a-tab-pane key="register" tab="用户注册"> </a-tab-pane> <a-tab-pane key="register" tab="用户注册"> </a-tab-pane>
</a-tabs> </a-tabs>
@@ -39,7 +49,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, ref, toRaw } from "vue"; import { defineComponent, reactive, ref, toRaw } from "vue";
import { useUserStore } from "/src/store/modules/user"; import { useUserStore } from "/src/store/modules/user";
import {utils} from "@fast-crud/fast-crud"; import { utils } from "@fast-crud/fast-crud";
export default defineComponent({ export default defineComponent({
name: "RegisterPage", name: "RegisterPage",
setup() { setup() {

View File

@@ -4,8 +4,7 @@ const apiPrefix = "/sys/settings";
export const SettingKeys = { export const SettingKeys = {
SysPublic: "sys.public", SysPublic: "sys.public",
SysPrivate: "sys.private", SysPrivate: "sys.private"
}; };
export async function SettingsGet(key: string) { export async function SettingsGet(key: string) {
return await request({ return await request({
@@ -17,22 +16,28 @@ export async function SettingsGet(key: string) {
}); });
} }
export async function SettingsSave(key: string,setting: any) { export async function SettingsSave(key: string, setting: any) {
await request({ await request({
url: apiPrefix + "/save", url: apiPrefix + "/save",
method: "post", method: "post",
data: { data: {
key, key,
setting: JSON.stringify(setting), setting: JSON.stringify(setting)
} }
}); });
} }
export async function PublicSettingsSave(setting: any) { export async function PublicSettingsSave(setting: any) {
await request({ await request({
url: apiPrefix + "/savePublicSettings", url: apiPrefix + "/savePublicSettings",
method: "post", method: "post",
data: setting data: setting
}); });
} }
export async function stopOtherUserTimer() {
await request({
url: apiPrefix + "/stopOtherUserTimer",
method: "post"
});
}

View File

@@ -4,14 +4,31 @@
<div class="title">系统设置</div> <div class="title">系统设置</div>
</template> </template>
<div class="sys-settings-form"> <div class="sys-settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed"> <a-form
:model="formState"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="onFinish"
@finish-failed="onFinishFailed"
>
<a-form-item label="开启自助注册" name="registerEnabled"> <a-form-item label="开启自助注册" name="registerEnabled">
<a-switch v-model:checked="formState.registerEnabled" /> <a-switch v-model:checked="formState.registerEnabled" />
</a-form-item> </a-form-item>
<a-form-item label="管理其他用户流水线" name="managerOtherUserPipeline">
<a-switch v-model:checked="formState.managerOtherUserPipeline" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }"> <a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">保存</a-button> <a-button type="primary" html-type="submit">保存</a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- <a-descriptions label="系统维护操作">-->
<!-- <a-descriptions-item label="自动化任务">-->
<!-- <a-button @click="stopOtherUserTimer">停止所有其他用户的定时任务</a-button>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
</div> </div>
</fs-page> </fs-page>
</template> </template>
@@ -25,11 +42,10 @@ import { useSettingStore } from "/@/store/modules/settings";
interface FormState { interface FormState {
registerEnabled: boolean; registerEnabled: boolean;
} }
const formState = reactive<Partial<FormState>>({ const formState = reactive<Partial<FormState>>({
registerEnabled:false registerEnabled: false
}); });
async function loadSysPublicSettings() { async function loadSysPublicSettings() {
@@ -39,11 +55,11 @@ async function loadSysPublicSettings() {
} }
loadSysPublicSettings(); loadSysPublicSettings();
const settingsStore= useSettingStore() const settingsStore = useSettingStore();
const onFinish = async (form: any) => { const onFinish = async (form: any) => {
console.log("Success:", form); console.log("Success:", form);
await api.PublicSettingsSave(form); await api.PublicSettingsSave(form);
await settingsStore.loadSysSettings() await settingsStore.loadSysSettings();
notification.success({ notification.success({
message: "保存成功" message: "保存成功"
}); });
@@ -53,6 +69,12 @@ const onFinishFailed = (errorInfo: any) => {
// console.log("Failed:", errorInfo); // console.log("Failed:", errorInfo);
}; };
async function stopOtherUserTimer() {
await api.stopOtherUserTimer();
notification.success({
message: "停止成功"
});
}
</script> </script>
<style lang="less"> <style lang="less">

View File

@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.8](https://github.com/certd/certd/compare/v1.22.7...v1.22.8) (2024-08-05)
### Performance Improvements
* 修复删除历史记录没有删除log的bug新增history管理页面演示站点启动时不自动启动非管理员用户的定时任务 ([f78ae93](https://github.com/certd/certd/commit/f78ae93eedfe214008c3d071ca3d77c962137a64))
* 优化pipeline删除时删除其他history ([b425203](https://github.com/certd/certd/commit/b4252033d56a9ad950f3e204ff021497c3978015))
## [1.22.7](https://github.com/certd/certd/compare/v1.22.6...v1.22.7) (2024-08-04)
### Bug Fixes
* 修复保存配置报id不能为空的bug ([367f807](https://github.com/certd/certd/commit/367f80731396003416665c22853dfbc09c2c03a0))
## [1.22.6](https://github.com/certd/certd/compare/v1.22.5...v1.22.6) (2024-08-03)
### Bug Fixes
* 修复在相同的cron时偶尔无法触发定时任务的bug ([680941a](https://github.com/certd/certd/commit/680941af119619006b592e3ab6fb112cb5556a8b))
* 修复pg下pipeline title 类型问题 ([a9717b9](https://github.com/certd/certd/commit/a9717b9a0df7b5a64d4fe03314fecad4f59774cc))
### Performance Improvements
* 流水线支持名称模糊查询 ([59897c4](https://github.com/certd/certd/commit/59897c4ceae992ebe2972ca9e8f9196616ffdfd7))
* 腾讯云clb支持更多大区选择 ([e4f4570](https://github.com/certd/certd/commit/e4f4570b29f26c60f1ee9660a4c507cbeaba3d7e))
## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26) ## [1.22.5](https://github.com/certd/certd/compare/v1.22.4...v1.22.5) (2024-07-26)
### Bug Fixes ### Bug Fixes

View File

@@ -2,4 +2,4 @@ INSERT INTO sys_settings (key, title, setting,access) VALUES ('sys.install','安
ALTER TABLE sys_user ADD COLUMN password_version integer DEFAULT 1; ALTER TABLE sys_user ADD COLUMN password_version integer DEFAULT 1;
ALTER TABLE sys_user ADD COLUMN password_salt varchar(36); ALTER TABLE sys_user ADD COLUMN password_salt varchar(36);
ALTER TABLE sys_user ALTER COLUMN password varchar(100); alter table sys_user alter column password type varchar(100) using password::varchar(100);

View File

@@ -0,0 +1,5 @@
alter table pi_pipeline alter column title type varchar(100) using title::varchar(100);
alter table pi_pipeline alter column content type text using content::text;
alter table pi_storage alter column value type text using value::text;
alter table pi_pipeline add "order" integer default 0;

View File

@@ -0,0 +1,3 @@
alter table pi_pipeline add COLUMN "order" integer default 0;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-server", "name": "@certd/ui-server",
"version": "1.22.5", "version": "1.22.8",
"description": "fast-server base midway", "description": "fast-server base midway",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -21,12 +21,12 @@
"@alicloud/cs20151215": "^3.0.3", "@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0", "@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10", "@alicloud/pop-core": "^1.7.10",
"@certd/acme-client": "^1.22.4", "@certd/acme-client": "^1.22.6",
"@certd/lib-huawei": "^1.22.1", "@certd/lib-huawei": "^1.22.1",
"@certd/lib-k8s": "^1.22.5", "@certd/lib-k8s": "^1.22.8",
"@certd/midway-flyway-js": "^1.22.3", "@certd/midway-flyway-js": "^1.22.6",
"@certd/pipeline": "^1.22.5", "@certd/pipeline": "^1.22.8",
"@certd/plugin-cert": "^1.22.5", "@certd/plugin-cert": "^1.22.8",
"@koa/cors": "^3.4.3", "@koa/cors": "^3.4.3",
"@midwayjs/bootstrap": "^3.16.2", "@midwayjs/bootstrap": "^3.16.2",
"@midwayjs/cache": "^3.14.0", "@midwayjs/cache": "^3.14.0",
@@ -42,6 +42,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"cache-manager": "^3.6.3", "cache-manager": "^3.6.3",
"cron-parser": "^4.9.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"glob": "^10.4.5", "glob": "^10.4.5",
"https-proxy-agent": "^7.0.4", "https-proxy-agent": "^7.0.4",
@@ -56,7 +57,6 @@
"md5": "^2.3.0", "md5": "^2.3.0",
"mwtsc": "^1.4.0", "mwtsc": "^1.4.0",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"node-cron": "^3.0.2",
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"pg": "^8.12.0", "pg": "^8.12.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",

View File

@@ -10,7 +10,7 @@ export abstract class BaseController {
* 成功返回 * 成功返回
* @param data 返回数据 * @param data 返回数据
*/ */
ok(data: any) { ok(data?: any) {
const res = { const res = {
...Constants.res.success, ...Constants.res.success,
data: undefined, data: undefined,

View File

@@ -118,10 +118,9 @@ export abstract class BaseService<T> {
} }
const qb = this.getRepository().createQueryBuilder('main'); const qb = this.getRepository().createQueryBuilder('main');
if (order && order.prop) { if (order && order.prop) {
qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC'); qb.addOrderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC');
} else {
qb.orderBy('id', 'DESC');
} }
qb.addOrderBy('id', 'DESC');
qb.offset(page.offset).limit(page.limit); qb.offset(page.offset).limit(page.limit);
//根据bean query //根据bean query
if (query) { if (query) {

View File

@@ -9,12 +9,7 @@ export abstract class CrudController<T> extends BaseController {
@Body(ALL) @Body(ALL)
body body
) { ) {
const pageRet = await this.getService().page( const pageRet = await this.getService().page(body?.query, body?.page, body?.sort, null);
body?.query,
body?.page,
body?.sort,
null
);
return this.ok(pageRet); return this.ok(pageRet);
} }
@@ -54,6 +49,7 @@ export abstract class CrudController<T> extends BaseController {
await this.getService().update(bean); await this.getService().update(bean);
return this.ok(null); return this.ok(null);
} }
@Post('/delete') @Post('/delete')
async delete( async delete(
@Query('id') @Query('id')

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import path from 'path';
const KEYS_FILE = './data/keys.yaml'; const KEYS_FILE = './data/keys.yaml';
export class Keys { export class Keys {
jwtKey: string = nanoid(); jwtKey: string = nanoid();
@@ -19,6 +20,12 @@ export class Keys {
} }
save() { save() {
const parent = path.dirname(KEYS_FILE);
if (!fs.existsSync(parent)) {
fs.mkdirSync(parent, {
recursive: true,
});
}
fs.writeFileSync(KEYS_FILE, yaml.dump(this)); fs.writeFileSync(KEYS_FILE, yaml.dump(this));
} }
} }

View File

@@ -1,10 +1,9 @@
import { Config, Inject, Provide } from '@midwayjs/core'; import { Config, Inject, MidwayWebRouterService, Provide } from '@midwayjs/core';
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa'; import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Constants } from '../basic/constants.js'; import { Constants } from '../basic/constants.js';
import { MidwayWebRouterService } from '@midwayjs/core';
import { RoleService } from '../modules/authority/service/role-service.js';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
import { AuthService } from '../modules/authority/service/auth-service.js';
/** /**
* 权限校验 * 权限校验
@@ -16,7 +15,7 @@ export class AuthorityMiddleware implements IWebMiddleware {
@Inject() @Inject()
webRouterService: MidwayWebRouterService; webRouterService: MidwayWebRouterService;
@Inject() @Inject()
roleService: RoleService; authService: AuthService;
resolve() { resolve() {
return async (ctx: IMidwayKoaContext, next: NextFunction) => { return async (ctx: IMidwayKoaContext, next: NextFunction) => {
@@ -59,11 +58,8 @@ export class AuthorityMiddleware implements IWebMiddleware {
} }
if (permission !== Constants.per.authOnly) { if (permission !== Constants.per.authOnly) {
//如果不是仅校验登录,还需要校验是否拥有权限 const pass = await this.authService.checkPermission(ctx, permission);
const roleIds: number[] = ctx.user.roles; if (!pass) {
const permissions = await this.roleService.getCachedPermissionSetByRoleIds(roleIds);
if (!permissions.has(permission)) {
logger.info('not permission: ', ctx.req.url); logger.info('not permission: ', ctx.req.url);
ctx.status = 401; ctx.status = 401;
ctx.body = Constants.res.permission; ctx.body = Constants.res.permission;

View File

@@ -0,0 +1,38 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { RoleService } from './role-service.js';
import { BaseService } from '../../../basic/base-service.js';
/**
* 权限校验
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class AuthService {
@Inject()
roleService: RoleService;
async checkPermission(ctx: any, permission: string) {
//如果不是仅校验登录,还需要校验是否拥有权限
const roleIds: number[] = ctx.user.roles;
const permissions = await this.roleService.getCachedPermissionSetByRoleIds(roleIds);
if (!permissions.has(permission)) {
return false;
}
return true;
}
async isAdmin(ctx: any) {
const roleIds: number[] = ctx.user.roles;
if (roleIds.includes(1)) {
return true;
}
}
async checkEntityUserId(ctx: any, service: BaseService<any>, id: any = 0, userKey = 'userId') {
const isAdmin = await this.isAdmin(ctx);
if (isAdmin) {
return true;
}
await service.checkUserId(id, ctx.user.id, userKey);
}
}

View File

@@ -3,7 +3,8 @@ import { logger } from '../../utils/logger.js';
import { UserService } from '../authority/service/user-service.js'; import { UserService } from '../authority/service/user-service.js';
import { SysSettingsService } from '../system/service/sys-settings-service.js'; import { SysSettingsService } from '../system/service/sys-settings-service.js';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { SysInstallInfo } from '../system/service/models.js'; import { SysInstallInfo, SysLicenseInfo } from '../system/service/models.js';
import { verify } from '@certd/pipeline';
export type InstallInfo = { export type InstallInfo = {
installTime: number; installTime: number;
@@ -32,6 +33,14 @@ export class AutoInitSite {
await this.sysSettingsService.saveSetting(installInfo); await this.sysSettingsService.saveSetting(installInfo);
} }
// 授权许可
const licenseInfo: SysLicenseInfo = await this.sysSettingsService.getSetting(SysLicenseInfo);
const req = {
subjectId: installInfo.siteId,
license: licenseInfo.license,
};
await verify(req);
logger.info('初始化站点完成'); logger.info('初始化站点完成');
} }
} }

View File

@@ -1,6 +1,6 @@
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core'; import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { PipelineService } from '../service/pipeline-service.js'; import { PipelineService } from '../pipeline/service/pipeline-service.js';
import { logger } from '../../../utils/logger.js'; import { logger } from '../../utils/logger.js';
@Autoload() @Autoload()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Singleton)
@@ -8,6 +8,9 @@ export class AutoRegisterCron {
@Inject() @Inject()
pipelineService: PipelineService; pipelineService: PipelineService;
@Config('preview.enabled')
private preview: boolean;
// @Inject() // @Inject()
// echoPlugin: EchoPlugin; // echoPlugin: EchoPlugin;
@Config('cron.immediateTriggerOnce') @Config('cron.immediateTriggerOnce')
@@ -16,7 +19,7 @@ export class AutoRegisterCron {
@Init() @Init()
async init() { async init() {
logger.info('加载定时trigger开始'); logger.info('加载定时trigger开始');
await this.pipelineService.onStartup(this.immediateTriggerOnce); await this.pipelineService.onStartup(this.immediateTriggerOnce, this.preview);
// logger.info(this.echoPlugin, this.echoPlugin.test); // logger.info(this.echoPlugin, this.echoPlugin.test);
// logger.info('加载定时trigger完成'); // logger.info('加载定时trigger完成');
// //

View File

@@ -29,9 +29,7 @@ export class BasicSettingsController extends BaseController {
@Get('/public', { summary: Constants.per.guest }) @Get('/public', { summary: Constants.per.guest })
public async getSysPublic() { public async getSysPublic() {
const settings = await this.sysSettingsService.getSetting( const settings = await this.sysSettingsService.getSetting(SysPublicSettings);
SysPublicSettings
);
return this.ok(settings); return this.ok(settings);
} }
} }

View File

@@ -11,6 +11,8 @@ import { CommonException } from '../../../basic/exception/common-exception.js';
import { PermissionException } from '../../../basic/exception/permission-exception.js'; import { PermissionException } from '../../../basic/exception/permission-exception.js';
import * as fs from 'fs'; import * as fs from 'fs';
import { logger } from '../../../utils/logger.js'; import { logger } from '../../../utils/logger.js';
import { AuthService } from '../../authority/service/auth-service.js';
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
/** /**
* 证书 * 证书
@@ -25,19 +27,35 @@ export class HistoryController extends CrudController<HistoryService> {
@Inject() @Inject()
logService: HistoryLogService; logService: HistoryLogService;
@Inject()
authService: AuthService;
@Inject()
sysSettingsService: SysSettingsService;
getService() { getService() {
return this.service; return this.service;
} }
@Post('/page', { summary: Constants.per.authOnly }) @Post('/page', { summary: Constants.per.authOnly })
async page(@Body(ALL) body) { async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id; const isAdmin = await this.authService.isAdmin(this.ctx);
return super.page(body); const publicSettings = await this.sysSettingsService.getPublicSettings();
if (!(publicSettings.managerOtherUserPipeline && isAdmin)) {
body.query.userId = this.ctx.user.id;
}
const res = await super.page(body);
return res;
} }
@Post('/list', { summary: Constants.per.authOnly }) @Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body) { async list(@Body(ALL) body) {
body.userId = this.ctx.user.id; const isAdmin = await this.authService.isAdmin(this.ctx);
if (!isAdmin) {
body.userId = this.ctx.user.id;
}
if (body.pipelineId == null) { if (body.pipelineId == null) {
return this.ok([]); return this.ok([]);
} }
@@ -56,7 +74,7 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/update', { summary: Constants.per.authOnly }) @Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) { async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
return super.update(bean); return super.update(bean);
} }
@@ -64,7 +82,7 @@ export class HistoryController extends CrudController<HistoryService> {
async save(@Body(ALL) bean: HistoryEntity) { async save(@Body(ALL) bean: HistoryEntity) {
bean.userId = this.ctx.user.id; bean.userId = this.ctx.user.id;
if (bean.id > 0) { if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
} }
await this.service.save(bean); await this.service.save(bean);
return this.ok(bean.id); return this.ok(bean.id);
@@ -74,7 +92,7 @@ export class HistoryController extends CrudController<HistoryService> {
async saveLog(@Body(ALL) bean: HistoryLogEntity) { async saveLog(@Body(ALL) bean: HistoryLogEntity) {
bean.userId = this.ctx.user.id; bean.userId = this.ctx.user.id;
if (bean.id > 0) { if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
} }
await this.logService.save(bean); await this.logService.save(bean);
return this.ok(bean.id); return this.ok(bean.id);
@@ -82,26 +100,37 @@ export class HistoryController extends CrudController<HistoryService> {
@Post('/delete', { summary: Constants.per.authOnly }) @Post('/delete', { summary: Constants.per.authOnly })
async delete(@Query('id') id) { async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
return super.delete(id); await super.delete(id);
return this.ok();
}
@Post('/deleteByIds', { summary: Constants.per.authOnly })
async deleteByIds(@Body(ALL) body) {
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;
await this.getService().deleteByIds(body.ids, userId);
return this.ok();
} }
@Post('/detail', { summary: Constants.per.authOnly }) @Post('/detail', { summary: Constants.per.authOnly })
async detail(@Query('id') id) { async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
const detail = await this.service.detail(id); const detail = await this.service.detail(id);
return this.ok(detail); return this.ok(detail);
} }
@Post('/logs', { summary: Constants.per.authOnly }) @Post('/logs', { summary: Constants.per.authOnly })
async logs(@Query('id') id) { async logs(@Query('id') id) {
await this.logService.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.logService, id);
const logInfo = await this.logService.info(id); const logInfo = await this.logService.info(id);
return this.ok(logInfo); return this.ok(logInfo);
} }
@Post('/files', { summary: Constants.per.authOnly }) @Post('/files', { summary: Constants.per.authOnly })
async files(@Query('pipelineId') pipelineId, @Query('historyId') historyId) { async files(@Query('pipelineId') pipelineId, @Query('historyId') historyId) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId); const files = await this.getFiles(historyId, pipelineId);
return this.ok(files); return this.ok(files);
} }
@@ -125,6 +154,7 @@ export class HistoryController extends CrudController<HistoryService> {
@Get('/download', { summary: Constants.per.authOnly }) @Get('/download', { summary: Constants.per.authOnly })
async download(@Query('pipelineId') pipelineId, @Query('historyId') historyId, @Query('fileId') fileId) { async download(@Query('pipelineId') pipelineId, @Query('historyId') historyId, @Query('fileId') fileId) {
await this.authService.checkEntityUserId(this.ctx, this.service, historyId);
const files = await this.getFiles(historyId, pipelineId); const files = await this.getFiles(historyId, pipelineId);
const file = files.find(f => f.id === fileId); const file = files.find(f => f.id === fileId);
if (file == null) { if (file == null) {

View File

@@ -4,6 +4,8 @@ import { PipelineService } from '../service/pipeline-service.js';
import { PipelineEntity } from '../entity/pipeline.js'; import { PipelineEntity } from '../entity/pipeline.js';
import { Constants } from '../../../basic/constants.js'; import { Constants } from '../../../basic/constants.js';
import { HistoryService } from '../service/history-service.js'; import { HistoryService } from '../service/history-service.js';
import { AuthService } from '../../authority/service/auth-service.js';
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
/** /**
* 证书 * 证书
@@ -15,6 +17,10 @@ export class PipelineController extends CrudController<PipelineService> {
service: PipelineService; service: PipelineService;
@Inject() @Inject()
historyService: HistoryService; historyService: HistoryService;
@Inject()
authService: AuthService;
@Inject()
sysSettingsService: SysSettingsService;
getService() { getService() {
return this.service; return this.service;
@@ -22,10 +28,24 @@ export class PipelineController extends CrudController<PipelineService> {
@Post('/page', { summary: Constants.per.authOnly }) @Post('/page', { summary: Constants.per.authOnly })
async page(@Body(ALL) body) { async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id; const isAdmin = await this.authService.isAdmin(this.ctx);
const publicSettings = await this.sysSettingsService.getPublicSettings();
if (!(publicSettings.managerOtherUserPipeline && isAdmin)) {
body.query.userId = this.ctx.user.id;
}
const title = body.query.title;
delete body.query.title;
const buildQuery = qb => { const buildQuery = qb => {
qb.where({}); if (title) {
qb.where('title like :title', { title: `%${title}%` });
}
}; };
if (!body.sort || !body.sort?.prop) {
body.sort = { prop: 'order', asc: false };
}
return super.page({ ...body, buildQuery }); return super.page({ ...body, buildQuery });
} }
@@ -37,7 +57,7 @@ export class PipelineController extends CrudController<PipelineService> {
@Post('/update', { summary: Constants.per.authOnly }) @Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) { async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
return super.update(bean); return super.update(bean);
} }
@@ -45,37 +65,36 @@ export class PipelineController extends CrudController<PipelineService> {
async save(@Body(ALL) bean: PipelineEntity) { async save(@Body(ALL) bean: PipelineEntity) {
bean.userId = this.ctx.user.id; bean.userId = this.ctx.user.id;
if (bean.id > 0) { if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), bean.id);
} }
await this.service.save(bean); await this.service.save(bean);
await this.service.registerTriggerById(bean.id);
return this.ok(bean.id); return this.ok(bean.id);
} }
@Post('/delete', { summary: Constants.per.authOnly }) @Post('/delete', { summary: Constants.per.authOnly })
async delete(@Query('id') id) { async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
await this.service.delete(id); await this.service.delete(id);
return this.ok({}); return this.ok({});
} }
@Post('/detail', { summary: Constants.per.authOnly }) @Post('/detail', { summary: Constants.per.authOnly })
async detail(@Query('id') id) { async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
const detail = await this.service.detail(id); const detail = await this.service.detail(id);
return this.ok(detail); return this.ok(detail);
} }
@Post('/trigger', { summary: Constants.per.authOnly }) @Post('/trigger', { summary: Constants.per.authOnly })
async trigger(@Query('id') id) { async trigger(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
await this.service.trigger(id); await this.service.trigger(id);
return this.ok({}); return this.ok({});
} }
@Post('/cancel', { summary: Constants.per.authOnly }) @Post('/cancel', { summary: Constants.per.authOnly })
async cancel(@Query('historyId') historyId) { async cancel(@Query('historyId') historyId) {
await this.historyService.checkUserId(historyId, this.ctx.user.id); await this.authService.checkEntityUserId(this.ctx, this.historyService, historyId);
await this.service.cancel(historyId); await this.service.cancel(historyId);
return this.ok({}); return this.ok({});
} }

View File

@@ -35,4 +35,13 @@ export class HistoryEntity {
default: () => 'CURRENT_TIMESTAMP', default: () => 'CURRENT_TIMESTAMP',
}) })
updateTime: Date; updateTime: Date;
pipelineTitle: string;
fillPipelineTitle() {
if (this.pipeline) {
const pipeline = JSON.parse(this.pipeline);
this.pipelineTitle = pipeline.title;
}
}
} }

View File

@@ -9,7 +9,7 @@ export class PipelineEntity {
userId: number; userId: number;
@Column({ name: 'title', comment: '标题' }) @Column({ name: 'title', comment: '标题' })
title: number; title: string;
@Column({ comment: '配置', length: 40960 }) @Column({ comment: '配置', length: 40960 })
content: string; content: string;
@@ -37,6 +37,16 @@ export class PipelineEntity {
}) })
lastHistoryTime: number; lastHistoryTime: number;
// 变量
lastVars: any;
@Column({
name: 'order',
comment: '排序',
nullable: true,
})
order: number;
@Column({ @Column({
name: 'create_time', name: 'create_time',
comment: '创建时间', comment: '创建时间',

View File

@@ -12,7 +12,7 @@ export class DbStorage implements IStorage {
this.storageService = storageService; this.storageService = storageService;
} }
remove(scope: string, namespace: string, version: string, key: string): Promise<void> { async remove(scope: string, namespace: string, version: string, key: string): Promise<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }

View File

@@ -1,6 +1,6 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js'; import { BaseService } from '../../../basic/base-service.js';
import { HistoryLogEntity } from '../entity/history-log.js'; import { HistoryLogEntity } from '../entity/history-log.js';
@@ -24,4 +24,15 @@ export class HistoryLogService extends BaseService<HistoryLogEntity> {
await this.add(bean); await this.add(bean);
} }
} }
async deleteByHistoryIds(numbers: number[]) {
if (numbers.length === 0) {
return;
}
await this.repository.delete({ historyId: In(numbers) });
}
async deleteByPipelineId(id: number) {
await this.repository.delete({ pipelineId: id });
}
} }

View File

@@ -1,6 +1,6 @@
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js'; import { BaseService } from '../../../basic/base-service.js';
import { HistoryEntity } from '../entity/history.js'; import { HistoryEntity } from '../entity/history.js';
import { PipelineEntity } from '../entity/pipeline.js'; import { PipelineEntity } from '../entity/pipeline.js';
@@ -28,6 +28,14 @@ export class HistoryService extends BaseService<HistoryEntity> {
return this.repository; return this.repository;
} }
async page(query, page, sort, buildQuery) {
const res = await super.page(query, page, sort, buildQuery);
for (const item of res.records) {
item.fillPipelineTitle();
}
return res;
}
async save(bean: HistoryEntity) { async save(bean: HistoryEntity) {
if (bean.id > 0) { if (bean.id > 0) {
await this.update(bean); await this.update(bean);
@@ -51,7 +59,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
}; };
const { id } = await this.add(bean); const { id } = await this.add(bean);
//清除大于pipeline.keepHistoryCount的历史记录 //清除大于pipeline.keepHistoryCount的历史记录
this.clear(pipeline.id, pipeline.keepHistoryCount); await this.clear(pipeline.id, pipeline.keepHistoryCount);
return id; return id;
} }
@@ -85,7 +93,6 @@ export class HistoryService extends BaseService<HistoryEntity> {
skip: 0, skip: 0,
take: deleteCountBatch, take: deleteCountBatch,
}); });
await this.repository.remove(list);
for (const historyEntity of list) { for (const historyEntity of list) {
const id = historyEntity.id; const id = historyEntity.id;
@@ -95,6 +102,9 @@ export class HistoryService extends BaseService<HistoryEntity> {
logger.error('删除文件失败', e); logger.error('删除文件失败', e);
} }
} }
await this.repository.remove(list);
await this.logService.deleteByHistoryIds(list.map(item => item.id));
shouldDeleteCount -= deleteCountBatch; shouldDeleteCount -= deleteCountBatch;
} }
@@ -124,4 +134,29 @@ export class HistoryService extends BaseService<HistoryEntity> {
}); });
return files; return files;
} }
async deleteByIds(ids: number[], userId: number) {
await this.repository.delete({
id: In(ids),
userId,
});
await this.logService.deleteByHistoryIds(ids);
}
async deleteByPipelineId(id: number) {
await this.repository.delete({
pipelineId: id,
});
try {
const fileStore = new FileStore({
rootDir: this.certdConfig.fileRootDir,
scope: id + '',
parent: '0',
});
fileStore.deleteByParent(id + '', '');
} catch (e) {
logger.error('删除文件失败', e);
}
}
} }

View File

@@ -47,8 +47,23 @@ export class PipelineService extends BaseService<PipelineEntity> {
return this.repository; return this.repository;
} }
async update(entity) { async page(query: any, page: { offset: number; limit: number }, order: any, buildQuery: any) {
await super.update(entity); const result = await super.page(query, page, order, buildQuery);
const pipelineIds: number[] = [];
const recordMap = {};
for (const record of result.records) {
pipelineIds.push(record.id);
recordMap[record.id] = record;
}
const vars = await this.storageService.findPipelineVars(pipelineIds);
for (const varEntity of vars) {
const record = recordMap[varEntity.namespace];
if (record) {
const value = JSON.parse(varEntity.value);
record.lastVars = value.value;
}
}
return result;
} }
public async registerTriggerById(pipelineId) { public async registerTriggerById(pipelineId) {
@@ -71,17 +86,21 @@ export class PipelineService extends BaseService<PipelineEntity> {
return new PipelineDetail(pipeline); return new PipelineDetail(pipeline);
} }
async update(bean: PipelineEntity) {
await this.clearTriggers(bean.id);
await super.update(bean);
await this.registerTriggerById(bean.id);
}
async save(bean: PipelineEntity) { async save(bean: PipelineEntity) {
await this.clearTriggers(bean.id);
const pipeline = JSON.parse(bean.content); const pipeline = JSON.parse(bean.content);
bean.title = pipeline.title; bean.title = pipeline.title;
await this.addOrUpdate(bean); await this.addOrUpdate(bean);
await this.registerTriggerById(bean.id);
} }
/** async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
* 应用启动后初始加载记录
*/
async onStartup(immediateTriggerOnce: boolean) {
logger.info('加载定时trigger开始');
const idEntityList = await this.repository.find({ const idEntityList = await this.repository.find({
select: { select: {
id: true, id: true,
@@ -112,15 +131,36 @@ export class PipelineService extends BaseService<PipelineEntity> {
}); });
for (const entity of list) { for (const entity of list) {
const pipeline = JSON.parse(entity.content ?? '{}'); await callback(entity);
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
}
} }
} }
logger.info('定时器数量:', this.cron.getListSize()); }
async stopOtherUserPipeline(userId: number) {
await this.foreachPipeline(async entity => {
if (entity.userId !== userId) {
await this.clearTriggers(entity.id);
}
});
}
/**
* 应用启动后初始加载记录
*/
async onStartup(immediateTriggerOnce: boolean, preview: boolean) {
logger.info('加载定时trigger开始');
await this.foreachPipeline(async entity => {
if (preview && entity.userId !== 1) {
return;
}
const pipeline = JSON.parse(entity.content ?? '{}');
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
}
});
logger.info('定时器数量:', this.cron.getTaskSize());
} }
async registerTriggers(pipeline?: Pipeline, immediateTriggerOnce = false) { async registerTriggers(pipeline?: Pipeline, immediateTriggerOnce = false) {
@@ -153,6 +193,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
} }
async delete(id: number) { async delete(id: number) {
await this.clearTriggers(id);
//TODO 删除storage
// const storage = new DbStorage(pipeline.userId, this.storageService);
// await storage.remove(pipeline.id);
await super.delete([id]);
await this.historyService.deleteByPipelineId(id);
await this.historyLogService.deleteByPipelineId(id);
}
async clearTriggers(id: number) {
if (id == null) {
return;
}
const pipeline = await this.info(id); const pipeline = await this.info(id);
if (!pipeline) { if (!pipeline) {
return; return;
@@ -163,7 +216,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
this.removeCron(id, trigger); this.removeCron(id, trigger);
} }
} }
await super.delete([id]);
} }
removeCron(pipelineId, trigger) { removeCron(pipelineId, trigger) {
@@ -176,6 +228,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (cron == null) { if (cron == null) {
return; return;
} }
cron = cron.trim();
if (cron.startsWith('*')) { if (cron.startsWith('*')) {
cron = '0' + cron.substring(1, cron.length); cron = '0' + cron.substring(1, cron.length);
} }
@@ -183,7 +236,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
this.cron.remove(name); this.cron.remove(name);
this.cron.register({ this.cron.register({
name, name,
cron: cron, cron,
job: async () => { job: async () => {
logger.info('定时任务触发:', pipelineId, trigger.id); logger.info('定时任务触发:', pipelineId, trigger.id);
try { try {
@@ -193,11 +246,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
} }
}, },
}); });
logger.info('当前定时器数量:', this.cron.getListSize()); logger.info('当前定时器数量:', this.cron.getTaskSize());
} }
async run(id: number, triggerId: string) { async run(id: number, triggerId: string) {
const entity: PipelineEntity = await this.info(id); const entity: PipelineEntity = await this.info(id);
const pipeline = JSON.parse(entity.content); const pipeline = JSON.parse(entity.content);
if (!pipeline.stages || pipeline.stages.length === 0) { if (!pipeline.stages || pipeline.stages.length === 0) {
@@ -209,9 +263,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
return; return;
} }
if (triggerType === 'timer') {
if (entity.disabled) {
return;
}
}
const onChanged = async (history: RunHistory) => { const onChanged = async (history: RunHistory) => {
//保存执行历史 //保存执行历史
try { try {
logger.info('保存执行历史:', history.id);
await this.saveHistory(history); await this.saveHistory(history);
} catch (e) { } catch (e) {
const pipelineEntity = new PipelineEntity(); const pipelineEntity = new PipelineEntity();

View File

@@ -1,6 +1,6 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js'; import { BaseService } from '../../../basic/base-service.js';
import { StorageEntity } from '../entity/storage.js'; import { StorageEntity } from '../entity/storage.js';
@@ -41,4 +41,14 @@ export class StorageService extends BaseService<StorageEntity> {
} }
return; return;
} }
async findPipelineVars(pipelineIds: number[]) {
return await this.repository.find({
where: {
scope: 'pipeline',
namespace: In(pipelineIds),
key: 'vars',
},
});
}
} }

View File

@@ -1,5 +1,7 @@
import cron from 'node-cron'; import parser from 'cron-parser';
export type CronTask = { import { ILogger } from '@certd/pipeline';
export type CronTaskReq = {
/** /**
* 为空则为单次执行 * 为空则为单次执行
*/ */
@@ -7,39 +9,90 @@ export type CronTask = {
job: () => Promise<void>; job: () => Promise<void>;
name: string; name: string;
}; };
export class CronTask {
logger: ILogger;
cron: string;
job: () => Promise<void>;
name: string;
stoped = false;
timeoutId: any;
constructor(req: CronTaskReq, logger: ILogger) {
this.cron = req.cron;
this.job = req.job;
this.name = req.name;
this.logger = logger;
this.start();
}
start() {
if (!this.cron) {
return;
}
if (this.stoped) {
return;
}
const interval = parser.parseExpression(this.cron);
const next = interval.next().getTime();
const now = Date.now();
const delay = next - now;
this.timeoutId = setTimeout(async () => {
try {
if (this.stoped) {
return;
}
await this.job();
} catch (e) {
this.logger.error(`[cron] job error : [${this.name}]`, e);
}
this.start();
}, delay);
}
stop() {
this.stoped = true;
clearTimeout(this.timeoutId);
}
}
export class Cron { export class Cron {
logger; logger: ILogger;
immediateTriggerOnce: boolean; immediateTriggerOnce: boolean;
constructor(opts) {
bucket: Record<string, CronTask> = {};
constructor(opts: any) {
this.logger = opts.logger; this.logger = opts.logger;
this.immediateTriggerOnce = opts.immediateTriggerOnce; this.immediateTriggerOnce = opts.immediateTriggerOnce;
} }
register(task: CronTask) { register(req: CronTaskReq) {
if (!task.cron) { if (!req.cron) {
this.logger.info(`[cron] register once : [${task.name}]`); this.logger.info(`[cron] register once : [${req.name}]`);
task.job(); req.job().catch(e => {
this.logger.error(`job execute error : [${req.name}]`, e);
});
return; return;
} }
this.logger.info(`[cron] register cron : [${task.name}] ,${task.cron}`); this.logger.info(`[cron] register cron : [${req.name}] ,${req.cron}`);
cron.schedule(task.cron, task.job, {
name: task.name, const task = new CronTask(req, this.logger);
}); this.bucket[task.name] = task;
this.logger.info('当前定时任务数量:', this.getListSize()); this.logger.info('当前定时任务数量:', this.getTaskSize());
} }
remove(taskName: string) { remove(taskName: string) {
this.logger.info(`[cron] remove : [${taskName}]`); this.logger.info(`[cron] remove : [${taskName}]`);
const tasks = cron.getTasks() as Map<any, any>; const task = this.bucket[taskName];
const node = tasks.get(taskName); if (task) {
if (node) { task.stop();
node.stop(); delete this.bucket[taskName];
tasks.delete(taskName);
} }
} }
getListSize() { getTaskSize() {
const tasks = cron.getTasks(); const tasks = Object.keys(this.bucket);
return tasks.size; return tasks.length;
} }
} }

View File

@@ -4,6 +4,7 @@ import { SysSettingsService } from '../service/sys-settings-service.js';
import { SysSettingsEntity } from '../entity/sys-settings.js'; import { SysSettingsEntity } from '../entity/sys-settings.js';
import { SysPublicSettings } from '../service/models.js'; import { SysPublicSettings } from '../service/models.js';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { PipelineService } from '../../pipeline/service/pipeline-service.js';
/** /**
*/ */
@@ -12,6 +13,8 @@ import * as _ from 'lodash-es';
export class SysSettingsController extends CrudController<SysSettingsService> { export class SysSettingsController extends CrudController<SysSettingsService> {
@Inject() @Inject()
service: SysSettingsService; service: SysSettingsService;
@Inject()
pipelineService: PipelineService;
getService() { getService() {
return this.service; return this.service;
@@ -73,4 +76,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
await this.service.savePublicSettings(setting); await this.service.savePublicSettings(setting);
return this.ok({}); return this.ok({});
} }
@Post('/stopOtherUserTimer', { summary: 'sys:settings:edit' })
async stopOtherUserTimer(@Body(ALL) body) {
await this.pipelineService.stopOtherUserPipeline(1);
return this.ok({});
}
} }

View File

@@ -12,6 +12,7 @@ export class SysPublicSettings extends BaseSettings {
static __title__ = '系统公共设置'; static __title__ = '系统公共设置';
static __access__ = 'public'; static __access__ = 'public';
registerEnabled = false; registerEnabled = false;
managerOtherUserPipeline = false;
} }
export class SysPrivateSettings extends BaseSettings { export class SysPrivateSettings extends BaseSettings {
@@ -27,3 +28,10 @@ export class SysInstallInfo extends BaseSettings {
installTime: number; installTime: number;
siteId?: string; siteId?: string;
} }
export class SysLicenseInfo extends BaseSettings {
static __title__ = '授权许可信息';
static __key__ = 'sys.license';
static __access__ = 'private';
license?: string;
}

View File

@@ -27,6 +27,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
default: 'cn-hangzhou', default: 'cn-hangzhou',
component: { component: {
name: 'a-select', name: 'a-select',
mode: 'tags',
vModel: 'value', vModel: 'value',
options: ZoneOptions, options: ZoneOptions,
}, },

View File

@@ -29,6 +29,7 @@ export class CloudflareDeployToCDNPlugin extends AbstractTaskPlugin {
component: { component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn //前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-select', name: 'a-select',
mode: 'tags',
options: [ options: [
{ value: '1', label: '选项1' }, { value: '1', label: '选项1' },
{ value: '2', label: '选项2' }, { value: '2', label: '选项2' },

View File

@@ -29,6 +29,7 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
component: { component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn //前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-select', name: 'a-select',
mode: 'tags',
options: [ options: [
{ value: '1', label: '选项1' }, { value: '1', label: '选项1' },
{ value: '2', label: '选项2' }, { value: '2', label: '选项2' },

View File

@@ -11,6 +11,7 @@ export class DnspodAccess {
component: { component: {
placeholder: 'endpoint', placeholder: 'endpoint',
name: 'a-select', name: 'a-select',
mode: 'tags',
vModel: 'value', vModel: 'value',
options: [ options: [
{ value: 'https://dnsapi.cn', label: '中国站' }, { value: 'https://dnsapi.cn', label: '中国站' },

View File

@@ -1,10 +1,5 @@
import { Autowire, HttpClient, ILogger } from '@certd/pipeline'; import { Autowire, HttpClient, ILogger } from '@certd/pipeline';
import { import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
AbstractDnsProvider,
CreateRecordOptions,
IsDnsProvider,
RemoveRecordOptions,
} from '@certd/plugin-cert';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { DnspodAccess } from '../access/index.js'; import { DnspodAccess } from '../access/index.js';
@@ -50,9 +45,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
if (!ret || !ret.status) { if (!ret || !ret.status) {
const code = ret.status.code; const code = ret.status.code;
if (code !== '1' || !successCodes.includes(code)) { if (code !== '1' || !successCodes.includes(code)) {
throw new Error( throw new Error('请求失败:' + ret.status.message + ',api=' + config.url);
'请求失败:' + ret.status.message + ',api=' + config.url
);
} }
} }
return ret; return ret;
@@ -87,12 +80,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
}, },
['104'] ['104']
); // 104错误码为记录已存在无需再次添加 ); // 104错误码为记录已存在无需再次添加
this.logger.info( this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record));
'添加域名解析成功:',
fullRecord,
value,
JSON.stringify(ret.record)
);
return ret.record; return ret.record;
} }

View File

@@ -20,7 +20,28 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
default: 'ap-guangzhou', default: 'ap-guangzhou',
component: { component: {
name: 'a-select', name: 'a-select',
options: [{ value: 'ap-guangzhou' }], mode: 'tags',
options: [
{ value: 'ap-guangzhou' },
{ value: 'ap-beijing' },
{ value: 'ap-chengdu' },
{ value: 'ap-chongqing' },
{ value: 'ap-hongkong' },
{ value: 'ap-jakarta' },
{ value: 'ap-mumbai' },
{ value: 'ap-nanjing' },
{ value: 'ap-seoul' },
{ value: 'ap-shanghai' },
{ value: 'ap-shanghai-fsi' },
{ value: 'ap-shenzhen-fsi' },
{ value: 'ap-singapore' },
{ value: 'ap-tokyo' },
{ value: 'eu-frankfurt' },
{ value: 'na-ashburn' },
{ value: 'na-siliconvalley' },
{ value: 'na-toronto' },
{ value: 'sa-saopaulo' },
],
}, },
required: true, required: true,
}) })