Compare commits

..

5 Commits

Author SHA1 Message Date
xiaojunnuo 1f002159e2 v1.38.11 2026-02-16 23:44:19 +08:00
xiaojunnuo 5bc690fcd9 build: prepare to build 2026-02-16 23:40:03 +08:00
xiaojunnuo bab9adce24 perf: 支持自定义发件人名称,格式:名称<邮箱> 2026-02-16 23:38:08 +08:00
xiaojunnuo e47eddaa85 perf: 优化登陆页面的黑暗模式 2026-02-16 23:18:55 +08:00
xiaojunnuo 8ef1f2e395 fix: 修复1panel2.1.0新版本测试失败的问题 2026-02-16 17:28:46 +08:00
194 changed files with 650 additions and 5796 deletions
-301
View File
@@ -1,301 +0,0 @@
# Access 插件开发技能
## 什么是 Access 插件
Access 插件是 Certd 系统中用于存储用户第三方应用授权数据的插件,例如用户名密码、accessSecret 或 accessToken 等。同时,它还负责对接实现第三方的 API 接口,供其他插件调用使用。
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
```
### 2. 使用 @IsAccess 注解注册插件
```typescript
@IsAccess({
name: 'demo', // 插件唯一标识
title: '授权插件示例', // 插件标题
icon: 'clarity:plugin-line', // 插件图标
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件', // 插件描述
})
export class DemoAccess extends BaseAccess {
// 插件实现...
}
```
### 3. 定义授权属性
使用 `@AccessInput` 注解定义授权属性:
```typescript
@AccessInput({
title: '授权方式',
value: 'apiKey', // 默认值
component: {
name: "a-select", // 基于 antdv 的输入组件
vModel: "value", // v-model 绑定的属性名
options: [ // 组件参数
{ label: "API密钥(推荐)", value: "apiKey" },
{ label: "账号密码", value: "account" },
],
placeholder: 'demoKeyId',
},
required: true,
})
apiType = '';
@AccessInput({
title: '密钥Id',
component: {
name:"a-input",
allowClear: true,
placeholder: 'demoKeyId',
},
required: true,
})
demoKeyId = '';
@AccessInput({
title: '密钥',//标题
required: true, //text组件可以省略
encrypt: true, //该属性是否需要加密
})
demoKeySecret = '';
```
### 4. 实现测试方法
```typescript
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
/**
* 会通过上面的testRequest参数在ui界面上生成测试按钮,供用户测试接口调用是否正常
*/
async onTestRequest() {
await this.GetDomainList({});
return "ok"
}
```
### 5. 实现 API 方法
```typescript
/**
* 获api接口示例 取域名列表,
*/
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
//输出日志必须使用ctx.logger
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
action: "ListDomains",
data: {
domain: req.searchKey,
offset: pager.getOffset(),
limit: pager.pageSize,
}
});
const total = resp?.TotalCount || 0;
let list = resp?.DomainList?.map((item) => {
item.domain = item.Domain;
item.id = item.DomainId;
return item;
})
return {
total,
list
};
}
/**
* 通用api调用方法, 具体如何构造请求体,需参考对应应用的API文档
*/
async doRequest(req: { action: string, data?: any }) {
/**
this.ctx中包含很多有用的工具类
type AccessContext = {
http: HttpClient;
logger: ILogger;
utils: typeof utils;
accessService: IAccessService;
}
*/
const res = await this.ctx.http.request({
url: "https://api.demo.cn/api/",
method: "POST",
data: {
Action: req.action,
Body: req.data
}
});
if (res.Code !== 0) {
//异常处理
throw new Error(res.Message || "请求失败");
}
return res.Resp;
}
```
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
2. **属性加密**:对于敏感信息(如密钥),应设置 `encrypt: true`
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
## 完整示例
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
/**
* 这个注解将注册一个授权配置
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
*/
@IsAccess({
name: 'demo',
title: '授权插件示例',
icon: 'clarity:plugin-line', //插件图标
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件',
})
export class DemoAccess extends BaseAccess {
/**
* 授权属性配置
*/
@AccessInput({
title: '授权方式',
value: 'apiKey', //默认值
component: {
name: "a-select", //基于antdv的输入组件
vModel: "value", // v-model绑定的属性名
options: [ //组件参数
{
label: "API密钥(推荐)",
value: "apiKey"
},
{
label: "账号密码",
value: "account"
},
],
placeholder: 'demoKeyId',
},
required: true,
})
apiType = '';
/**
* 授权属性配置
*/
@AccessInput({
title: '密钥Id',
component: {
name:"a-input",
allowClear: true,
placeholder: 'demoKeyId',
},
required: true,
})
demoKeyId = '';
@AccessInput({
title: '密钥',//标题
required: true, //text组件可以省略
encrypt: true, //该属性是否需要加密
})
demoKeySecret = '';
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
/**
* 会通过上面的testRequest参数在ui界面上生成测试按钮,供用户测试接口调用是否正常
*/
async onTestRequest() {
await this.GetDomainList({});
return "ok"
}
/**
* 获api接口示例 取域名列表,
*/
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
//输出日志必须使用ctx.logger
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
action: "ListDomains",
data: {
domain: req.searchKey,
offset: pager.getOffset(),
limit: pager.pageSize,
}
});
const total = resp?.TotalCount || 0;
let list = resp?.DomainList?.map((item) => {
item.domain = item.Domain;
item.id = item.DomainId;
return item;
})
return {
total,
list
};
}
// 还可以继续编写API
/**
* 通用api调用方法, 具体如何构造请求体,需参考对应应用的API文档
*/
async doRequest(req: { action: string, data?: any }) {
/**
this.ctx中包含很多有用的工具类
type AccessContext = {
http: HttpClient;
logger: ILogger;
utils: typeof utils;
accessService: IAccessService;
}
*/
const res = await this.ctx.http.request({
url: "https://api.demo.cn/api/",
method: "POST",
data: {
Action: req.action,
Body: req.data
}
});
if (res.Code !== 0) {
//异常处理
throw new Error(res.Message || "请求失败");
}
return res.Resp;
}
}
```
@@ -1 +0,0 @@
我需要开发一个 Access 插件,用于存储和管理第三方应用的授权信息。请指导我如何实现。
@@ -1,145 +0,0 @@
# Access 插件开发指南
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
```
### 2. 使用 @IsAccess 注解注册插件
```typescript
@IsAccess({
name: 'demo', // 插件唯一标识
title: '授权插件示例', // 插件标题
icon: 'clarity:plugin-line', // 插件图标
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件', // 插件描述
})
export class DemoAccess extends BaseAccess {
// 插件实现...
}
```
### 3. 定义授权属性
使用 `@AccessInput` 注解定义授权属性:
```typescript
@AccessInput({
title: '授权方式',
value: 'apiKey', // 默认值
component: {
name: "a-select", // 基于 antdv 的输入组件
vModel: "value", // v-model 绑定的属性名
options: [ // 组件参数
{ label: "API密钥(推荐)", value: "apiKey" },
{ label: "账号密码", value: "account" },
],
placeholder: 'demoKeyId',
},
required: true,
})
apiType = '';
@AccessInput({
title: '密钥Id',
component: {
name:"a-input",
allowClear: true,
placeholder: 'demoKeyId',
},
required: true,
})
demoKeyId = '';
@AccessInput({
title: '密钥',//标题
required: true, //text组件可以省略
encrypt: true, //该属性是否需要加密
})
demoKeySecret = '';
```
### 4. 实现测试方法
```typescript
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
/**
* 会通过上面的testRequest参数在ui界面上生成测试按钮,供用户测试接口调用是否正常
*/
async onTestRequest() {
await this.GetDomainList({});
return "ok"
}
```
### 5. 实现 API 方法
```typescript
/**
* 获api接口示例 取域名列表,
*/
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
//输出日志必须使用ctx.logger
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
action: "ListDomains",
data: {
domain: req.searchKey,
offset: pager.getOffset(),
limit: pager.pageSize,
}
});
const total = resp?.TotalCount || 0;
let list = resp?.DomainList?.map((item) => {
item.domain = item.Domain;
item.id = item.DomainId;
return item;
})
return {
total,
list
};
}
/**
* 通用api调用方法, 具体如何构造请求体,需参考对应应用的API文档
*/
async doRequest(req: { action: string, data?: any }) {
const res = await this.ctx.http.request({
url: "https://api.demo.cn/api/",
method: "POST",
data: {
Action: req.action,
Body: req.data
}
});
if (res.Code !== 0) {
//异常处理
throw new Error(res.Message || "请求失败");
}
return res.Resp;
}
```
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
2. **属性加密**:对于敏感信息(如密钥),应设置 `encrypt: true`
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
-212
View File
@@ -1,212 +0,0 @@
# DNS Provider 插件开发技能
## 什么是 DNS Provider 插件
DNS Provider 插件是 Certd 系统中的 DNS 提供商插件,它用于在 ACME 申请证书时给域名添加 TXT 解析记录,以验证域名所有权。
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';
import { isDev } from '../../utils/env.js';
```
### 2. 定义记录数据结构
```typescript
type DemoRecord = {
// 这里定义 Record 记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到 id 就行,用于删除 txt 解析记录,清理申请痕迹
// id:string
};
```
### 3. 使用 @IsDnsProvider 注解注册插件
```typescript
// 这里通过 IsDnsProvider 注册一个 dnsProvider
@IsDnsProvider({
name: 'demo', // 插件唯一标识
title: 'Dns提供商Demo', // 插件标题
desc: 'dns provider示例', // 插件描述
icon: 'clarity:plugin-line', // 插件图标
// 这里是对应的云平台的 access 类型名称
accessType: 'demo',
order: 99, // 排序
})
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
access!: DemoAccess;
async onInstance() {
this.access = this.ctx.access as DemoAccess;
// 也可以通过 ctx 成员变量传递 context
this.logger.debug('access', this.access);
// 初始化的操作
// ...
}
// 插件实现...
}
```
### 4. 实现 createRecord 方法
```typescript
/**
* 创建 dns 解析记录,用于验证域名所有权
*/
async createRecord(options: CreateRecordOptions): Promise<any> {
/**
* options 参数说明
* fullRecord: '_acme-challenge.example.com',
* value: 一串 uuid
* type: 'TXT',
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
// 调用创建 dns 解析记录的对应的云端接口,创建 txt 类型的 dns 解析记录
// 请根据实际接口情况调用,例如:
// const createDnsRecordUrl = "xxx"
// const record = this.http.post(createDnsRecordUrl,{
// // 授权参数
// // 创建 dns 解析记录的参数
// })
// // 返回本次创建的 dns 解析记录,这个记录会在删除的时候用到
// return record
}
```
### 5. 实现 removeRecord 方法
```typescript
/**
* 删除 dns 解析记录,清理申请痕迹
* @param options
*/
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
// 这里调用删除 txt dns 解析记录接口
// 请根据实际接口情况调用,例如:
// const deleteDnsRecordUrl = "xxx"
// const res = this.http.delete(deleteDnsRecordUrl,{
// // 授权参数
// // 删除 dns 解析记录的参数
// })
this.logger.info('删除域名解析成功:', fullRecord, value);
}
```
### 6. 实例化插件
```typescript
// 实例化这个 provider,将其自动注册到系统中
if (isDev()) {
// 你的实现 要去掉这个 if,不然生产环境将不会显示
new DemoDnsProvider();
}
```
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
2. **accessType**:必须指定对应的云平台的 access 类型名称。
3. **记录结构**:定义适合对应云平台的记录数据结构,至少包含 id 字段用于删除记录。
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`
5. **错误处理**:API 调用失败时应抛出明确的错误信息。
6. **实例化**:生产环境中应移除 `if (isDev())` 条件,确保插件在生产环境中也能被注册。
## 完整示例
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';
import { isDev } from '../../utils/env.js';
type DemoRecord = {
// 这里定义 Record 记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到 id 就行,用于删除 txt 解析记录,清理申请痕迹
// id:string
};
// 这里通过 IsDnsProvider 注册一个 dnsProvider
@IsDnsProvider({
name: 'demo',
title: 'Dns提供商Demo',
desc: 'dns provider示例',
icon: 'clarity:plugin-line',
// 这里是对应的云平台的 access 类型名称
accessType: 'demo',
order: 99,
})
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
access!: DemoAccess;
async onInstance() {
this.access = this.ctx.access as DemoAccess;
// 也可以通过 ctx 成员变量传递 context
this.logger.debug('access', this.access);
// 初始化的操作
// ...
}
/**
* 创建 dns 解析记录,用于验证域名所有权
*/
async createRecord(options: CreateRecordOptions): Promise<any> {
/**
* options 参数说明
* fullRecord: '_acme-challenge.example.com',
* value: 一串 uuid
* type: 'TXT',
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
// 调用创建 dns 解析记录的对应的云端接口,创建 txt 类型的 dns 解析记录
// 请根据实际接口情况调用,例如:
// const createDnsRecordUrl = "xxx"
// const record = this.http.post(createDnsRecordUrl,{
// // 授权参数
// // 创建 dns 解析记录的参数
// })
// // 返回本次创建的 dns 解析记录,这个记录会在删除的时候用到
// return record
}
/**
* 删除 dns 解析记录,清理申请痕迹
* @param options
*/
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
// 这里调用删除 txt dns 解析记录接口
// 请根据实际接口情况调用,例如:
// const deleteDnsRecordUrl = "xxx"
// const res = this.http.delete(deleteDnsRecordUrl,{
// // 授权参数
// // 删除 dns 解析记录的参数
// })
this.logger.info('删除域名解析成功:', fullRecord, value);
}
}
// 实例化这个 provider,将其自动注册到系统中
if (isDev()) {
// 你的实现 要去掉这个 if,不然生产环境将不会显示
new DemoDnsProvider();
}
```
@@ -1 +0,0 @@
我需要开发一个 DNS Provider 插件,用于在 ACME 申请证书时添加 TXT 解析记录。请指导我如何实现。
@@ -1,121 +0,0 @@
# DNS Provider 插件开发指南
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';
import { isDev } from '../../utils/env.js';
```
### 2. 定义记录数据结构
```typescript
type DemoRecord = {
// 这里定义 Record 记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到 id 就行,用于删除 txt 解析记录,清理申请痕迹
// id:string
};
```
### 3. 使用 @IsDnsProvider 注解注册插件
```typescript
// 这里通过 IsDnsProvider 注册一个 dnsProvider
@IsDnsProvider({
name: 'demo', // 插件唯一标识
title: 'Dns提供商Demo', // 插件标题
desc: 'dns provider示例', // 插件描述
icon: 'clarity:plugin-line', // 插件图标
// 这里是对应的云平台的 access 类型名称
accessType: 'demo',
order: 99, // 排序
})
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
access!: DemoAccess;
async onInstance() {
this.access = this.ctx.access as DemoAccess;
// 也可以通过 ctx 成员变量传递 context
this.logger.debug('access', this.access);
// 初始化的操作
// ...
}
// 插件实现...
}
```
### 4. 实现 createRecord 方法
```typescript
/**
* 创建 dns 解析记录,用于验证域名所有权
*/
async createRecord(options: CreateRecordOptions): Promise<any> {
/**
* options 参数说明
* fullRecord: '_acme-challenge.example.com',
* value: 一串 uuid
* type: 'TXT',
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
// 调用创建 dns 解析记录的对应的云端接口,创建 txt 类型的 dns 解析记录
// 请根据实际接口情况调用,例如:
// const createDnsRecordUrl = "xxx"
// const record = this.http.post(createDnsRecordUrl,{
// // 授权参数
// // 创建 dns 解析记录的参数
// })
// // 返回本次创建的 dns 解析记录,这个记录会在删除的时候用到
// return record
}
```
### 5. 实现 removeRecord 方法
```typescript
/**
* 删除 dns 解析记录,清理申请痕迹
* @param options
*/
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
// 这里调用删除 txt dns 解析记录接口
// 请根据实际接口情况调用,例如:
// const deleteDnsRecordUrl = "xxx"
// const res = this.http.delete(deleteDnsRecordUrl,{
// // 授权参数
// // 删除 dns 解析记录的参数
// })
this.logger.info('删除域名解析成功:', fullRecord, value);
}
```
### 6. 实例化插件
```typescript
// 实例化这个 provider,将其自动注册到系统中
if (isDev()) {
// 你的实现 要去掉这个 if,不然生产环境将不会显示
new DemoDnsProvider();
}
```
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
2. **accessType**:必须指定对应的云平台的 access 类型名称。
3. **记录结构**:定义适合对应云平台的记录数据结构,至少包含 id 字段用于删除记录。
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`
5. **错误处理**:API 调用失败时应抛出明确的错误信息。
6. **实例化**:生产环境中应移除 `if (isDev())` 条件,确保插件在生产环境中也能被注册。
-201
View File
@@ -1,201 +0,0 @@
# 插件转换工具技能
## 什么是插件转换工具
插件转换工具是一个用于将 Certd 插件转换为 YAML 配置文件的命令行工具。它可以分析单个插件文件,识别插件类型,并生成对应的 YAML 配置,方便插件的注册和管理。
## 工具位置
`trae/skills/convert-plugin-to-yaml.js`
## 功能特性
- **单个插件转换**:支持指定单个插件文件进行转换,而不是扫描整个目录
- **自动类型识别**:自动识别插件类型(Access、Task、DNS Provider、Notification、Addon
- **详细日志输出**:提供详细的转换过程日志,便于调试
- **YAML 配置生成**:生成标准的 YAML 配置文件
- **配置文件保存**:自动将生成的配置保存到 `./metadata` 目录
- **可复用函数**:导出了可复用的函数,便于其他模块调用
## 使用方法
### 基本用法
```bash
node trae/skills/convert-plugin-to-yaml.js <插件文件路径>
```
### 示例
```bash
# 转换 Access 插件
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
# 转换 Task 插件
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
# 转换 DNS Provider 插件
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
```
## 转换过程
1. **加载插件模块**:使用 `import()` 动态加载指定的插件文件
2. **分析插件定义**:检查模块导出的对象,寻找带有 `define` 属性的插件
3. **识别插件类型**:根据插件的继承关系或属性识别插件类型
4. **生成 YAML 配置**:基于插件定义生成标准的 YAML 配置
5. **保存配置文件**:将生成的配置保存到 `./metadata` 目录
## 输出说明
### 命令行输出
执行转换命令后,工具会输出以下信息:
- 插件加载状态
- 插件导出的对象列表
- 插件类型识别结果
- 生成的 YAML 配置内容
- 配置文件保存路径
### 配置文件命名规则
生成的配置文件命名规则为:
```
<插件类型>[_<子类型>]_<插件名称>.yaml
```
例如:
- `access_demo.yaml`Access 插件)
- `deploy_DemoTest.yaml`Task 插件)
- `dnsProvider_demo.yaml`DNS Provider 插件)
## 插件类型识别逻辑
工具通过以下逻辑识别插件类型:
1. **DNS Provider**:如果插件定义中包含 `accessType` 属性
2. **Task**:如果插件继承自 `AbstractTaskPlugin`
3. **Notification**:如果插件继承自 `BaseNotification`
4. **Access**:如果插件继承自 `BaseAccess`
5. **Addon**:如果插件继承自 `BaseAddon`
## 注意事项
1. **文件路径**:插件文件路径可以是相对路径或绝对路径
2. **文件格式**:仅支持 `.js` 文件,不支持 `.ts` 文件(需要先编译)
3. **依赖安装**:执行前确保已安装所有必要的依赖
4. **配置目录**:如果 `./metadata` 目录不存在,工具会自动创建
5. **错误处理**:如果插件加载失败或识别失败,工具会输出错误信息但不会终止执行
## 代码结构
### 主要函数
1. **isPrototypeOf(value, cls)**:检查对象是否是指定类的原型
2. **loadSingleModule(filePath)**:加载单个插件模块
3. **convertSinglePlugin(pluginPath)**:分析单个插件并生成 YAML 配置
4. **main()**:主函数,处理命令行参数并执行转换
### 导出函数
工具导出了以下函数,便于其他模块调用:
```javascript
export {
convertSinglePlugin, // 转换单个插件
loadSingleModule, // 加载单个模块
isPrototypeOf // 检查原型关系
};
```
## 应用场景
1. **插件开发**:在开发新插件时,快速生成配置文件
2. **插件调试**:查看插件的内部定义和配置
3. **插件管理**:批量转换现有插件为标准配置格式
4. **自动化构建**:集成到构建流程中,自动生成插件配置
## 示例输出
### 转换 Access 插件示例
```bash
$ node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
插件模块导出了 1 个对象: DemoAccess
处理插件: DemoAccess
插件类型: access
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
生成的 YAML 配置:
name: demo
title: 授权插件示例
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
icon: clarity:plugin-line
pluginType: access
type: builtIn
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
YAML 配置已保存到: ./metadata/access_demo.yaml
插件转换完成!
```
### 转换 Task 插件示例
```bash
$ node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
插件模块导出了 1 个对象: DemoTest
处理插件: DemoTest
插件类型: deploy
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
生成的 YAML 配置:
name: DemoTest
title: Demo-测试插件
desc: ""
icon: clarity:plugin-line
group: other
default:
strategy:
runStrategy: SkipWhenSucceed
pluginType: deploy
type: builtIn
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
YAML 配置已保存到: ./metadata/deploy_DemoTest.yaml
插件转换完成!
```
## 故障排除
### 常见问题
1. **模块加载失败**
- 原因:插件文件依赖未安装或路径错误
- 解决:确保已安装所有依赖,检查文件路径是否正确
2. **插件类型识别失败**
- 原因:插件未正确继承基类或缺少必要的属性
- 解决:检查插件代码,确保正确继承对应的基类
3. **YAML 配置生成失败**
- 原因:插件定义格式不正确
- 解决:检查插件的 `define` 属性格式是否正确
4. **配置文件保存失败**
- 原因:权限不足或磁盘空间不足
- 解决:确保有足够的权限和磁盘空间
### 调试建议
- **查看详细日志**:工具会输出详细的转换过程日志,仔细查看日志信息
- **检查插件代码**:确保插件代码符合 Certd 插件开发规范
- **尝试简化插件**:如果转换失败,尝试创建一个最小化的插件示例进行测试
- **检查依赖版本**:确保使用的依赖版本与 Certd 兼容
## 总结
插件转换工具是一个方便实用的工具,它可以帮助开发者快速生成插件的 YAML 配置文件,简化插件的注册和管理过程。通过命令行参数指定单个插件文件,工具会自动完成类型识别、配置生成和保存等操作,大大提高了插件开发和管理的效率。
@@ -1 +0,0 @@
我需要将一个插件转换为 YAML 配置文件。请指导我如何使用插件转换工具。
@@ -1,95 +0,0 @@
# 插件转换工具使用指南
## 工具说明
插件转换工具用于将单个 Certd 插件转换为 YAML 配置文件,方便插件的注册和管理。
## 工具位置
`.trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js`
## 使用方法
### 基本用法
```bash
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js <插件文件路径>
```
### 示例
#### 转换 Access 插件
```bash
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
```
#### 转换 Task 插件
```bash
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
```
#### 转换 DNS Provider 插件
```bash
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
```
## 转换过程
1. **加载插件模块**:使用 `import()` 动态加载指定的插件文件
2. **分析插件定义**:检查模块导出的对象,寻找带有 `define` 属性的插件
3. **识别插件类型**:根据插件的继承关系或属性识别插件类型
4. **生成 YAML 配置**:基于插件定义生成标准的 YAML 配置
5. **保存配置文件**:将生成的配置保存到 `./metadata` 目录
## 输出说明
### 命令行输出
执行转换命令后,工具会输出以下信息:
- 插件加载状态
- 插件导出的对象列表
- 插件类型识别结果
- 生成的 YAML 配置内容
- 配置文件保存路径
### 配置文件命名规则
生成的配置文件命名规则为:
```
<插件类型>[_<子类型>]_<插件名称>.yaml
```
例如:
- `access_demo.yaml`Access 插件)
- `deploy_DemoTest.yaml`Task 插件)
- `dnsProvider_demo.yaml`DNS Provider 插件)
## 示例输出
### 转换 Access 插件示例
```bash
$ node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
插件模块导出了 1 个对象: DemoAccess
处理插件: DemoAccess
插件类型: access
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
生成的 YAML 配置:
name: demo
title: 授权插件示例
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
icon: clarity:plugin-line
pluginType: access
type: builtIn
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
YAML 配置已保存到: ./metadata/access_demo.yaml
插件转换完成!
```
@@ -1,160 +0,0 @@
// 转换单个插件为 YAML 配置的技能脚本
import path from "path";
import fs from "fs";
import { pathToFileURL } from "node:url";
import * as yaml from "js-yaml";
import { AbstractTaskPlugin, BaseAccess, BaseNotification} from "@certd/pipeline";
import { BaseAddon} from "@certd/lib-server";
/**
* 检查对象是否是指定类的原型
*/
function isPrototypeOf(value, cls) {
return cls.prototype.isPrototypeOf(value.prototype);
}
/**
* 加载单个插件模块
*/
async function loadSingleModule(filePath) {
try {
// 转换为 file:// URLWindows 必需)
const moduleUrl = pathToFileURL(filePath).href;
const module = await import(moduleUrl);
return module.default || module;
} catch (err) {
console.error(`加载模块 ${filePath} 失败:`, err);
return null;
}
}
/**
* 分析单个插件并生成 YAML 配置
*/
async function convertSinglePlugin(pluginPath) {
console.log(`开始转换插件: ${pluginPath}`);
// 加载插件模块
const module = await loadSingleModule(pluginPath);
if (!module) {
console.error("加载插件失败,退出");
return;
}
// 处理模块中的所有导出
const entry = Object.entries(module);
if (entry.length === 0) {
console.error("插件模块没有导出任何内容");
return;
}
console.log(`插件模块导出了 ${entry.length} 个对象: ${entry.map(([name]) => name).join(", ")}`);
// 处理每个导出的对象
for (const [name, value] of entry) {
// 检查是否是插件(有 define 属性)
if (!value.define) {
console.log(`跳过非插件对象: ${name}`);
continue;
}
console.log(`处理插件: ${name}`);
// 构建插件定义
const pluginDefine = {
...value.define
};
let subType = "";
// 确定插件类型
if (pluginDefine.accessType) {
pluginDefine.pluginType = "dnsProvider";
} else if (isPrototypeOf(value, AbstractTaskPlugin)) {
pluginDefine.pluginType = "deploy";
} else if (isPrototypeOf(value, BaseNotification)) {
pluginDefine.pluginType = "notification";
} else if (isPrototypeOf(value, BaseAccess)) {
pluginDefine.pluginType = "access";
} else if (isPrototypeOf(value, BaseAddon)) {
pluginDefine.pluginType = "addon";
subType = "_" + (pluginDefine.addonType || "");
} else {
console.log(`[warning] 未知的插件类型:${pluginDefine.name}`);
continue;
}
pluginDefine.type = "builtIn";
// 计算脚本文件路径
const relativePath = path.relative(process.cwd(), pluginPath);
const scriptFilePath = relativePath.replace(/\\/g, "/").replace(/\.js$/, ".js");
pluginDefine.scriptFilePath = scriptFilePath;
console.log(`插件类型: ${pluginDefine.pluginType}`);
console.log(`脚本路径: ${scriptFilePath}`);
// 生成 YAML 配置
const yamlContent = yaml.dump(pluginDefine);
console.log("\n生成的 YAML 配置:");
console.log(yamlContent);
// 可选:保存到文件
const outputDir = "./metadata";
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const outputFileName = `${pluginDefine.pluginType}${subType}_${pluginDefine.name}.yaml`;
const outputPath = path.join(outputDir, outputFileName);
fs.writeFileSync(outputPath, yamlContent, 'utf8');
console.log(`\nYAML 配置已保存到: ${outputPath}`);
return pluginDefine;
}
console.error("未找到有效的插件定义");
}
/**
* 主函数
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("请指定插件文件路径");
console.log("用法: node convert-plugin-to-yaml.js <插件文件路径>");
process.exit(1);
}
const pluginPath = args[0];
if (!fs.existsSync(pluginPath)) {
console.error(`插件文件不存在: ${pluginPath}`);
process.exit(1);
}
try {
await convertSinglePlugin(pluginPath);
console.log("\n插件转换完成!");
} catch (error) {
console.error("转换过程中出错:", error);
process.exit(1);
}
}
// 如果直接运行此脚本
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
main();
}
// 导出函数,以便其他模块使用
export {
convertSinglePlugin,
loadSingleModule,
isPrototypeOf
};
-388
View File
@@ -1,388 +0,0 @@
# Task 插件开发技能
## 什么是 Task 插件
Task 插件是 Certd 系统中的部署任务插件,它继承自 `AbstractTaskPlugin` 类,被流水线调用 `execute` 方法,将证书部署到对应的应用上。
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { optionsUtils } from '@certd/basic';
import { CertApplyPluginNames} from '@certd/plugin-cert';
```
### 2. 使用 @IsTaskPlugin 注解注册插件
```typescript
@IsTaskPlugin({
// 命名规范,插件类型+功能,大写字母开头,驼峰命名
name: 'DemoTest',
title: 'Demo-测试插件', // 插件标题
icon: 'clarity:plugin-line', // 插件图标
// 插件分组
group: pluginGroups.other.key,
default: {
// 默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
// 类名规范,跟上面插件名称(name)一致
export class DemoTest extends AbstractTaskPlugin {
// 插件实现...
}
```
### 3. 定义任务输入参数
使用 `@TaskInput` 注解定义任务输入参数:
```typescript
// 测试参数
@TaskInput({
title: '属性示例',
value: '默认值',
component: {
// 前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
name: 'a-input',
vModel: 'value', // 双向绑定组件的 props 名称
},
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
required: false, // 是否必填
})
text!: string;
// 证书选择,此项必须要有
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
// 前端可以展示,当前申请的证书域名列表
certDomains!: string[];
// 授权选择框
@TaskInput({
title: 'demo授权',
helper: 'demoAccess授权',
component: {
name: 'access-selector',
type: 'demo', // 固定授权类型
},
// rules: [{ required: true, message: '此项必填' }],
// required: true, // 必填
})
accessId!: string;
```
### 4. 实现插件方法
#### 4.1 插件实例化时执行的方法
```typescript
// 插件实例化时执行的方法
async onInstance() {}
```
#### 4.2 插件执行方法
```typescript
// 插件执行方法
async execute(): Promise<void> {
const { select, text, cert, accessId } = this;
try {
const access = await this.getAccess(accessId);
this.logger.debug('access', access);
} catch (e) {
this.logger.error('获取授权失败', e);
}
try {
const certReader = new CertReader(cert);
this.logger.debug('certReader', certReader);
} catch (e) {
this.logger.error('读取crt失败', e);
}
this.logger.info('DemoTestPlugin execute');
this.logger.info('text:', text);
this.logger.info('select:', select);
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
// 具体的部署逻辑
// ...
}
```
#### 4.3 后端获取选项方法
```typescript
@TaskInput(
createRemoteSelectInputDefine({
title: '从后端获取选项',
helper: '选择时可以从后端获取选项',
action: DemoTest.prototype.onGetSiteList.name,
// 当以下参数变化时,触发获取选项
watches: ['certDomains', 'accessId'],
required: true,
})
)
siteName!: string | string[];
// 从后端获取选项的方法
async onGetSiteList(req: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
// @ts-ignore
const access = await this.getAccess(this.accessId);
// const siteRes = await access.GetDomainList(req);
// 以下是模拟数据
const siteRes = [
{ id: 1, siteName: 'site1.com' },
{ id: 2, siteName: 'site2.com' },
{ id: 3, siteName: 'site2.com' },
];
// 转换为前端所需要的格式
const options = siteRes.map((item: any) => {
return {
value: item.siteName,
label: item.siteName,
domain: item.siteName,
};
});
// 将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
```
## 注意事项
1. **插件命名**:插件名称应遵循命名规范,大写字母开头,驼峰命名。
2. **类名规范**:类名应与插件名称(name)一致。
3. **证书选择**:必须包含证书选择参数,用于获取前置任务输出的域名证书。
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`
5. **错误处理**:执行过程中的错误应被捕获并记录。
6. **授权获取**:使用 `this.getAccess(accessId)` 获取授权信息。
## 完整示例
```typescript
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { optionsUtils } from '@certd/basic';
import { CertApplyPluginNames} from '@certd/plugin-cert';
@IsTaskPlugin({
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
name: 'DemoTest',
title: 'Demo-测试插件',
icon: 'clarity:plugin-line',
//插件分组
group: pluginGroups.other.key,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
//类名规范,跟上面插件名称(name)一致
export class DemoTest extends AbstractTaskPlugin {
//测试参数
@TaskInput({
title: '属性示例',
value: '默认值',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
name: 'a-input',
vModel: 'value', //双向绑定组件的props名称
},
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
required: false, //是否必填
})
text!: string;
//测试参数
@TaskInput({
title: '选择框',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-auto-complete',
vModel: 'value',
options: [
//选项列表
{ label: '动态显', value: 'show' },
{ label: '动态隐', value: 'hide' },
],
},
})
select!: string;
@TaskInput({
title: '动态显隐',
helper: '我会根据选择框的值进行显隐',
show: true, //动态计算的值会覆盖它
//动态计算脚本, mergeScript返回的对象会合并当前配置,此处演示 show的值会被动态计算结果覆盖,show的值根据用户选择的select的值决定
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.select === 'show';
})
}
`,
})
showText!: string;
//测试参数
@TaskInput({
title: '多选框',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-select',
vModel: 'value',
mode: 'tags',
multiple: true,
options: [
{ value: '1', label: '选项1' },
{ value: '2', label: '选项2' },
],
},
})
multiSelect!: string;
//测试参数
@TaskInput({
title: 'switch',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/switch-cn
name: 'a-switch',
vModel: 'checked',
},
})
switch!: boolean;
//证书选择,此项必须要有
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
//前端可以展示,当前申请的证书域名列表
certDomains!: string[];
//授权选择框
@TaskInput({
title: 'demo授权',
helper: 'demoAccess授权',
component: {
name: 'access-selector',
type: 'demo', //固定授权类型
},
// rules: [{ required: true, message: '此项必填' }],
// required: true, //必填
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '从后端获取选项',
helper: '选择时可以从后端获取选项',
action: DemoTest.prototype.onGetSiteList.name,
//当以下参数变化时,触发获取选项
watches: ['certDomains', 'accessId'],
required: true,
})
)
siteName!: string | string[];
//插件实例化时执行的方法
async onInstance() {}
//插件执行方法
async execute(): Promise<void> {
const { select, text, cert, accessId } = this;
try {
const access = await this.getAccess(accessId);
this.logger.debug('access', access);
} catch (e) {
this.logger.error('获取授权失败', e);
}
try {
const certReader = new CertReader(cert);
this.logger.debug('certReader', certReader);
} catch (e) {
this.logger.error('读取crt失败', e);
}
this.logger.info('DemoTestPlugin execute');
this.logger.info('text:', text);
this.logger.info('select:', select);
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
// const res = await this.http.request({
// url: 'https://api.demo.com',
// method: 'GET',
// });
// if (res.code !== 0) {
// //检查res是否报错,你需要抛异常,来结束插件执行,否则会判定为执行成功,下次执行时会跳过本任务
// throw new Error(res.message);
// }
// this.logger.info('部署成功:', res);
}
//此方法演示,如何让前端在添加插件时可以从后端获取选项,这里是后端返回选项的方法
async onGetSiteList(req: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
// @ts-ignore
const access = await this.getAccess(this.accessId);
// const siteRes = await access.GetDomainList(req);
//以下是模拟数据
const siteRes = [
{ id: 1, siteName: 'site1.com' },
{ id: 2, siteName: 'site2.com' },
{ id: 3, siteName: 'site2.com' },
];
//转换为前端所需要的格式
const options = siteRes.map((item: any) => {
return {
value: item.siteName,
label: item.siteName,
domain: item.siteName,
};
});
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
}
```
@@ -1 +0,0 @@
我需要开发一个 Task 插件,用于将申请的证书部署到指定的应用系统中。请指导我如何实现。
@@ -1,129 +0,0 @@
# Task 插件开发指南
## 开发步骤
### 1. 导入必要的依赖
```typescript
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { optionsUtils } from '@certd/basic';
import { CertApplyPluginNames} from '@certd/plugin-cert';
```
### 2. 使用 @IsTaskPlugin 注解注册插件
```typescript
@IsTaskPlugin({
// 命名规范,插件类型+功能,大写字母开头,驼峰命名
name: 'DemoTest',
title: 'Demo-测试插件', // 插件标题
icon: 'clarity:plugin-line', // 插件图标
// 插件分组
group: pluginGroups.other.key,
default: {
// 默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
// 类名规范,跟上面插件名称(name)一致
export class DemoTest extends AbstractTaskPlugin {
// 插件实现...
}
```
### 3. 定义任务输入参数
使用 `@TaskInput` 注解定义任务输入参数:
```typescript
// 测试参数
@TaskInput({
title: '属性示例',
value: '默认值',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
name: 'a-input',
vModel: 'value', //双向绑定组件的props名称
},
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
required: false, //是否必填
})
text!: string;
//证书选择,此项必须要有
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
//前端可以展示,当前申请的证书域名列表
certDomains!: string[];
//授权选择框
@TaskInput({
title: 'demo授权',
helper: 'demoAccess授权',
component: {
name: 'access-selector',
type: 'demo', //固定授权类型
},
// rules: [{ required: true, message: '此项必填' }],
// required: true, //必填
})
accessId!: string;
```
### 4. 实现插件方法
```typescript
//插件实例化时执行的方法
async onInstance() {}
//插件执行方法
async execute(): Promise<void> {
const { select, text, cert, accessId } = this;
try {
const access = await this.getAccess(accessId);
this.logger.debug('access', access);
} catch (e) {
this.logger.error('获取授权失败', e);
}
try {
const certReader = new CertReader(cert);
this.logger.debug('certReader', certReader);
} catch (e) {
this.logger.error('读取crt失败', e);
}
this.logger.info('DemoTestPlugin execute');
this.logger.info('text:', text);
this.logger.info('select:', select);
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
// 具体的部署逻辑
// ...
}
```
## 注意事项
1. **插件命名**:插件名称应遵循命名规范,大写字母开头,驼峰命名。
2. **类名规范**:类名应与插件名称(name)一致。
3. **证书选择**:必须包含证书选择参数,用于获取前置任务输出的域名证书。
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`
5. **错误处理**:执行过程中的错误应被捕获并记录。
6. **授权获取**:使用 `this.getAccess(accessId)` 获取授权信息。
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
### Bug Fixes
* 修复1panel2.1.0新版本测试失败的问题 ([8ef1f2e](https://github.com/certd/certd/commit/8ef1f2e395ea5969a95f55535e6c16a65e2b463b))
### Performance Improvements
* 优化登陆页面的黑暗模式 ([e47edda](https://github.com/certd/certd/commit/e47eddaa858f8fffe7a40dfbd14e8cda1dcba4ac))
* 支持自定义发件人名称,格式:名称<邮箱> ([bab9adc](https://github.com/certd/certd/commit/bab9adce240108d4291eedc67e04abc4a01019e0))
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Bug Fixes
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.38.10"
"version": "1.38.11"
}
+9 -9
View File
@@ -15,25 +15,25 @@
},
"scripts": {
"start": "lerna bootstrap --hoist",
"start:server": "cd ./packages/ui/certd-server && pnpm start",
"start:server": "cd ./packages/ui/certd-server && npm start",
"devb": "lerna run dev-build",
"i-all": "lerna link && lerna exec npm install ",
"publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ",
"afterpublishOnly": "pnpm run copylogs && time /t >trigger/build.trigger && git add ./trigger/build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && pnpm run commitAll",
"publish": "npm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && npm run afterpublishOnly ",
"afterpublishOnly": "npm run copylogs && time /t >trigger/build.trigger && git add ./trigger/build.trigger && git commit -m \"build: trigger build image\" && TIMEOUT /T 10 && npm run commitAll",
"transform-sql": "cd ./packages/ui/certd-server/db/ && node --experimental-json-modules transform.js",
"plugin-doc-gen": "cd ./packages/ui/certd-server/ && pnpm run export-metadata",
"commitAll": "git add . && git commit -m \"build: publish\" && git push && pnpm run commitPro",
"plugin-doc-gen": "cd ./packages/ui/certd-server/ && npm run export-metadata",
"commitAll": "git add . && git commit -m \"build: publish\" && git push && npm run commitPro",
"commitPro": "cd ./packages/pro/ && git add . && git commit -m \"build: publish\" && git push",
"copylogs": "copyfiles \"CHANGELOG.md\" ./docs/guide/changelogs/",
"prepublishOnly1": "pnpm run check && lerna run build ",
"prepublishOnly2": "pnpm run check && pnpm run before-build && lerna run build && pnpm run plugin-doc-gen",
"before-build": "pnpm run transform-sql && cd ./packages/core/basic && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"prepublishOnly1": "npm run check && lerna run build ",
"prepublishOnly2": "npm run check && npm run before-build && lerna run build && npm run plugin-doc-gen",
"before-build": "npm run transform-sql && cd ./packages/core/basic && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules ./scripts/deploy.js ",
"check": "node --experimental-json-modules ./scripts/publish-check.js",
"init": "lerna run build",
"init:dev": "lerna run build",
"docs:dev": "vitepress dev docs",
"docs:build": "pnpm run copylogs && vitepress build docs",
"docs:build": "npm run copylogs && vitepress build docs",
"docs:preview": "vitepress preview docs",
"pub": "echo 1",
"dev": "pnpm run -r --parallel compile ",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/publishlab/node-acme-client/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/acme-client
## [1.38.10](https://github.com/publishlab/node-acme-client/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/acme-client
+3 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.38.10",
"@certd/basic": "^1.38.11",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -53,7 +53,7 @@
"prepublishOnly": "npm run build-docs",
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
"pub": "npm publish",
"compile": "echo '1'"
"compile": "tsc --skipLibCheck --watch"
},
"repository": {
"type": "git",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/basic
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Performance Improvements
+1 -1
View File
@@ -1 +1 @@
00:20
23:40
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -323,7 +323,6 @@ export function createAgent(opts: CreateAgentOptions = {}) {
{
autoSelectFamily: true,
autoSelectFamilyAttemptTimeout: 1000,
connectTimeout: 5000, // 连接建立超时
},
opts
);
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/pipeline
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Bug Fixes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,8 +18,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.38.10",
"@certd/plus-core": "^1.38.10",
"@certd/basic": "^1.38.11",
"@certd/plus-core": "^1.38.11",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
-3
View File
@@ -123,9 +123,6 @@ export type TaskInstanceContext = {
//用户信息
user: UserInfo;
//项目id
projectId?: number;
emitter: TaskEmitter;
//service 容器
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/lib-huawei
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/lib-huawei
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/lib-iframe
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/lib-iframe
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/jdcloud
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/jdcloud
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.38.10",
"version": "1.38.11",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/lib-k8s
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/lib-k8s
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.38.10",
"@certd/basic": "^1.38.11",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/lib-server
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/lib-server
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.38.10",
"version": "1.38.11",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -28,11 +28,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.38.10",
"@certd/basic": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@certd/plugin-lib": "^1.38.10",
"@certd/plus-core": "^1.38.10",
"@certd/acme-client": "^1.38.11",
"@certd/basic": "^1.38.11",
"@certd/pipeline": "^1.38.11",
"@certd/plugin-lib": "^1.38.11",
"@certd/plus-core": "^1.38.11",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -1,17 +1,11 @@
import { ApplicationContext, Inject } from '@midwayjs/core';
import type {IMidwayContainer} from '@midwayjs/core';
import { Inject } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import { Constants } from './constants.js';
import { isEnterprise } from './mode.js';
export abstract class BaseController {
@Inject()
ctx: koa.Context;
@ApplicationContext()
applicationContext: IMidwayContainer;
/**
*
* @param data
@@ -61,69 +55,4 @@ export abstract class BaseController {
}
}
async getProjectId(permission:string) {
if (!isEnterprise()) {
return null
}
let projectIdStr = this.ctx.headers["project-id"] as string;
if (!projectIdStr){
projectIdStr = this.ctx.request.query["projectId"] as string;
}
if (!projectIdStr){
return null
}
if (!projectIdStr) {
throw new Error("projectId 不能为空")
}
const userId = this.getUserId()
const projectId = parseInt(projectIdStr)
await this.checkProjectPermission(userId, projectId,permission)
return projectId;
}
async getProjectUserId(permission:string){
let userId = this.getUserId()
const projectId = await this.getProjectId(permission)
if(projectId){
userId = 0
}
return {
projectId,userId
}
}
async getProjectUserIdRead(){
return await this.getProjectUserId("read")
}
async getProjectUserIdWrite(){
return await this.getProjectUserId("write")
}
async getProjectUserIdAdmin(){
return await this.getProjectUserId("admin")
}
async checkProjectPermission(userId: number, projectId: number,permission:string) {
const projectService:any = await this.applicationContext.getAsync("projectService");
await projectService.checkPermission({userId,projectId,permission})
}
/**
*
* @param service
* @param id
*/
async checkOwner(service:any,id:number,permission:string,allowAdmin:boolean = false){
let { projectId,userId } = await this.getProjectUserId(permission)
const authService:any = await this.applicationContext.getAsync("authService");
if (projectId) {
await authService.checkProjectId(service, id, projectId);
}else{
if(allowAdmin){
await authService.checkUserIdButAllowAdmin(this.ctx, service, id);
}else{
await authService.checkUserId(this.ctx, service, id);
}
}
return {projectId,userId}
}
}
@@ -206,41 +206,30 @@ export abstract class BaseService<T> {
return await qb.getMany();
}
async checkUserId(ids: number | number[] = 0, userId: number, userKey = 'userId') {
if (ids == null) {
throw new ValidateException('id不能为空');
}
if (userId == null) {
throw new ValidateException('userId不能为空');
}
if (!Array.isArray(ids)) {
ids = [ids];
}
const res = await this.getRepository().find({
async checkUserId(id: any = 0, userId: number, userKey = 'userId') {
const res = await this.getRepository().findOne({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
select: { [userKey]: true },
where: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id: In(ids),
[userKey]: userId,
id,
},
});
if (!res || res.length === ids.length) {
if (!res || res[userKey] === userId) {
return;
}
throw new PermissionException('权限不足');
}
async batchDelete(ids: number[], userId: number,projectId?:number) {
if(userId!=null){
async batchDelete(ids: number[], userId: number) {
if(userId >0){
const list = await this.getRepository().find({
where: {
// @ts-ignore
id: In(ids),
userId,
projectId,
},
})
// @ts-ignore
@@ -253,19 +242,4 @@ export abstract class BaseService<T> {
async findOne(options: FindOneOptions<T>) {
return await this.getRepository().findOne(options);
}
}
export function checkUserProjectParam(userId: number, projectId: number) {
if (projectId != null ){
if( userId !==0) {
throw new ValidateException('userId projectId 错误');
}
return true
}else{
if( userId > 0) {
return true
}
throw new ValidateException('userId不能为空');
}
}
@@ -5,4 +5,3 @@ export * from './enum-item.js';
export * from './exception/index.js';
export * from './result.js';
export * from './base-service.js';
export * from "./mode.js"
@@ -1,12 +0,0 @@
let adminMode = "saas"
export function setAdminMode(mode:string = "saas"){
adminMode = mode
}
export function getAdminMode(){
return adminMode
}
export function isEnterprise(){
return adminMode === "enterprise"
}
@@ -65,8 +65,6 @@ export class SysPublicSettings extends BaseSettings {
}> = {};
notice?: string;
adminMode?: "enterprise" | "saas" = "saas";
}
export class SysPrivateSettings extends BaseSettings {
@@ -7,10 +7,9 @@ import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, Sy
import { getAllSslProviderDomains, setSslProviderReverseProxies } from '@certd/acme-client';
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
import * as dns from 'node:dns';
import { BaseService, setAdminMode } from '../../../basic/index.js';
import { BaseService } from '../../../basic/index.js';
import { executorQueue } from '../../basic/service/executor-queue.js';
import { isComm } from '@certd/plus-core';
const { merge } = mergeUtils;
const {merge} = mergeUtils;
/**
*
*/
@@ -117,15 +116,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
}
async savePublicSettings(bean: SysPublicSettings) {
if(isComm()){
if(bean.adminMode === 'enterprise'){
throw new Error("商业版不支持使用企业管理模式")
}
}
await this.saveSetting(bean);
//让设置生效
await this.reloadPublicSettings();
}
async getPrivateSettings(): Promise<SysPrivateSettings> {
@@ -146,33 +137,23 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
await this.reloadPrivateSettings();
}
async reloadSettings() {
await this.reloadPrivateSettings()
await this.reloadPublicSettings()
}
async reloadPublicSettings() {
const publicSetting = await this.getPublicSettings()
setAdminMode(publicSetting.adminMode)
}
async reloadPrivateSettings() {
const privateSetting = await this.getPrivateSettings();
const bean = await this.getPrivateSettings();
const opts = {
httpProxy: privateSetting.httpProxy,
httpsProxy: privateSetting.httpsProxy,
httpProxy: bean.httpProxy,
httpsProxy: bean.httpsProxy,
};
setGlobalProxy(opts);
if (privateSetting.dnsResultOrder) {
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
if (bean.dnsResultOrder) {
dns.setDefaultResultOrder(bean.dnsResultOrder as any);
}
if (privateSetting.pipelineMaxRunningCount) {
executorQueue.setMaxRunningCount(privateSetting.pipelineMaxRunningCount);
if (bean.pipelineMaxRunningCount){
executorQueue.setMaxRunningCount(bean.pipelineMaxRunningCount);
}
setSslProviderReverseProxies(privateSetting.reverseProxies);
setSslProviderReverseProxies(bean.reverseProxies);
}
async updateByKey(key: string, setting: any) {
@@ -21,9 +21,6 @@ export class AccessEntity {
@Column({ name: 'encrypt_setting', comment: '已加密设置', length: 10240, nullable: true })
encryptSetting: string;
@Column({ name: 'project_id', comment: '项目id' })
projectId: number;
@Column({
name: 'create_time',
comment: '创建时间',
@@ -2,19 +2,17 @@ import { IAccessService } from '@certd/pipeline';
export class AccessGetter implements IAccessService {
userId: number;
projectId?: number;
getter: <T>(id: any, userId?: number, projectId?: number) => Promise<T>;
constructor(userId: number, projectId: number, getter: (id: any, userId: number, projectId?: number) => Promise<any>) {
getter: <T>(id: any, userId?: number) => Promise<T>;
constructor(userId: number, getter: (id: any, userId: number) => Promise<any>) {
this.userId = userId;
this.projectId = projectId;
this.getter = getter;
}
async getById<T = any>(id: any) {
return await this.getter<T>(id, this.userId, this.projectId);
return await this.getter<T>(id, this.userId);
}
async getCommonById<T = any>(id: any) {
return await this.getter<T>(id, 0,null);
return await this.getter<T>(id, 0);
}
}
@@ -129,11 +129,10 @@ export class AccessService extends BaseService<AccessEntity> {
id: entity.id,
name: entity.name,
userId: entity.userId,
projectId: entity.projectId,
};
}
async getAccessById(id: any, checkUserId: boolean, userId?: number, projectId?: number): Promise<any> {
async getAccessById(id: any, checkUserId: boolean, userId?: number): Promise<any> {
const entity = await this.info(id);
if (entity == null) {
throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`);
@@ -146,9 +145,6 @@ export class AccessService extends BaseService<AccessEntity> {
throw new PermissionException('您对该Access授权无访问权限');
}
}
if (projectId != null && projectId !== entity.projectId) {
throw new PermissionException('您对该Access授权无访问权限');
}
// const access = accessRegistry.get(entity.type);
const setting = this.decryptAccessEntity(entity);
@@ -156,12 +152,12 @@ export class AccessService extends BaseService<AccessEntity> {
id: entity.id,
...setting,
};
const accessGetter = new AccessGetter(userId,projectId, this.getById.bind(this));
const accessGetter = new AccessGetter(userId, this.getById.bind(this));
return await newAccess(entity.type, input,accessGetter);
}
async getById(id: any, userId: number, projectId?: number): Promise<any> {
return await this.getAccessById(id, true, userId, projectId);
async getById(id: any, userId: number): Promise<any> {
return await this.getAccessById(id, true, userId);
}
decryptAccessEntity(entity: AccessEntity): any {
@@ -192,25 +188,23 @@ export class AccessService extends BaseService<AccessEntity> {
}
async getSimpleByIds(ids: number[], userId: any, projectId?: number) {
async getSimpleByIds(ids: number[], userId: any) {
if (ids.length === 0) {
return [];
}
if (userId==null) {
if (!userId) {
return [];
}
return await this.repository.find({
where: {
id: In(ids),
userId,
projectId,
},
select: {
id: true,
name: true,
type: true,
userId:true,
projectId:true,
userId:true
},
});
@@ -28,9 +28,6 @@ export class AddonEntity {
@Column({ name: 'is_default', comment: '是否默认', nullable: false, default: false })
isDefault: boolean;
@Column({ name: 'project_id', comment: '项目id' })
projectId: number;
@Column({
name: 'create_time',
@@ -70,8 +70,7 @@ export class AddonService extends BaseService<AddonEntity> {
name: entity.name,
userId: entity.userId,
addonType: entity.addonType,
type: entity.type,
projectId: entity.projectId
type: entity.type
};
}
@@ -85,18 +84,17 @@ export class AddonService extends BaseService<AddonEntity> {
}
async getSimpleByIds(ids: number[], userId: any,projectId?:number) {
async getSimpleByIds(ids: number[], userId: any) {
if (ids.length === 0) {
return [];
}
if (userId==null) {
if (!userId) {
return [];
}
return await this.repository.find({
where: {
id: In(ids),
userId,
projectId
userId
},
select: {
id: true,
@@ -111,12 +109,11 @@ export class AddonService extends BaseService<AddonEntity> {
}
async getDefault(userId: number, addonType: string,projectId?:number): Promise<any> {
async getDefault(userId: number, addonType: string): Promise<any> {
const res = await this.repository.findOne({
where: {
userId,
addonType,
projectId
addonType
},
order: {
isDefault: "DESC"
@@ -136,23 +133,21 @@ export class AddonService extends BaseService<AddonEntity> {
type: res.type,
name: res.name,
userId: res.userId,
setting,
projectId: res.projectId
setting
};
}
async setDefault(id: number, userId: number, addonType: string,projectId?:number) {
async setDefault(id: number, userId: number, addonType: string) {
if (!id) {
throw new ValidateException("id不能为空");
}
if (userId==null) {
if (!userId) {
throw new ValidateException("userId不能为空");
}
await this.repository.update(
{
userId,
addonType,
projectId
addonType
},
{
isDefault: false
@@ -162,8 +157,7 @@ export class AddonService extends BaseService<AddonEntity> {
{
id,
userId,
addonType,
projectId
addonType
},
{
isDefault: true
@@ -171,12 +165,12 @@ export class AddonService extends BaseService<AddonEntity> {
);
}
async getOrCreateDefault(opts: { addonType: string, type: string, inputs: any, userId: any,projectId?:number }) {
const { addonType, type, inputs, userId,projectId } = opts;
async getOrCreateDefault(opts: { addonType: string, type: string, inputs: any, userId: any }) {
const { addonType, type, inputs, userId } = opts;
const addonDefine = this.getDefineByType(type, addonType);
const defaultConfig = await this.getDefault(userId, addonType,projectId);
const defaultConfig = await this.getDefault(userId, addonType);
if (defaultConfig) {
return defaultConfig;
}
@@ -189,19 +183,17 @@ export class AddonService extends BaseService<AddonEntity> {
type: type,
name: addonDefine.title,
setting: JSON.stringify(setting),
isDefault: true,
projectId
isDefault: true
});
return this.buildAddonInstanceConfig(res);
}
async getOneByType(req:{addonType:string,type:string,userId:number,projectId?:number}) {
async getOneByType(req:{addonType:string,type:string,userId:number}) {
return await this.repository.findOne({
where: {
addonType: req.addonType,
type: req.type,
userId: req.userId,
projectId: req.projectId
userId: req.userId
}
});
}
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/midway-flyway-js
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.38.10",
"version": "1.38.11",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/plugin-cert
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/plugin-cert
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.38.10",
"@certd/basic": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@certd/plugin-lib": "^1.38.10",
"@certd/acme-client": "^1.38.11",
"@certd/basic": "^1.38.11",
"@certd/pipeline": "^1.38.11",
"@certd/plugin-lib": "^1.38.11",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
**Note:** Version bump only for package @certd/plugin-lib
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
**Note:** Version bump only for package @certd/plugin-lib
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.38.10",
"version": "1.38.11",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -22,10 +22,10 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.38.10",
"@certd/basic": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@certd/plus-core": "^1.38.10",
"@certd/acme-client": "^1.38.11",
"@certd/basic": "^1.38.11",
"@certd/pipeline": "^1.38.11",
"@certd/plus-core": "^1.38.11",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
+7
View File
@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.11](https://github.com/certd/certd/compare/v1.38.10...v1.38.11) (2026-02-16)
### Performance Improvements
* 优化登陆页面的黑暗模式 ([e47edda](https://github.com/certd/certd/commit/e47eddaa858f8fffe7a40dfbd14e8cda1dcba4ac))
* 支持自定义发件人名称,格式:名称<邮箱> ([bab9adc](https://github.com/certd/certd/commit/bab9adce240108d4291eedc67e04abc4a01019e0))
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.38.10",
"version": "1.38.11",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@certd/lib-iframe": "^1.38.11",
"@certd/pipeline": "^1.38.11",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -1,7 +1,7 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500" height="500" viewBox="0 0 500.000000 500.000000"
>
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
<path fill="#333" d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
transform="translate(70, 76) scale(6,6)"
></path>
</svg>

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 409 B

+1 -8
View File
@@ -3,7 +3,6 @@ import { get } from "lodash-es";
import { errorLog, errorCreate } from "./tools";
import { env } from "/src/utils/util.env";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "../store/project";
export class CodeError extends Error {
code: number;
@@ -139,17 +138,11 @@ function createRequestFunction(service: any) {
const configDefault = {
headers: {
"Content-Type": get(config, "headers.Content-Type", "application/json"),
} as any,
},
timeout: 30000,
baseURL: env.API,
data: {},
};
const projectStore = useProjectStore();
if (projectStore.isEnterprise && !config.url.startsWith("/sys")) {
configDefault.headers["project-id"] = projectStore.currentProjectId;
}
const userStore = useUserStore();
const token = userStore.getToken;
if (token != null) {
@@ -16,7 +16,6 @@ import { defineAsyncComponent } from "vue";
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
import EmailSelector from "./email-selector/index.vue";
import ValidTimeFormat from "./valid-time-format.vue";
import ProjectSelector from "./project-selector/index.vue";
export default {
install(app: any) {
app.component(
@@ -46,6 +45,5 @@ export default {
app.component("ExpiresTimeText", ExpiresTimeText);
app.use(vip);
app.use(Plugins);
app.component("ProjectSelector", ProjectSelector);
},
};
@@ -83,7 +83,6 @@ const props = defineProps<{
search?: boolean;
pager?: boolean;
value?: any[];
open?: boolean;
}>();
const emit = defineEmits<{
@@ -1,9 +0,0 @@
import { request } from "/src/api/service";
export async function MyProjectList() {
return await request({
url: "/enterprise/project/list",
method: "post",
data: {},
});
}
@@ -1,45 +0,0 @@
<template>
<a-dropdown class="project-selector">
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-for="item in projectStore.myProjects" :key="item.id">
{{ item.name }}
</a-menu-item>
</a-menu>
</template>
<div class="rounded pl-3 pr-3 px-2 py-1 flex-center flex pointer items-center bg-accent h-10 button-text" title="当前项目">
<fs-icon icon="ion:apps" class="mr-1"></fs-icon>
当前项目{{ projectStore.currentProject?.name || "..." }}
<fs-icon icon="ion:chevron-down-outline" class="ml-1"></fs-icon>
</div>
</a-dropdown>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useProjectStore } from "/@/store/project";
defineOptions({
name: "ProjectSelector",
});
const projectStore = useProjectStore();
onMounted(async () => {
await projectStore.reload();
});
function handleMenuClick({ key }: any) {
projectStore.changeCurrentProject(key);
window.location.reload();
}
</script>
<style lang="less">
.project-selector {
&.button-text {
min-width: 150px;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
@@ -10,7 +10,6 @@ import PageFooter from "./components/footer/index.vue";
import { useRouter } from "vue-router";
import MaxKBChat from "/@/components/ai/index.vue";
import { useI18n } from "vue-i18n";
import { useProjectStore } from "../store/project";
const { t } = useI18n();
@@ -78,13 +77,10 @@ const openChat = (q: string) => {
chatBox.value.openChat({ q });
};
provide("fn:ai.open", openChat);
const projectStore = useProjectStore();
</script>
<template>
<BasicLayout @clear-preferences-and-logout="handleLogout">
<template #header-left-0> </template>
<template #user-dropdown>
<UserDropdown :avatar="avatar" :menus="menus" :text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text="" @logout="handleLogout" />
</template>
@@ -92,16 +88,13 @@ const projectStore = useProjectStore();
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div v-if="projectStore.isEnterprise" class="ml-1 mr-2">
<project-selector class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button class="flex-center header-btn" mode="nav" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<vip-button class="flex-center header-btn" mode="nav" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
</div>
</template>
@@ -1,6 +1,6 @@
<template>
<div id="userLayout" :class="['user-layout-wrapper']">
<div class="login-container flex justify-start">
<div class="login-container flex justify-start dark:background-[#141414]">
<div class="user-layout-content flex-col justify-start">
<div class="top flex flex-col items-center justify-start">
<div class="header flex flex-row items-center">
@@ -59,6 +59,14 @@ const sysPublic: Ref<SysPublicSetting> = computed(() => {
</script>
<style lang="less">
.dark {
.login-container {
background: #141414 !important;
.desc {
color: rgba(227, 227, 227, 0.45) !important;
}
}
}
#userLayout.user-layout-wrapper {
height: 100%;
@@ -161,8 +161,6 @@ export default {
triggerType: "Trigger Type",
pipelineId: "Pipeline Id",
nextRunTime: "Next Run Time",
projectName: "Project",
adminId: "Admin",
},
pi: {
@@ -211,12 +209,6 @@ export default {
orderManager: "Order Management",
userSuites: "User Suites",
netTest: "Network Test",
enterpriseSetting: "Enterprise Settings",
projectManager: "Project Management",
projectUserManager: "Project User Management",
myProjectManager: "My Projects",
myProjectDetail: "Project Detail",
},
certificateRepo: {
title: "Certificate Repository",
@@ -698,6 +690,7 @@ export default {
password: "Password",
pleaseEnterPassword: "Please enter password",
qqEmailAuthCodeHelper: "If using QQ email, get an authorization code in QQ email settings as the password",
senderEmailHelper: "You can use the format: Name<Email> to set the sender name, e.g.: autossl<certd@example.com>",
senderEmail: "Sender Email",
pleaseEnterSenderEmail: "Please enter sender email",
useSsl: "Use SSL",
@@ -795,10 +788,6 @@ export default {
pipelineSetting: "Pipeline Settings",
oauthSetting: "OAuth2 Settings",
networkSetting: "Network Settings",
adminModeSetting: "Admin Mode Settings",
adminModeHelper: "enterprise mode : allow to create and manage pipelines, roles, users, etc.\n saas mode : only allow to create and manage pipelines",
enterpriseMode: "Enterprise Mode",
saasMode: "SaaS Mode",
showRunStrategy: "Show RunStrategy",
showRunStrategyHelper: "Allow modify the run strategy of the task",
@@ -877,18 +866,6 @@ export default {
fromType: "From Type",
expirationDate: "Expiration Date",
},
ent: {
projectName: "Project Name",
projectDescription: "Project Description",
projectDetailManager: "Project Detail",
projectDetailDescription: "Manage Project Members",
projectPermission: "Permission",
permission: {
read: "Read",
write: "Write",
admin: "Admin",
},
},
addonSelector: {
select: "Select",
placeholder: "select please",
@@ -168,8 +168,6 @@ export default {
triggerType: "触发类型",
pipelineId: "流水线Id",
nextRunTime: "下次运行时间",
projectName: "项目",
adminId: "管理员",
},
pi: {
validTime: "流水线有效期",
@@ -217,12 +215,6 @@ export default {
orderManager: "订单管理",
userSuites: "用户套餐",
netTest: "网络测试",
enterpriseManager: "企业管理设置",
projectManager: "项目管理",
projectDetail: "项目详情",
enterpriseSetting: "企业设置",
myProjectManager: "我的项目",
myProjectDetail: "项目详情",
},
certificateRepo: {
title: "证书仓库",
@@ -312,8 +304,6 @@ export default {
days: "天",
lastCheckTime: "上次检查时间",
disabled: "禁用启用",
ipAddress: "IP地址",
ipAddressHelper: "填写则固定检查此IP,不从DNS获取域名的IP地址",
ipCheck: "开启IP检查",
ipCheckHelper: "开启后,会检查IP(或源站)上的证书有效期",
ipSyncAuto: "自动同步IP",
@@ -711,6 +701,7 @@ export default {
password: "密码",
pleaseEnterPassword: "请输入密码",
qqEmailAuthCodeHelper: "如果是qq邮箱,需要到qq邮箱的设置里面申请授权码作为密码",
senderEmailHelper: "您可以使用 名称<邮箱> 的格式,来修改发件人名称,例如: autossl<certd@example.com>",
senderEmail: "发件邮箱",
pleaseEnterSenderEmail: "请输入发件邮箱",
useSsl: "是否ssl",
@@ -804,12 +795,6 @@ export default {
pipelineSetting: "流水线设置",
oauthSetting: "第三方登录",
networkSetting: "网络设置",
adminModeSetting: "管理模式",
adminModeHelper: "企业管理模式: 企业内部使用,通过项目来隔离权限,流水线、授权数据属于项目。\nsaas模式:供外部用户注册使用,各个用户之间数据隔离,流水线、授权数据属于用户。",
adminMode: "管理模式",
enterpriseMode: "企业模式",
saasMode: "SaaS模式",
showRunStrategy: "显示运行策略选择",
showRunStrategyHelper: "任务设置中是否允许选择运行策略",
@@ -900,16 +885,4 @@ export default {
select: "选择",
placeholder: "请选择",
},
ent: {
projectName: "项目名称",
projectDescription: "项目描述",
projectDetailManager: "项目详情",
projectDetailDescription: "管理项目成员",
projectPermission: "权限",
permission: {
read: "读取",
write: "写入",
admin: "管理员",
},
},
};
@@ -1,7 +1,6 @@
import { useSettingStore } from "/@/store/settings";
import aboutResource from "/@/router/source/modules/about";
import i18n from "/@/locales/i18n";
import { useProjectStore } from "/@/store/project";
export const certdResources = [
{
@@ -15,33 +14,6 @@ export const certdResources = [
order: 0,
},
children: [
{
title: "certd.sysResources.myProjectManager",
name: "MyProjectManager",
path: "/certd/project",
component: "/certd/project/index.vue",
meta: {
show: () => {
const projectStore = useProjectStore();
return projectStore.isEnterprise;
},
icon: "ion:apps",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "certd.sysResources.myProjectDetail",
name: "MyProjectDetail",
path: "/certd/project/detail",
component: "/certd/project/detail/index.vue",
meta: {
isMenu: false,
show: true,
icon: "ion:apps",
permission: "sys:settings:edit",
},
},
{
title: "certd.pipeline",
name: "PipelineManager",
@@ -40,30 +40,6 @@ export const sysResources = [
permission: "sys:settings:view",
},
},
{
title: "certd.sysResources.projectManager",
name: "ProjectManager",
path: "/sys/enterprise/project",
component: "/sys/enterprise/project/index.vue",
meta: {
show: true,
icon: "ion:apps",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "certd.sysResources.projectDetail",
name: "ProjectDetail",
path: "/sys/enterprise/project/detail",
component: "/sys/enterprise/project/detail/index.vue",
meta: {
isMenu: false,
show: true,
icon: "ion:apps",
permission: "sys:settings:edit",
},
},
{
title: "certd.sysResources.cnameSetting",
name: "CnameSetting",
@@ -211,6 +187,7 @@ export const sysResources = [
keepAlive: true,
},
},
{
title: "certd.sysResources.suiteManager",
name: "SuiteManager",
@@ -1,9 +0,0 @@
import { request } from "/src/api/service";
export async function MyProjectList() {
return await request({
url: "/enterprise/project/list",
method: "post",
data: {},
});
}
@@ -1,92 +0,0 @@
import { defineStore } from "pinia";
import * as api from "./api";
import { message } from "ant-design-vue";
import { computed, ref } from "vue";
import { useSettingStore } from "../settings";
import { LocalStorage } from "/@/utils/util.storage";
export type ProjectItem = {
id: string;
name: string;
permission?: string;
};
export const useProjectStore = defineStore("app.project", () => {
const myProjects = ref([]);
const lastProjectId = LocalStorage.get("currentProjectId");
const currentProjectId = ref(lastProjectId); // 直接调用
const projects = computed(() => {
return myProjects.value;
});
const currentProject = computed(() => {
if (currentProjectId.value) {
const project = projects.value.find(item => item.id === currentProjectId.value);
if (project) {
return project;
}
}
if (projects.value.length > 0) {
return projects.value[0];
}
return null;
});
const settingStore = useSettingStore();
const isEnterprise = computed(() => {
return settingStore.isEnterprise;
});
function getSearchForm() {
if (!currentProjectId.value || !isEnterprise.value) {
return {};
}
return {
projectId: currentProjectId.value,
};
}
async function loadMyProjects(): Promise<ProjectItem[]> {
if (!isEnterprise.value) {
return [];
}
const projects = await api.MyProjectList();
myProjects.value = projects;
if (projects.length > 0 && !currentProjectId.value) {
changeCurrentProject(projects[0].id, true);
}
}
function changeCurrentProject(id: string, silent?: boolean) {
currentProjectId.value = id;
LocalStorage.set("currentProjectId", id);
if (!silent) {
message.success("切换项目成功");
}
}
async function reload() {
const projects = await api.MyProjectList();
myProjects.value = projects;
}
async function init() {
if (!myProjects.value) {
await reload();
}
return myProjects.value;
}
return {
projects,
myProjects,
currentProject,
currentProjectId,
isEnterprise,
getSearchForm,
loadMyProjects,
changeCurrentProject,
reload,
init,
};
});
@@ -86,9 +86,6 @@ export type SysPublicSetting = {
>;
// 系统通知
notice?: string;
// 管理员模式
adminMode?: "enterprise" | "saas";
};
export type SuiteSetting = {
enabled?: boolean;
@@ -141,9 +141,6 @@ export const useSettingStore = defineStore({
isComm(): boolean {
return this.plusInfo?.isComm && (this.plusInfo?.expireTime === -1 || this.plusInfo?.expireTime > new Date().getTime());
},
isEnterprise(): boolean {
return this.isPlus && this.sysPublic.adminMode === "enterprise";
},
isAgent(): boolean {
return this.siteEnv?.agent?.enabled === true;
},
@@ -18,8 +18,8 @@
-webkit-border-radius: 4em;
-moz-border-radius: 4em;
border-radius: 4em;
background-color: #b3b3b3;
box-shadow: 0px 1px 1px #eee inset;
background-color: #757575;
}
::-webkit-scrollbar-thumb:hover {
@@ -111,7 +111,7 @@ function clearPreferencesAndLogout() {
<template v-for="slot in leftSlots.filter(item => item.index < REFERENCE_VALUE)" :key="slot.name">
<slot :name="slot.name">
<template v-if="slot.name === 'refresh'">
<VbenIconButton class="my-0 mr-1 rounded-md hidden md:block" @click="refresh">
<VbenIconButton class="my-0 mr-1 rounded-md" @click="refresh">
<RotateCw class="size-4" />
</VbenIconButton>
</template>
@@ -131,7 +131,7 @@ function clearPreferencesAndLogout() {
<template v-for="slot in rightSlots" :key="slot.name">
<slot :name="slot.name">
<template v-if="slot.name === 'global-search'">
<GlobalSearch :enable-shortcut-key="globalSearchShortcutKey" :menus="accessStore.accessMenus" class="mr-1 sm:mr-4 hidden md:block" />
<GlobalSearch :enable-shortcut-key="globalSearchShortcutKey" :menus="accessStore.accessMenus" class="mr-1 sm:mr-4" />
</template>
<template v-else-if="slot.name === 'preferences'">
@@ -144,7 +144,7 @@ function clearPreferencesAndLogout() {
<LanguageToggle class="mr-1" />
</template>
<template v-else-if="slot.name === 'fullscreen'">
<VbenFullScreen class="mr-1 hidden md:block" />
<VbenFullScreen class="mr-1" />
</template>
</slot>
</template>
@@ -210,11 +210,9 @@ const headerSlots = computed(() => {
</template>
<!-- 侧边额外区域 -->
<template #side-extra>
1111
<LayoutExtraMenu :accordion="preferences.navigation.accordion" :collapse="preferences.sidebar.extraCollapse" :menus="wrapperMenus(extraMenus)" :rounded="isMenuRounded" :theme="sidebarTheme" />
</template>
<template #side-extra-title>
234234234
<VbenLogo v-if="preferences.logo.enable" :text="preferences.app.name" :theme="theme" />
</template>
@@ -3,8 +3,6 @@ import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -46,7 +44,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
context.typeRef = typeRef;
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
commonColumnsDefine.type.form.component.disabled = true;
const projectStore = useProjectStore();
return {
typeRef,
crudOptions: {
@@ -61,9 +58,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
search: {
show: true,
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
wrapper: {
@@ -147,14 +141,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
...commonColumnsDefine,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -1,10 +1,8 @@
// @ts-ignore
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { ref } from "vue";
import { myProjectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { useI18n } from "/src/locales";
import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -31,7 +29,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const typeRef = ref();
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
const projectStore = useProjectStore();
return {
crudOptions: {
request: {
@@ -45,11 +42,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
confirmMessage: "授权如果已经被使用,可能会导致流水线无法正常运行,请谨慎操作",
},
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
rowHandle: {
width: 200,
buttons: {
@@ -123,14 +115,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
...commonColumnsDefine,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -1,34 +0,0 @@
import { dict } from "@fast-crud/fast-crud";
export const projectPermissionDict = dict({
data: [
{
label: "read",
value: "只读",
},
{
label: "write",
value: "读写",
},
{
label: "admin",
value: "管理员",
},
],
});
export const myProjectDict = dict({
url: "/enterprise/project/list",
value: "id",
label: "name",
});
export const userDict = dict({
url: "/sys/authority/user/getSimpleUsers",
value: "id",
onReady: ({ dict }) => {
for (const item of dict.data) {
item.label = item.nickName || item.username || item.phoneCode + item.mobile;
}
},
});
@@ -6,8 +6,6 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { myProjectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -34,8 +32,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const projectStore = useProjectStore();
return {
crudOptions: {
settings: {
@@ -68,9 +64,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
formItem: {
labelCol: {
style: {
@@ -202,13 +195,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
align: "center",
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
createTime: {
title: t("certd.fields.createTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t("certd.fields.updateTime"),
@@ -10,8 +10,6 @@ import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import { useSettingStore } from "/@/store/settings";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -38,7 +36,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const router = useRouter();
const settingStore = useSettingStore();
const projectStore = useProjectStore();
const model = useModal();
const viewCert = async (row: any) => {
const cert = await api.GetCert(row.id);
@@ -65,7 +63,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const expireStatus = route?.query?.expireStatus as string;
const searchInitForm = {
expiresLeft: expireStatus,
...projectStore.getSearchForm(),
};
return {
crudOptions: {
@@ -347,14 +344,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -13,8 +13,6 @@ import { useSiteImport } from "/@/views/certd/monitor/site/use";
import { ref } from "vue";
import GroupSelector from "../../basic/group/group-selector.vue";
import { createGroupDictRef } from "../../basic/group/api";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = siteInfoApi;
@@ -107,8 +105,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return searchFrom.groupId;
}
}
const projectStore = useProjectStore();
return {
id: "siteMonitorCrud",
crudOptions: {
@@ -293,11 +289,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
// name: "disabled",
// show: true,
// },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
columns: {
id: {
title: "ID",
@@ -556,20 +547,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
ipAddress: {
title: t("certd.monitor.ipAddress"),
search: {
show: false,
},
type: "text",
form: {
helper: t("certd.monitor.ipAddressHelper"),
},
column: {
width: 150,
sorter: true,
},
},
groupId: {
title: t("certd.fields.group"),
type: "dict-select",
@@ -823,14 +800,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -5,8 +5,6 @@ import { forEach, get, merge, set } from "lodash-es";
import { Modal } from "ant-design-vue";
import { mitter } from "/@/utils/util.mitt";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../dicts";
export function notificationProvide(api: any) {
provide("notificationApi", api);
@@ -28,8 +26,6 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
},
};
const projectStore = useProjectStore();
provide("getCurrentPluginDefine", () => {
return currentDefine;
});
@@ -251,13 +247,5 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
},
},
} as ColumnCompositionProps,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
};
}
@@ -2,7 +2,6 @@ import { ref } from "vue";
import { getCommonColumnDefine } from "./common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { createNotificationApi } from "/@/views/certd/notification/api";
import { useProjectStore } from "/@/store/project";
const api = createNotificationApi();
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@@ -27,7 +26,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const typeRef = ref();
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
const projectStore = useProjectStore();
return {
crudOptions: {
request: {
@@ -36,11 +34,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest,
delRequest,
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
labelCol: {
//固定label宽度
@@ -2,7 +2,6 @@
import { ref } from "vue";
import { getCommonColumnDefine } from "../../common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { crudBinding } = crudExpose;
@@ -30,7 +29,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res;
};
const projectStore = useProjectStore();
const selectedRowKey = ref([props.modelValue]);
const onSelectChange = (changed: any) => {
@@ -56,9 +54,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
search: {
show: false,
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
labelCol: {
@@ -3,8 +3,6 @@ import { useI18n } from "/src/locales";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { OPEN_API_DOC, openkeyApi } from "./api";
import { useModal } from "/@/use/use-modal";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -28,7 +26,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const res = await api.AddObj(form);
return res;
};
const projectStore = useProjectStore();
const model = useModal();
return {
crudOptions: {
@@ -40,9 +37,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
search: {
show: false,
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
labelCol: {
@@ -169,14 +163,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
sorter: true,
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
createTime: {
title: t("certd.fields.createTime"),
type: "datetime",
@@ -14,8 +14,6 @@ import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/src/locales";
import { myProjectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context: { selectedRowKeys, openCertApplyDialog } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -68,8 +66,6 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
const userStore = useUserStore();
const settingStore = useSettingStore();
const projectStore = useProjectStore();
const DEFAULT_WILL_EXPIRE_DAYS = settingStore.sysPublic.defaultWillExpireDays || settingStore.sysPublic.defaultCertRenewDays || 15;
function onDialogOpen(opt: any) {
@@ -151,9 +147,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
},
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
col: { span: 3 },
},
form: {
afterSubmit({ form, res, mode }) {
@@ -446,7 +440,6 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
type: "dict-select",
search: {
show: true,
col: { span: 2 },
},
dict: dict({
data: statusUtil.getOptions(),
@@ -551,7 +544,6 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
type: "dict-select",
search: {
show: true,
col: { span: 2 },
},
dict: dict({
data: [
@@ -649,14 +641,6 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
},
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
updateTime: {
title: t("certd.fields.updateTime"),
type: "datetime",
@@ -1,8 +1,6 @@
import { useI18n } from "/src/locales";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -27,8 +25,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res;
};
const projectStore = useProjectStore();
return {
crudOptions: {
settings: {
@@ -48,11 +44,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest,
delRequest,
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
labelCol: {
//固定label宽度
@@ -136,14 +127,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 400,
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -1,7 +1,7 @@
<template>
<fs-page v-if="pipeline" class="page-pipeline-edit">
<template #header>
<div class="title flex-0">
<div class="title flex-1">
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
</div>
@@ -6,8 +6,6 @@ import createCrudOptionsPipeline from "../crud";
import * as pipelineApi from "../api";
import { useTemplate } from "/@/views/certd/pipeline/template/use";
import { useI18n } from "/@/locales";
import { useProjectStore } from "/@/store/project";
import { myProjectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const api = templateApi;
const { t } = useI18n();
@@ -30,8 +28,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const res = await api.AddObj(form);
return res;
};
const projectStore = useProjectStore();
const { openCrudFormDialog } = useFormWrapper();
const router = useRouter();
@@ -52,11 +48,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
router.push({ path: "/certd/pipeline/template/edit", query: { templateId: res.id, editMode: "true" } });
},
},
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: {
labelCol: {
//固定label宽度
@@ -241,14 +232,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
@@ -1,75 +0,0 @@
import { request } from "/src/api/service";
const apiPrefix = "/enterprise/project";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/list",
method: "post",
data: query,
});
}
export async function GetPage(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj,
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj,
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id },
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids },
});
}
export async function SetDisabled(id: any, disabled: boolean) {
return await request({
url: apiPrefix + "/setDisabled",
method: "post",
data: { id, disabled },
});
}
@@ -1,165 +0,0 @@
import * as api from "./api";
import { useI18n } from "/src/locales";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings";
import { Modal } from "ant-design-vue";
import { userDict } from "../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
const list = await api.GetList(query);
return {
records: list,
total: list.length,
offset: 0,
limit: 999,
};
};
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) => {
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,
},
rowHandle: {
minWidth: 200,
fixed: "right",
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100,
},
form: {
show: false,
},
},
name: {
title: t("certd.ent.projectName"),
type: "link",
search: {
show: true,
},
form: {
component: {},
rules: [{ required: true, message: t("certd.requiredField") }],
},
column: {
width: 200,
cellRender({ row }) {
return <router-link to={{ path: `/certd/project/detail`, query: { projectId: row.id } }}>{row.name}</router-link>;
},
},
},
disabled: {
title: t("certd.disabled"),
type: "dict-switch",
dict: dict({
data: [
{ label: t("certd.enabled"), value: false, color: "success" },
{ label: t("certd.disabledLabel"), value: true, color: "error" },
],
}),
form: {
value: false,
},
column: {
width: 100,
component: {
title: t("certd.clickToToggle"),
on: {
async click({ value, row }) {
Modal.confirm({
title: t("certd.prompt"),
content: t("certd.confirmToggleStatus", { action: !value ? t("certd.disable") : t("certd.enable") }),
onOk: async () => {
await api.SetDisabled(row.id, !value);
await crudExpose.doRefresh();
},
});
},
},
},
},
},
adminId: {
title: t("certd.fields.adminId"),
type: "dict-select",
dict: userDict,
column: {
width: 160,
},
},
createTime: {
title: t("certd.createTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t("certd.updateTime"),
type: "datetime",
form: {
show: false,
},
column: {
show: true,
width: 160,
},
},
},
},
};
}
@@ -1,67 +0,0 @@
import { request } from "/src/api/service";
const apiPrefix = "/enterprise/myProjectMember";
const userApiPrefix = "/sys/authority/user";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj,
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj,
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id },
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids },
});
}
export async function GetUserSimpleByIds(query: any) {
return await request({
url: userApiPrefix + "/getSimpleByIds",
method: "post",
data: query,
});
}
@@ -1,162 +0,0 @@
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { Modal } from "ant-design-vue";
import { Ref, ref } from "vue";
import { useRouter } from "vue-router";
import * as api from "./api";
import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { userDict } from "../../dicts";
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) => {
const res = await api.AddObj(form);
return res;
};
const projectId = context.projectId;
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,
},
rowHandle: {
minWidth: 200,
fixed: "right",
},
search: {
initialForm: {
projectId,
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100,
},
form: {
show: false,
},
},
projectId: {
title: "项目ID",
type: "text",
search: {
show: false,
},
form: {
value: projectId,
show: false,
},
editForm: {
show: false,
},
column: {
width: 200,
show: false,
},
},
userId: {
title: "用户",
type: "dict-select",
dict: userDict,
search: {
show: true,
},
form: {},
editForm: {
show: false,
},
column: {
width: 200,
},
},
permission: {
title: t("certd.ent.projectPermission"),
type: "dict-select",
dict: dict({
data: [
{ label: t("certd.ent.permission.read"), value: "read", color: "cyan" },
{ label: t("certd.ent.permission.write"), value: "write", color: "blue" },
{ label: t("certd.ent.permission.admin"), value: "admin", color: "green" },
],
}),
search: {
show: true,
},
form: {
show: true,
},
column: {
width: 200,
},
},
createTime: {
title: t("certd.createTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t("certd.updateTime"),
type: "datetime",
form: {
show: false,
},
column: {
show: true,
width: 160,
},
},
},
},
};
}
@@ -1,71 +0,0 @@
<template>
<fs-page class="page-project-detail">
<template #header>
<div class="title">
{{ t("certd.ent.projectDetailManager") }}
<span class="sub">
{{ t("certd.ent.projectDetailDescription") }}
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip :title="t('certd.batchDelete')">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useRoute } from "vue-router";
const { t } = useI18n();
defineOptions({
name: "ProjectDetail",
});
const route = useRoute();
const projectIdStr = route.query.projectId as string;
const projectId = Number(projectIdStr);
const context: any = {
projectId,
};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: t("certd.confirmTitle"),
content: t("certd.confirmDeleteBatch", { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info(t("certd.deleteSuccess"));
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error(t("certd.selectRecordsFirst"));
}
};
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -1,61 +0,0 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">
{{ t("certd.sysResources.myProjectManager") }}
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<!-- <div v-if="crudBinding.data" class="project-list">
<div v-for="item of crudBinding.data" :key="item.id" class="project-item">
<a-card style="width: 300px">
<p>{{ item.name }}</p>
</a-card>
</div>
</div> -->
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
name: "MyProjectManager",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: t("certd.confirmTitle"),
content: t("certd.confirmDeleteBatch", { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info(t("certd.deleteSuccess"));
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error(t("certd.selectRecordsFirst"));
}
};
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -74,6 +74,13 @@ function goDetail(link: any) {
}
</script>
<style lang="less">
.dark {
.data-item {
.header {
color: rgba(242, 242, 242, 0.85) !important;
}
}
}
.statistic-card {
margin-bottom: 10px;
.icon-text {
@@ -311,6 +311,7 @@ export default defineComponent({
.input-right {
width: 160px;
margin-left: 10px;
background: #cfcfcf !important;
}
.forge-password {
@@ -1,75 +0,0 @@
import { request } from "/src/api/service";
const apiPrefix = "/sys/project/provider";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj,
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj,
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id },
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids },
});
}
export async function SetDefault(id: any) {
return await request({
url: apiPrefix + "/setDefault",
method: "post",
data: { id },
});
}
export async function SetDisabled(id: any, disabled: boolean) {
return await request({
url: apiPrefix + "/setDisabled",
method: "post",
data: { id, disabled },
});
}
@@ -1,254 +0,0 @@
import * as api from "./api";
import { useI18n } from "/src/locales";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings";
import { Modal } from "ant-design-vue";
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) => {
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,
},
rowHandle: {
minWidth: 200,
fixed: "right",
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100,
},
form: {
show: false,
},
},
domain: {
title: t("certd.cnameDomain"),
type: "text",
editForm: {
component: {
disabled: true,
},
},
search: {
show: true,
},
form: {
component: {
placeholder: t("certd.cnameDomainPlaceholder"),
},
helper: t("certd.cnameDomainHelper"),
rules: [
{ required: true, message: t("certd.requiredField") },
{ pattern: /^[^*]+$/, message: t("certd.cnameDomainPattern") },
],
},
column: {
width: 200,
},
},
dnsProviderType: {
title: t("certd.dnsProvider"),
type: "dict-select",
search: {
show: true,
},
dict: dict({
url: "pi/dnsProvider/list",
value: "key",
label: "title",
}),
form: {
rules: [{ required: true, message: t("certd.requiredField") }],
},
column: {
width: 150,
component: {
color: "auto",
},
},
},
accessId: {
title: t("certd.dnsProviderAuthorization"),
type: "dict-select",
dict: dict({
url: "/pi/access/list",
value: "id",
label: "name",
}),
form: {
component: {
name: "access-selector",
vModel: "modelValue",
type: compute(({ form }) => {
return form.dnsProviderType;
}),
},
rules: [{ required: true, message: t("certd.requiredField") }],
},
column: {
width: 150,
component: {
color: "auto",
},
},
},
isDefault: {
title: t("certd.isDefault"),
type: "dict-switch",
dict: dict({
data: [
{ label: t("certd.yes"), value: true, color: "success" },
{ label: t("certd.no"), value: false, color: "default" },
],
}),
form: {
value: false,
rules: [{ required: true, message: t("certd.selectIsDefault") }],
},
column: {
align: "center",
width: 100,
},
},
setDefault: {
title: t("certd.setDefault"),
type: "text",
form: {
show: false,
},
column: {
width: 100,
align: "center",
conditionalRenderDisabled: true,
cellRender: ({ row }) => {
if (row.isDefault) {
return;
}
const onClick = async () => {
Modal.confirm({
title: t("certd.prompt"),
content: t("certd.confirmSetDefault"),
onOk: async () => {
await api.SetDefault(row.id);
await crudExpose.doRefresh();
},
});
};
return (
<a-button type={"link"} size={"small"} onClick={onClick}>
{t("certd.setAsDefault")}
</a-button>
);
},
},
},
disabled: {
title: t("certd.disabled"),
type: "dict-switch",
dict: dict({
data: [
{ label: t("certd.enabled"), value: false, color: "success" },
{ label: t("certd.disabledLabel"), value: true, color: "error" },
],
}),
form: {
value: false,
},
column: {
width: 100,
component: {
title: t("certd.clickToToggle"),
on: {
async click({ value, row }) {
Modal.confirm({
title: t("certd.prompt"),
content: t("certd.confirmToggleStatus", { action: !value ? t("certd.disable") : t("certd.enable") }),
onOk: async () => {
await api.SetDisabled(row.id, !value);
await crudExpose.doRefresh();
},
});
},
},
},
},
},
createTime: {
title: t("certd.createTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t("certd.updateTime"),
type: "datetime",
form: {
show: false,
},
column: {
show: true,
width: 160,
},
},
},
},
};
}
@@ -1,65 +0,0 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">
{{ t("certd.cnameTitle") }}
<span class="sub">
{{ t("certd.cnameDescription") }}
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">
{{ t("certd.cnameLinkText") }}
</a>
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip :title="t('certd.batchDelete')">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
name: "CnameProvider",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: t("certd.confirmTitle"),
content: t("certd.confirmDeleteBatch", { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info(t("certd.deleteSuccess"));
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error(t("certd.selectRecordsFirst"));
}
};
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -1,11 +0,0 @@
import { dict } from "@fast-crud/fast-crud";
export const userDict = dict({
url: "/sys/authority/user/getSimpleUsers",
value: "id",
onReady: ({ dict }) => {
for (const item of dict.data) {
item.label = item.nickName || item.username || item.phoneCode + item.mobile;
}
},
});
@@ -1,68 +0,0 @@
import { request } from "/src/api/service";
const apiPrefix = "/sys/enterprise/project";
const userApiPrefix = "/sys/authority/user";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj,
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj,
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id },
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids },
});
}
export async function SetDisabled(id: any, disabled: boolean) {
return await request({
url: apiPrefix + "/setDisabled",
method: "post",
data: { id, disabled },
});
}

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