mirror of
https://github.com/certd/certd.git
synced 2026-04-23 19:57:27 +08:00
Merge branch 'v2-dev' into v2_admin_mode
This commit is contained in:
@@ -35,9 +35,9 @@ export class HandleController extends BaseController {
|
||||
@Post('/access', { summary: Constants.per.authOnly })
|
||||
async accessRequest(@Body(ALL) body: AccessRequestHandleReq) {
|
||||
const {projectId,userId} = await this.getProjectUserIdRead()
|
||||
let inputAccess = body.input.access;
|
||||
if (body.input.id > 0) {
|
||||
const oldEntity = await this.accessService.info(body.input.id);
|
||||
let inputAccess = body.input;
|
||||
if (body.record.id > 0) {
|
||||
const oldEntity = await this.accessService.info(body.record.id);
|
||||
if (oldEntity) {
|
||||
if (oldEntity.userId !== this.getUserId()) {
|
||||
throw new Error('access not found');
|
||||
@@ -47,7 +47,7 @@ export class HandleController extends BaseController {
|
||||
}
|
||||
const param: any = {
|
||||
type: body.typeName,
|
||||
setting: JSON.stringify(body.input.access),
|
||||
setting: JSON.stringify(body.input),
|
||||
};
|
||||
this.accessService.encryptSetting(param, oldEntity);
|
||||
inputAccess = this.accessService.decryptAccessEntity(param);
|
||||
@@ -56,7 +56,7 @@ export class HandleController extends BaseController {
|
||||
const accessGetter = new AccessGetter(userId,projectId, this.accessService.getById.bind(this.accessService));
|
||||
const access = await newAccess(body.typeName, inputAccess,accessGetter);
|
||||
|
||||
mergeUtils.merge(access, body.input);
|
||||
// mergeUtils.merge(access, body.input);
|
||||
const res = await access.onRequest(body);
|
||||
|
||||
return this.ok(res);
|
||||
@@ -64,7 +64,7 @@ export class HandleController extends BaseController {
|
||||
|
||||
@Post('/notification', { summary: Constants.per.authOnly })
|
||||
async notificationRequest(@Body(ALL) body: NotificationRequestHandleReq) {
|
||||
const input = body.input.body;
|
||||
const input = body.input;
|
||||
|
||||
const notification = await newNotification(body.typeName, input, {
|
||||
http,
|
||||
|
||||
@@ -58,6 +58,8 @@ export class PipelineEntity {
|
||||
// 变量
|
||||
lastVars: any;
|
||||
|
||||
nextRunTime: number;
|
||||
|
||||
@Column({name: 'order', comment: '排序', nullable: true,})
|
||||
order: number;
|
||||
|
||||
@@ -72,4 +74,5 @@ export class PipelineEntity {
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import { TaskServiceBuilder } from "./getter/task-service-getter.js";
|
||||
import { nanoid } from "nanoid";
|
||||
import { set } from "lodash-es";
|
||||
import { executorQueue } from "@certd/lib-server";
|
||||
|
||||
import parser from "cron-parser";
|
||||
const runningTasks: Map<string | number, Executor> = new Map();
|
||||
|
||||
|
||||
@@ -142,16 +142,47 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
// @ts-ignore
|
||||
item.stepCount = stepCount;
|
||||
if(item.triggerCount == 0 ){
|
||||
if (item.triggerCount == 0) {
|
||||
item.triggerCount = pipeline.triggers?.length;
|
||||
}
|
||||
|
||||
|
||||
//获取下次执行时间
|
||||
if (pipeline.triggers?.length > 0) {
|
||||
const triggers = pipeline.triggers.filter((item) => item.type === 'timer');
|
||||
if (triggers && triggers.length > 0) {
|
||||
let nextTimes: any = [];
|
||||
for (const item of triggers) {
|
||||
if (!item.props?.cron) {
|
||||
continue;
|
||||
}
|
||||
const ret = this.getCronNextTimes(item.props?.cron, 1);
|
||||
nextTimes.push(...ret);
|
||||
}
|
||||
item.nextRunTime = nextTimes[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete item.content;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getCronNextTimes(cron: string, count: number = 1) {
|
||||
if (cron == null) {
|
||||
return [];
|
||||
}
|
||||
const nextTimes = [];
|
||||
const interval = parser.parseExpression(cron);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const next = interval.next().getTime();
|
||||
nextTimes.push(dayjs(next).format("YYYY-MM-DD HH:mm:ss"));
|
||||
}
|
||||
return nextTimes;
|
||||
}
|
||||
|
||||
|
||||
private async fillLastVars(records: PipelineEntity[]) {
|
||||
const pipelineIds: number[] = [];
|
||||
const recordMap = {};
|
||||
@@ -221,7 +252,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
//修改
|
||||
old = await this.info(bean.id);
|
||||
}
|
||||
if (!old || !old.webhookKey ) {
|
||||
if (!old || !old.webhookKey) {
|
||||
bean.webhookKey = await this.genWebhookKey();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
|
||||
import { Dns51Client } from './client.js';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -27,14 +28,38 @@ export class Dns51Access extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: '登录密码',
|
||||
component: {
|
||||
name:"a-input-password",
|
||||
vModel:"value",
|
||||
name: "a-input-password",
|
||||
vModel: "value",
|
||||
placeholder: '密码',
|
||||
},
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
password = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
|
||||
const client = new Dns51Client({
|
||||
logger: this.ctx.logger,
|
||||
access: this,
|
||||
});
|
||||
|
||||
await client.login();
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
new Dns51Access();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './plugin-restart.js';
|
||||
export * from './plugin-script.js';
|
||||
export * from './plugin-db-backup.js';
|
||||
export * from './plugin-deploy-to-certd.js';
|
||||
|
||||
@@ -34,8 +34,8 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||
name: "a-select",
|
||||
options: [
|
||||
{ label: "本地复制", value: "local" },
|
||||
{ label: "ssh上传", value: "ssh" },
|
||||
{ label: "oss上传", value: "oss" },
|
||||
{ label: "oss上传(推荐)", value: "oss" },
|
||||
{ label: "ssh上传(请使用oss上传方式)", value: "ssh", disabled: true },
|
||||
],
|
||||
placeholder: "",
|
||||
},
|
||||
@@ -72,6 +72,7 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||
{ value: "tencentcos", label: "腾讯云COS" },
|
||||
{ value: "ftp", label: "Ftp" },
|
||||
{ value: "sftp", label: "Sftp" },
|
||||
{ value: "scp", label: "SCP" },
|
||||
],
|
||||
},
|
||||
mergeScript: `
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { httpsServer } from '../../modules/auto/https/server.js';
|
||||
import { RestartCertdPlugin } from './plugin-restart.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { CertApplyPluginNames, CertInfo, CertReader } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployToCertd',
|
||||
title: '部署证书到Certd本身',
|
||||
icon: 'mdi:restart',
|
||||
desc: '【仅管理员可用】 部署证书到 certd的https服务,用于更新 Certd 的 ssl 证书,建议将此任务放在流水线的最后一步',
|
||||
group: pluginGroups.admin.key,
|
||||
onlyAdmin: true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class DeployToCertdPlugin extends AbstractTaskPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
async onInstance() { }
|
||||
async execute(): Promise<void> {
|
||||
if (!this.isAdmin()) {
|
||||
throw new Error('只有管理员才能运行此任务');
|
||||
}
|
||||
|
||||
//部署证书
|
||||
let crtPath = "ssl/cert.crt";
|
||||
let keyPath = "ssl/cert.key";
|
||||
|
||||
const certReader = new CertReader(this.cert);
|
||||
const dataDir = "./data";
|
||||
const handle = async ({ tmpCrtPath, tmpKeyPath, }) => {
|
||||
this.logger.info('复制到目标路径');
|
||||
if (crtPath) {
|
||||
crtPath = crtPath.startsWith('/') ? crtPath : path.join(dataDir, crtPath);
|
||||
this.copyFile(tmpCrtPath, crtPath);
|
||||
}
|
||||
if (keyPath) {
|
||||
keyPath = keyPath.trim();
|
||||
keyPath = keyPath.startsWith('/') ? keyPath : path.join(dataDir, keyPath);
|
||||
this.copyFile(tmpKeyPath, keyPath);
|
||||
}
|
||||
};
|
||||
|
||||
await certReader.readCertFile({ logger: this.logger, handle });
|
||||
this.logger.info(`证书已部署到 ${crtPath} 和 ${keyPath}`);
|
||||
|
||||
|
||||
this.logger.info('Certd https server 将在 30 秒后重启');
|
||||
await this.ctx.utils.sleep(30000);
|
||||
await httpsServer.restart();
|
||||
}
|
||||
|
||||
copyFile(srcFile: string, destFile: string) {
|
||||
this.logger.info(`复制文件:${srcFile} => ${destFile}`);
|
||||
const dir = path.dirname(destFile);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
}
|
||||
}
|
||||
new RestartCertdPlugin();
|
||||
+5
-38
@@ -1,6 +1,6 @@
|
||||
import { IAccessService, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { AliesaAccess, AliyunAccess } from '../../plugin-lib/aliyun/index.js';
|
||||
import { AliesaAccess } from '../../plugin-lib/aliyun/index.js';
|
||||
import { AliyunClientV2 } from '../../plugin-lib/aliyun/lib/aliyun-client-v2.js';
|
||||
|
||||
|
||||
@@ -17,11 +17,8 @@ export class AliesaDnsProvider extends AbstractDnsProvider {
|
||||
|
||||
client: AliyunClientV2
|
||||
async onInstance() {
|
||||
const access: AliesaAccess = this.ctx.access as AliesaAccess
|
||||
const accessGetter = await this.ctx.serviceGetter.get("accessService") as IAccessService
|
||||
const aliAccess = await accessGetter.getById(access.accessId) as AliyunAccess
|
||||
const endpoint = `esa.${access.region}.aliyuncs.com`
|
||||
this.client = aliAccess.getClient(endpoint)
|
||||
const access : AliesaAccess = this.ctx.access as AliesaAccess
|
||||
this.client = await access.getEsaClient()
|
||||
}
|
||||
|
||||
|
||||
@@ -103,37 +100,7 @@ export class AliesaDnsProvider extends AbstractDnsProvider {
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req)
|
||||
const ret = await this.client.doRequest({
|
||||
// 接口名称
|
||||
action: "ListSites",
|
||||
// 接口版本
|
||||
version: "2024-09-10",
|
||||
// 接口协议
|
||||
protocol: "HTTPS",
|
||||
// 接口 HTTP 方法
|
||||
method: "GET",
|
||||
authType: "AK",
|
||||
style: "RPC",
|
||||
data: {
|
||||
query: {
|
||||
SiteName: req.searchKey,
|
||||
// ["SiteSearchType"] = "exact";
|
||||
SiteSearchType: "fuzzy",
|
||||
AccessType: "NS",
|
||||
PageSize: pager.pageSize,
|
||||
PageNumber: pager.pageNo,
|
||||
}
|
||||
}
|
||||
})
|
||||
const list = ret.Sites?.map(item => ({
|
||||
domain: item.SiteName,
|
||||
id: item.SiteId,
|
||||
}))
|
||||
return {
|
||||
list: list || [],
|
||||
total: ret.TotalCount,
|
||||
}
|
||||
return await this.ctx.access.getDomainListPage(req)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,8 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
|
||||
certId = certIdRes.certId as any;
|
||||
}else if (casCert.certId){
|
||||
certId = casCert.certId;
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,11 +141,13 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
|
||||
const casCert = this.cert as CasCertId;
|
||||
if (casCert.certId) {
|
||||
certId = casCert.certId;
|
||||
} else {
|
||||
} else if (certInfo.crt) {
|
||||
certId = await sslClient.uploadCert({
|
||||
name: this.buildCertName(CertReader.getMainDomain(certInfo.crt)),
|
||||
cert: certInfo,
|
||||
});
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,13 +117,15 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
const casCert = this.cert as CasCertId;
|
||||
if (casCert.certId) {
|
||||
certId = casCert.certId;
|
||||
} else {
|
||||
} else if (certInfo.crt) {
|
||||
certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt))
|
||||
const certIdRes = await sslClient.uploadCertificate({
|
||||
name:certName,
|
||||
cert: certInfo,
|
||||
});
|
||||
certId = certIdRes.certId as any;
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
let certId: any = this.cert
|
||||
if (typeof this.cert === 'object') {
|
||||
const certInfo = this.cert as CertInfo;
|
||||
const casCertId = this.cert as CasCertId;
|
||||
if (certInfo.crt) {
|
||||
this.logger.info('上传证书:', CertName);
|
||||
const cert: any = this.cert;
|
||||
@@ -117,6 +118,10 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
SSLPub: cert.crt,
|
||||
SSLPri: cert.key,
|
||||
};
|
||||
}else if (casCertId.certId){
|
||||
certId = casCertId.certId;
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
this.logger.info('使用已上传的证书:', certId);
|
||||
|
||||
@@ -122,7 +122,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
if (casCert.certId) {
|
||||
certId = casCert.certId;
|
||||
certName = casCert.certName;
|
||||
} else {
|
||||
} else if (certInfo.crt) {
|
||||
certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt));
|
||||
|
||||
const certIdRes = await sslClient.uploadCertificate({
|
||||
@@ -131,6 +131,8 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
});
|
||||
certId = certIdRes.certId as any;
|
||||
this.logger.info("上传证书成功", certId, certName);
|
||||
}else{
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -175,7 +175,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
|
||||
<PrivateKey>${certInfo.key}</PrivateKey>
|
||||
<Certificate>${certInfo.crt}</Certificate>
|
||||
`
|
||||
}else{
|
||||
}else {
|
||||
const casCert = this.cert as CasCertId;
|
||||
certStr = `<CertId>${casCert.certIdentifier}</CertId>`
|
||||
}
|
||||
|
||||
@@ -153,15 +153,17 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
|
||||
});
|
||||
|
||||
const cert = this.cert as CertInfo;
|
||||
const casCert = this.cert as CasCertInfo;
|
||||
if (cert.crt) {
|
||||
const certIdRes = await sslClient.uploadCertificate({
|
||||
name: this.buildCertName(CertReader.getMainDomain(cert.crt)),
|
||||
cert: cert,
|
||||
});
|
||||
certId = certIdRes.certId as any;
|
||||
}else {
|
||||
const casCert = this.cert as CasCertInfo;
|
||||
} else if (casCert.certId) {
|
||||
certId = casCert.certId;
|
||||
} else {
|
||||
throw new Error('证书格式错误'+JSON.stringify(this.cert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
|
||||
let certName = ""
|
||||
if (this.name){
|
||||
certName = this.appendTimeSuffix(this.name)
|
||||
}else{
|
||||
}else {
|
||||
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
|
||||
}
|
||||
const certIdRes = await client.uploadCertificate({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||
import { AwsRegions } from './constants.js';
|
||||
import { AwsClient } from './libs/aws-client.js';
|
||||
|
||||
@IsAccess({
|
||||
name: 'aws',
|
||||
@@ -33,7 +34,7 @@ export class AwsAccess extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: 'region',
|
||||
component: {
|
||||
name:"a-select",
|
||||
name: "a-select",
|
||||
options: AwsRegions,
|
||||
},
|
||||
required: true,
|
||||
@@ -41,6 +42,25 @@ export class AwsAccess extends BaseAccess {
|
||||
options: AwsRegions,
|
||||
})
|
||||
region = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
|
||||
const client = new AwsClient({ access: this, logger: this.ctx.logger, region: this.region || 'us-east-1' });
|
||||
await client.getCallerIdentity();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new AwsAccess();
|
||||
|
||||
@@ -45,6 +45,26 @@ export class AwsClient {
|
||||
}
|
||||
|
||||
|
||||
async getCallerIdentity() {
|
||||
const { STSClient, GetCallerIdentityCommand } = await import ("@aws-sdk/client-sts");
|
||||
|
||||
const client = new STSClient({
|
||||
region: this.access.region || 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
|
||||
secretAccessKey: this.access.secretAccessKey,
|
||||
},
|
||||
});
|
||||
|
||||
const command = new GetCallerIdentityCommand({});
|
||||
const response = await client.send(command);
|
||||
this.logger.info(` 账户ID: ${response.Account}`);
|
||||
this.logger.info(` ARN: ${response.Arn}`);
|
||||
this.logger.info(` 用户ID: ${response.UserId}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
async route53ClientGet() {
|
||||
const { Route53Client } = await import('@aws-sdk/client-route-53');
|
||||
return new Route53Client({
|
||||
@@ -85,7 +105,7 @@ export class AwsClient {
|
||||
const { ListHostedZonesByNameCommand } = await import("@aws-sdk/client-route-53"); // ES Modules import
|
||||
|
||||
const client = await this.route53ClientGet();
|
||||
const input:any = { // ListHostedZonesByNameRequest
|
||||
const input: any = { // ListHostedZonesByNameRequest
|
||||
MaxItems: req.pageSize,
|
||||
};
|
||||
if (req.searchKey) {
|
||||
@@ -93,7 +113,7 @@ export class AwsClient {
|
||||
}
|
||||
const command = new ListHostedZonesByNameCommand(input);
|
||||
const response = await this.doRequest(() => client.send(command));
|
||||
let list :any[]= response.HostedZones || [];
|
||||
let list: any[] = response.HostedZones || [];
|
||||
list = list.map((item: any) => ({
|
||||
id: item.Id.replace('/hostedzone/', ''),
|
||||
domain: item.Name,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||
icon: 'clarity:plugin-line',
|
||||
})
|
||||
export class CacheflyAccess extends BaseAccess {
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: 'username',
|
||||
component: {
|
||||
@@ -32,6 +35,62 @@ export class CacheflyAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
otpkey = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.login();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async login(){
|
||||
let otp = null;
|
||||
if (this.otpkey) {
|
||||
const response = await this.ctx.http.request<any, any>({
|
||||
url: `https://cn-api.my-api.cn/api/totp/?key=${this.otpkey}`,
|
||||
method: 'get',
|
||||
});
|
||||
otp = response;
|
||||
this.ctx.logger.info('获取到otp:', otp);
|
||||
}
|
||||
const loginResponse = await this.doRequestApi(`/api/2.6/auth/login`, {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
...(otp && { otp }),
|
||||
});
|
||||
const token = loginResponse.token;
|
||||
this.ctx.logger.info('Token 获取成功');
|
||||
return token;
|
||||
}
|
||||
|
||||
async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
|
||||
|
||||
const baseApi = 'https://api.cachefly.com';
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'x-cf-authorization': `Bearer ${token}` } : {}),
|
||||
};
|
||||
const res = await this.ctx.http.request<any, any>({
|
||||
url,
|
||||
baseURL: baseApi,
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
new CacheflyAccess();
|
||||
|
||||
+7
-33
@@ -35,47 +35,21 @@ export class CacheFlyPlugin extends AbstractTaskPlugin {
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
private readonly baseApi = 'https://api.cachefly.com';
|
||||
|
||||
|
||||
async onInstance() {}
|
||||
|
||||
private async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'x-cf-authorization': `Bearer ${token}` } : {}),
|
||||
};
|
||||
const res = await this.http.request<any, any>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async execute(): Promise<void> {
|
||||
const { cert, accessId } = this;
|
||||
const access = (await this.getAccess(accessId)) as CacheflyAccess;
|
||||
let otp = null;
|
||||
if (access.otpkey) {
|
||||
const response = await this.http.request<any, any>({
|
||||
url: `https://cn-api.my-api.cn/api/totp/?key=${access.otpkey}`,
|
||||
method: 'get',
|
||||
});
|
||||
otp = response;
|
||||
this.logger.info('获取到otp:', otp);
|
||||
}
|
||||
const loginResponse = await this.doRequestApi(`${this.baseApi}/api/2.6/auth/login`, {
|
||||
username: access.username,
|
||||
password: access.password,
|
||||
...(otp && { otp }),
|
||||
});
|
||||
const token = loginResponse.token;
|
||||
this.logger.info('Token 获取成功');
|
||||
|
||||
const token = await access.login();
|
||||
// 更新证书
|
||||
await this.doRequestApi(
|
||||
`${this.baseApi}/api/2.6/certificates`,
|
||||
await access.doRequestApi(
|
||||
`/api/2.6/certificates`,
|
||||
{
|
||||
certificate: cert.crt,
|
||||
certificateKey: cert.key,
|
||||
|
||||
@@ -37,6 +37,58 @@ export class CloudflareAccess extends BaseAccess {
|
||||
encrypt: false,
|
||||
})
|
||||
proxy = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getZoneList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async getZoneList() {
|
||||
const url = `https://api.cloudflare.com/client/v4/zones`;
|
||||
const res = await this.doRequestApi(url, null, 'get');
|
||||
return res.result
|
||||
}
|
||||
|
||||
async doRequestApi(url: string, data: any = null, method = 'post') {
|
||||
try {
|
||||
const res = await this.ctx.http.request<any, any>({
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.apiToken}`,
|
||||
},
|
||||
data,
|
||||
httpProxy: this.proxy,
|
||||
});
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(`${JSON.stringify(res.errors)}`);
|
||||
}
|
||||
return res;
|
||||
} catch (e: any) {
|
||||
const data = e.response?.data;
|
||||
if (data && data.success === false && data.errors && data.errors.length > 0) {
|
||||
if (data.errors[0].code === 81058) {
|
||||
this.ctx.logger.info('dns解析记录重复');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new CloudflareAccess();
|
||||
|
||||
@@ -41,41 +41,14 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
|
||||
async getZoneId(domain: string) {
|
||||
this.logger.info('获取zoneId:', domain);
|
||||
const url = `https://api.cloudflare.com/client/v4/zones?name=${domain}`;
|
||||
const res = await this.doRequestApi(url, null, 'get');
|
||||
const res = await this.access.doRequestApi(url, null, 'get');
|
||||
if (res.result.length === 0) {
|
||||
throw new Error(`未找到域名${domain}的zoneId`);
|
||||
}
|
||||
return res.result[0].id;
|
||||
}
|
||||
|
||||
private async doRequestApi(url: string, data: any = null, method = 'post') {
|
||||
try {
|
||||
const res = await this.http.request<any, any>({
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.access.apiToken}`,
|
||||
},
|
||||
data,
|
||||
httpProxy: this.access.proxy,
|
||||
});
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(`${JSON.stringify(res.errors)}`);
|
||||
}
|
||||
return res;
|
||||
} catch (e: any) {
|
||||
const data = e.response?.data;
|
||||
if (data && data.success === false && data.errors && data.errors.length > 0) {
|
||||
if (data.errors[0].code === 81058) {
|
||||
this.logger.info('dns解析记录重复');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建dns解析记录,用于验证域名所有权
|
||||
@@ -95,7 +68,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
|
||||
|
||||
// 给domain下创建txt类型的dns解析记录,fullRecord
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;
|
||||
const res = await this.doRequestApi(url, {
|
||||
const res = await this.access.doRequestApi(url, {
|
||||
content: value,
|
||||
name: fullRecord,
|
||||
type: type,
|
||||
@@ -119,7 +92,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
|
||||
async findRecord(zoneId: string, options: CreateRecordOptions): Promise<CloudflareRecord | null> {
|
||||
const { fullRecord, value } = options;
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?type=TXT&name=${fullRecord}&content=${value}`;
|
||||
const res = await this.doRequestApi(url, null, 'get');
|
||||
const res = await this.access.doRequestApi(url, null, 'get');
|
||||
if (res.result.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -142,7 +115,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
|
||||
const zoneId = record.zone_id;
|
||||
const recordId = record.id;
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}`;
|
||||
await this.doRequestApi(url, null, 'delete');
|
||||
await this.access.doRequestApi(url, null, 'delete');
|
||||
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`);
|
||||
}
|
||||
|
||||
@@ -153,7 +126,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
|
||||
if (req.searchKey) {
|
||||
url += `&name=${req.searchKey}`;
|
||||
}
|
||||
const ret = await this.doRequestApi(url, null, 'get');
|
||||
const ret = await this.access.doRequestApi(url, null, 'get');
|
||||
|
||||
let list = ret.result || []
|
||||
list = list.map((item: any) => ({
|
||||
|
||||
@@ -128,9 +128,10 @@ export class DemoTest extends AbstractTaskPlugin {
|
||||
//当以下参数变化时,触发获取选项
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
multi: true,
|
||||
})
|
||||
)
|
||||
siteName!: string | string[];
|
||||
siteName!: string[];
|
||||
|
||||
//插件实例化时执行的方法
|
||||
async onInstance() {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
|
||||
import { IsAccess, AccessInput, BaseAccess, PageSearch, PageRes, Pager } from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -19,7 +20,7 @@ export class DnslaAccess extends BaseAccess {
|
||||
component: {
|
||||
placeholder: 'APIID',
|
||||
},
|
||||
helper:"从我的账户->API密钥中获取 APIID APISecret",
|
||||
helper: "从我的账户->API密钥中获取 APIID APISecret",
|
||||
required: true,
|
||||
encrypt: false,
|
||||
})
|
||||
@@ -36,6 +37,83 @@ export class DnslaAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
apiSecret = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainListPage({
|
||||
pageNo: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const url = `/api/domainList?pageIndex=${pager.pageNo}&pageSize=${pager.pageSize}`;
|
||||
const ret = await this.doRequestApi(url, null, 'get');
|
||||
|
||||
let list = ret.data.results || []
|
||||
list = list.map((item: any) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
}));
|
||||
const total = ret.data.total || list.length;
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async doRequestApi(url: string, data: any = null, method = 'post') {
|
||||
/**
|
||||
* Basic 认证
|
||||
* 我的账户 API 密钥 中获取 APIID APISecret
|
||||
* APIID=myApiId
|
||||
* APISecret=mySecret
|
||||
* 生成 Basic 令牌
|
||||
* # 用冒号连接 APIID APISecret
|
||||
* str = "myApiId:mySecret"
|
||||
* token = base64Encode(str)
|
||||
* 在请求头中添加 Basic 认证令牌
|
||||
* Authorization: Basic {token}
|
||||
* 响应示例
|
||||
* application/json
|
||||
* {
|
||||
* "code":200,
|
||||
* "msg":"",
|
||||
* "data":{}
|
||||
* }
|
||||
*/
|
||||
const token = Buffer.from(`${this.apiId}:${this.apiSecret}`).toString('base64');
|
||||
const res = await this.ctx.http.request<any, any>({
|
||||
url: "https://api.dns.la" + url,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Basic ${token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
if (res.code !== 200) {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new DnslaAccess();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
|
||||
import { PageRes, PageSearch } from "@certd/pipeline";
|
||||
import { DnslaAccess } from "./access.js";
|
||||
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
|
||||
|
||||
export type DnslaRecord = {
|
||||
id: string;
|
||||
@@ -25,43 +25,6 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
|
||||
}
|
||||
|
||||
|
||||
private async doRequestApi(url: string, data: any = null, method = 'post') {
|
||||
/**
|
||||
* Basic 认证
|
||||
* 我的账户 API 密钥 中获取 APIID APISecret
|
||||
* APIID=myApiId
|
||||
* APISecret=mySecret
|
||||
* 生成 Basic 令牌
|
||||
* # 用冒号连接 APIID APISecret
|
||||
* str = "myApiId:mySecret"
|
||||
* token = base64Encode(str)
|
||||
* 在请求头中添加 Basic 认证令牌
|
||||
* Authorization: Basic {token}
|
||||
* 响应示例
|
||||
* application/json
|
||||
* {
|
||||
* "code":200,
|
||||
* "msg":"",
|
||||
* "data":{}
|
||||
* }
|
||||
*/
|
||||
const token = Buffer.from(`${this.access.apiId}:${this.access.apiSecret}`).toString('base64');
|
||||
const res = await this.http.request<any, any>({
|
||||
url:"https://api.dns.la"+url,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Basic ${token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
if (res.code !== 200) {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
async getDomainDetail(domain:string){
|
||||
/**
|
||||
@@ -88,7 +51,7 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
|
||||
*/
|
||||
|
||||
const url = `/api/domain?domain=${domain}`;
|
||||
const res = await this.doRequestApi(url, null, 'get');
|
||||
const res = await this.access.doRequestApi(url, null, 'get');
|
||||
return res.data
|
||||
}
|
||||
|
||||
@@ -141,7 +104,7 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
|
||||
* CAA 257
|
||||
* URL转发 256
|
||||
*/
|
||||
const res = await this.doRequestApi(url, {
|
||||
const res = await this.access.doRequestApi(url, {
|
||||
domainId: domainId,
|
||||
type: 16,
|
||||
host: fullRecord.replace(`.${domain}`, ''),
|
||||
@@ -174,27 +137,14 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
|
||||
*/
|
||||
const recordId = record.id;
|
||||
const url = `/api/record?id=${recordId}`;
|
||||
await this.doRequestApi(url, null, 'delete');
|
||||
await this.access.doRequestApi(url, null, 'delete');
|
||||
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`);
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const url = `/api/domain?pageIndex=${pager.pageNo}&pageSize=${pager.pageSize}`;
|
||||
const ret = await this.doRequestApi(url, null, 'get');
|
||||
|
||||
|
||||
let list = ret.data.results || []
|
||||
list = list.map((item: any) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
}));
|
||||
const total = ret.data.total || list.length;
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
return await this.access.getDomainListPage(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//实例化这个provider,将其自动注册到系统中
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
|
||||
import { DogeClient } from './lib/index.js';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -35,6 +36,25 @@ export class DogeCloudAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
secretKey = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "测试授权是否正确"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const dogeClient = new DogeClient(this, this.ctx.http, this.ctx.logger);
|
||||
await dogeClient.request(
|
||||
'/cdn/domain/list.json',
|
||||
{},
|
||||
);
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
|
||||
new DogeCloudAccess();
|
||||
|
||||
@@ -32,6 +32,60 @@ export class GcoreAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
otpkey = '';
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.login();
|
||||
return "ok"
|
||||
}
|
||||
|
||||
async login() {
|
||||
let otp = null;
|
||||
if (this.otpkey) {
|
||||
const response = await this.ctx.http.request<any, any>({
|
||||
url: `https://cn-api.my-api.cn/api/totp/?key=${this.otpkey}`,
|
||||
method: 'get',
|
||||
});
|
||||
otp = response;
|
||||
this.ctx.logger.info('获取到otp:', otp);
|
||||
}
|
||||
const loginResponse = await this.doRequestApi(`/iam/auth/jwt/login`, {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
...(otp && { otp }),
|
||||
});
|
||||
const token = loginResponse.access;
|
||||
this.ctx.logger.info('Token 获取成功');
|
||||
return token;
|
||||
}
|
||||
|
||||
async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
|
||||
const baseApi = 'https://api.gcore.com';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { authorization: `Bearer ${token}` } : {}),
|
||||
};
|
||||
const res = await this.ctx.http.request<any, any>({
|
||||
url,
|
||||
baseURL: baseApi,
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
new GcoreAccess();
|
||||
|
||||
@@ -41,47 +41,20 @@ export class GcoreuploadPlugin extends AbstractTaskPlugin {
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
private readonly baseApi = 'https://api.gcore.com';
|
||||
|
||||
async onInstance() {}
|
||||
|
||||
private async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { authorization: `Bearer ${token}` } : {}),
|
||||
};
|
||||
const res = await this.http.request<any, any>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
const { cert, accessId } = this;
|
||||
const access = (await this.getAccess(accessId)) as GcoreAccess;
|
||||
let otp = null;
|
||||
if (access.otpkey) {
|
||||
const response = await this.http.request<any, any>({
|
||||
url: `https://cn-api.my-api.cn/api/totp/?key=${access.otpkey}`,
|
||||
method: 'get',
|
||||
});
|
||||
otp = response;
|
||||
this.logger.info('获取到otp:', otp);
|
||||
}
|
||||
const loginResponse = await this.doRequestApi(`${this.baseApi}/iam/auth/jwt/login`, {
|
||||
username: access.username,
|
||||
password: access.password,
|
||||
...(otp && { otp }),
|
||||
});
|
||||
const token = loginResponse.access;
|
||||
|
||||
const token = await access.login();
|
||||
this.logger.info('Token 获取成功');
|
||||
this.logger.info('开始上传证书');
|
||||
await this.doRequestApi(
|
||||
`${this.baseApi}/cdn/sslData`,
|
||||
await access.doRequestApi(
|
||||
`/cdn/sslData`,
|
||||
{
|
||||
name: this.certName,
|
||||
sslCertificate: cert.crt,
|
||||
|
||||
@@ -29,6 +29,25 @@ export class HuaweiAccess extends BaseAccess {
|
||||
accessKeySecret = '';
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
accessToken: { expiresAt: number, token: string }
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getProjectList();
|
||||
return "ok"
|
||||
}
|
||||
|
||||
|
||||
async getProjectList() {
|
||||
const endpoint = "https://iam.cn-north-4.myhuaweicloud.com";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {AccessInput, BaseAccess, IsAccess} from '@certd/pipeline';
|
||||
import {AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch} from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -32,6 +33,59 @@ export class JDCloudAccess extends BaseAccess {
|
||||
})
|
||||
secretAccessKey = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
accessToken: { expiresAt: number, token: string }
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainListPage({
|
||||
pageNo: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
return "ok"
|
||||
}
|
||||
|
||||
|
||||
async getJDDomainService() {
|
||||
const {JDDomainService} = await import("@certd/jdcloud")
|
||||
const service = new JDDomainService({
|
||||
credentials: {
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.secretAccessKey
|
||||
},
|
||||
regionId: "cn-north-1" //地域信息,某个api调用可以单独传参regionId,如果不传则会使用此配置中的regionId
|
||||
});
|
||||
return service;
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const service = await this.getJDDomainService();
|
||||
const domainRes = await service.describeDomains({
|
||||
domainName: req.searchKey,
|
||||
pageNumber: pager.pageNo,
|
||||
pageSize: pager.pageSize,
|
||||
})
|
||||
let list = domainRes.result?.dataList || []
|
||||
list = list.map((item: any) => ({
|
||||
id: item.domainId,
|
||||
domain: item.domainName,
|
||||
}));
|
||||
return {
|
||||
total:domainRes.result.totalCount || list.length,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new JDCloudAccess();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PageRes, PageSearch } from "@certd/pipeline";
|
||||
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import { JDCloudAccess } from "./access.js";
|
||||
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
|
||||
|
||||
@IsDnsProvider({
|
||||
name: "jdcloud",
|
||||
@@ -85,34 +85,11 @@ export class JDCloudDnsProvider extends AbstractDnsProvider {
|
||||
}
|
||||
|
||||
private async getJDDomainService() {
|
||||
const {JDDomainService} = await import("@certd/jdcloud")
|
||||
const service = new JDDomainService({
|
||||
credentials: {
|
||||
accessKeyId: this.access.accessKeyId,
|
||||
secretAccessKey: this.access.secretAccessKey
|
||||
},
|
||||
regionId: "cn-north-1" //地域信息,某个api调用可以单独传参regionId,如果不传则会使用此配置中的regionId
|
||||
});
|
||||
return service;
|
||||
return await this.access.getJDDomainService();
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const service = await this.getJDDomainService();
|
||||
const domainRes = await service.describeDomains({
|
||||
domainName: req.searchKey,
|
||||
pageNumber: pager.pageNo,
|
||||
pageSize: pager.pageSize,
|
||||
})
|
||||
let list = domainRes.result?.dataList || []
|
||||
list = list.map((item: any) => ({
|
||||
id: item.domainId,
|
||||
domain: item.domainName,
|
||||
}));
|
||||
return {
|
||||
total:domainRes.result.totalCount || list.length,
|
||||
list,
|
||||
};
|
||||
return await this.access.getDomainListPage(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from "@certd/pipeline";
|
||||
import { DomainRecord } from "@certd/plugin-lib";
|
||||
import { AliyunAccess } from "./aliyun-access.js";
|
||||
|
||||
@IsAccess({
|
||||
name: "aliesa",
|
||||
@@ -40,6 +42,68 @@ export class AliesaAccess extends BaseAccess {
|
||||
required: true,
|
||||
})
|
||||
region = "";
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainListPage({
|
||||
pageNo: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
return "ok"
|
||||
}
|
||||
|
||||
|
||||
async getEsaClient(){
|
||||
const access: AliesaAccess = this
|
||||
const aliAccess = await this.ctx.accessService.getById(access.accessId) as AliyunAccess
|
||||
const endpoint = `esa.${access.region}.aliyuncs.com`
|
||||
return aliAccess.getClient(endpoint)
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req)
|
||||
const client = await this.getEsaClient()
|
||||
const ret = await client.doRequest({
|
||||
// 接口名称
|
||||
action: "ListSites",
|
||||
// 接口版本
|
||||
version: "2024-09-10",
|
||||
// 接口协议
|
||||
protocol: "HTTPS",
|
||||
// 接口 HTTP 方法
|
||||
method: "GET",
|
||||
authType: "AK",
|
||||
style: "RPC",
|
||||
data: {
|
||||
query: {
|
||||
SiteName: req.searchKey,
|
||||
// ["SiteSearchType"] = "exact";
|
||||
SiteSearchType: "fuzzy",
|
||||
AccessType: "NS",
|
||||
PageSize: pager.pageSize,
|
||||
PageNumber: pager.pageNo,
|
||||
}
|
||||
}
|
||||
})
|
||||
const list = ret.Sites?.map(item => ({
|
||||
domain: item.SiteName,
|
||||
id: item.SiteId,
|
||||
}))
|
||||
return {
|
||||
list: list || [],
|
||||
total: ret.TotalCount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new AliesaAccess();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
import { AliyunClientV2 } from "../lib/aliyun-client-v2.js";
|
||||
import { AliyunSslClient } from "../lib/ssl-client.js";
|
||||
@IsAccess({
|
||||
name: "aliyun",
|
||||
title: "阿里云授权",
|
||||
@@ -28,6 +29,67 @@ export class AliyunAccess extends BaseAccess {
|
||||
})
|
||||
accessKeySecret = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCallerIdentity();
|
||||
return "ok"
|
||||
}
|
||||
|
||||
|
||||
async getStsClient() {
|
||||
const StsClient = await import('@alicloud/sts-sdk');
|
||||
|
||||
// 配置凭证
|
||||
const sts = new StsClient.default({
|
||||
endpoint: 'sts.aliyuncs.com',
|
||||
accessKeyId: this.accessKeyId,
|
||||
accessKeySecret: this.accessKeySecret,
|
||||
});
|
||||
|
||||
return sts
|
||||
}
|
||||
|
||||
|
||||
async getCallerIdentity() {
|
||||
|
||||
const sts = await this.getStsClient();
|
||||
// 调用 GetCallerIdentity 接口
|
||||
const result = await sts.getCallerIdentity();
|
||||
|
||||
this.ctx.logger.log("✅ 密钥有效!");
|
||||
this.ctx.logger.log(` 账户ID: ${result.AccountId}`);
|
||||
this.ctx.logger.log(` ARN: ${result.Arn}`);
|
||||
this.ctx.logger.log(` 用户ID: ${result.UserId}`);
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
accountId: result.AccountId,
|
||||
arn: result.Arn,
|
||||
userId: result.UserId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
getSslClient({ endpoint }: { endpoint: string }) {
|
||||
const client = new AliyunSslClient({
|
||||
access: this,
|
||||
logger: this.ctx.logger,
|
||||
endpoint,
|
||||
});
|
||||
return client
|
||||
}
|
||||
|
||||
|
||||
|
||||
getClient(endpoint: string) {
|
||||
return new AliyunClientV2({
|
||||
access: this,
|
||||
|
||||
@@ -23,6 +23,9 @@ export class OssClientFactory {
|
||||
} else if (type === "s3") {
|
||||
const module = await import("./impls/s3.js");
|
||||
return module.default;
|
||||
} else if (type === "scp") {
|
||||
const module = await import("./impls/scp.js");
|
||||
return module.default;
|
||||
} else {
|
||||
throw new Error(`暂不支持此文件上传方式: ${type}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import SftpOssClientImpl from "./sftp.js";
|
||||
|
||||
export default class ScpOssClientImpl extends SftpOssClientImpl {
|
||||
getUploaderType() {
|
||||
return 'scp';
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import fs from "fs";
|
||||
import { SftpAccess, SshAccess, SshClient } from "../../ssh/index.js";
|
||||
|
||||
export default class SftpOssClientImpl extends BaseOssClient<SftpAccess> {
|
||||
getUploaderType() {
|
||||
return 'sftp';
|
||||
}
|
||||
async download(fileName: string, savePath: string): Promise<void> {
|
||||
const path = this.join(this.rootDir, fileName);
|
||||
const client = new SshClient(this.logger);
|
||||
@@ -48,6 +51,7 @@ export default class SftpOssClientImpl extends BaseOssClient<SftpAccess> {
|
||||
const key = this.join(this.rootDir, filePath);
|
||||
try {
|
||||
const client = new SshClient(this.logger);
|
||||
const uploaderType = this.getUploaderType();
|
||||
await client.uploadFiles({
|
||||
connectConf: access,
|
||||
mkdirs: true,
|
||||
@@ -57,7 +61,7 @@ export default class SftpOssClientImpl extends BaseOssClient<SftpAccess> {
|
||||
remotePath: key,
|
||||
},
|
||||
],
|
||||
uploadType: "sftp",
|
||||
uploadType: uploaderType,
|
||||
opts: {
|
||||
mode: this.access?.fileMode ?? undefined,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
import { AccessInput, BaseAccess, IsAccess, PageSearch } from "@certd/pipeline";
|
||||
import { QiniuClient } from "./lib/sdk.js";
|
||||
|
||||
@IsAccess({
|
||||
name: "qiniu",
|
||||
@@ -21,6 +22,34 @@ export class QiniuAccess extends BaseAccess {
|
||||
helper: "SK",
|
||||
})
|
||||
secretKey!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
|
||||
async getDomainList(req: PageSearch = {}) {
|
||||
const qiniuClient = new QiniuClient({
|
||||
http: this.ctx.http,
|
||||
access:this,
|
||||
logger: this.ctx.logger,
|
||||
});
|
||||
const url = `https://api.qiniu.com/domain?limit=${req.pageSize || 1000}`;
|
||||
const res = await qiniuClient.doRequest(url, 'get');
|
||||
return res.domains||[]
|
||||
}
|
||||
}
|
||||
|
||||
new QiniuAccess();
|
||||
|
||||
@@ -57,6 +57,24 @@ export class TencentAccess extends BaseAccess {
|
||||
})
|
||||
closeExpiresNotify: boolean = true;
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCallerIdentity();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
isIntl() {
|
||||
return this.accountType === "intl";
|
||||
}
|
||||
@@ -68,4 +86,44 @@ export class TencentAccess extends BaseAccess {
|
||||
buildEndpoint(endpoint: string) {
|
||||
return `${this.intlDomain()}${endpoint}`;
|
||||
}
|
||||
|
||||
async getCallerIdentity(){
|
||||
const client = await this.getStsClient();
|
||||
|
||||
// 调用 GetCallerIdentity 接口
|
||||
const result = await client.GetCallerIdentity();
|
||||
|
||||
this.ctx.logger.info("✅ 密钥有效!");
|
||||
this.ctx.logger.info(` 账户ID: ${result.AccountId}`);
|
||||
this.ctx.logger.info(` ARN: ${result.Arn}`);
|
||||
this.ctx.logger.info(` 用户ID: ${result.UserId}`);
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
accountId: result.AccountId,
|
||||
arn: result.Arn,
|
||||
userId: result.UserId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async getStsClient(){
|
||||
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/sts/v20180813/index.js');
|
||||
const StsClient = sdk.v20180813.Client;
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: this.secretId,
|
||||
secretKey: this.secretKey,
|
||||
},
|
||||
region: 'ap-shanghai',
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: `sts.${this.intlDomain()}tencentcloudapi.com`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new StsClient(clientConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
|
||||
|
||||
import { IsAccess, AccessInput, BaseAccess, PageSearch, PageRes, Pager } from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
import { merge } from 'lodash-es';
|
||||
import qs from 'qs';
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||
@@ -25,6 +27,74 @@ export class NamesiloAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
apiKey = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainListPage({
|
||||
pageNo: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
return "ok"
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
|
||||
const pager = new Pager(req);
|
||||
const ret =await this.doRequest('/api/listDomains', {
|
||||
key:req.searchKey,
|
||||
page: pager.pageNo,
|
||||
pageSize: pager.pageSize,
|
||||
});
|
||||
let list = ret.domains ||[]
|
||||
// this.logger.info("获取域名列表成功:", ret);
|
||||
list = list.map((item: any) => ({
|
||||
id: item.domain,
|
||||
domain: item.domain,
|
||||
}));
|
||||
return {
|
||||
total:ret.pager?.total || list.length,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async doRequest(url: string, params: any = null) {
|
||||
params = merge(
|
||||
{
|
||||
version: 1,
|
||||
type: 'json',
|
||||
key: this.apiKey,
|
||||
},
|
||||
params
|
||||
);
|
||||
const qsString = qs.stringify(params);
|
||||
url = `${url}?${qsString}`;
|
||||
const res = await this.ctx.http.request<any, any>({
|
||||
url,
|
||||
baseURL: 'https://www.namesilo.com',
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.reply?.code !== '300' && res.reply?.code !== 300 && res.reply?.detail!=="success") {
|
||||
throw new Error(`${JSON.stringify(res.reply.detail)}`);
|
||||
}
|
||||
return res.reply;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new NamesiloAccess();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import qs from 'qs';
|
||||
import { NamesiloAccess } from './access.js';
|
||||
import { merge } from 'lodash-es';
|
||||
import { Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
|
||||
export type NamesiloRecord = {
|
||||
record_id: string;
|
||||
@@ -30,31 +28,6 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async doRequest(url: string, params: any = null) {
|
||||
params = merge(
|
||||
{
|
||||
version: 1,
|
||||
type: 'json',
|
||||
key: this.access.apiKey,
|
||||
},
|
||||
params
|
||||
);
|
||||
const qsString = qs.stringify(params);
|
||||
url = `${url}?${qsString}`;
|
||||
const res = await this.http.request<any, any>({
|
||||
url,
|
||||
baseURL: 'https://www.namesilo.com',
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.reply?.code !== '300' && res.reply?.code !== 300 && res.reply?.detail!=="success") {
|
||||
throw new Error(`${JSON.stringify(res.reply.detail)}`);
|
||||
}
|
||||
return res.reply;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建dns解析记录,用于验证域名所有权
|
||||
@@ -70,7 +43,7 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
|
||||
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
|
||||
|
||||
//domain=namesilo.com&rrtype=A&rrhost=test&rrvalue=55.55.55.55&rrttl=7207
|
||||
const record: any = await this.doRequest('/api/dnsAddRecord', {
|
||||
const record: any = await this.access.doRequest('/api/dnsAddRecord', {
|
||||
domain,
|
||||
rrtype: type,
|
||||
rrhost: hostRecord,
|
||||
@@ -97,7 +70,7 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
|
||||
//这里调用删除txt dns解析记录接口
|
||||
|
||||
const recordId = record.record_id;
|
||||
await this.doRequest('/api/dnsDeleteRecord', {
|
||||
await this.access.doRequest('/api/dnsDeleteRecord', {
|
||||
domain: options.recordReq.domain,
|
||||
rrid: recordId,
|
||||
});
|
||||
@@ -105,22 +78,7 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
|
||||
}
|
||||
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
|
||||
const pager = new Pager(req);
|
||||
const ret =await this.doRequest('/api/listDomains', {
|
||||
key:req.searchKey,
|
||||
page: pager.pageNo,
|
||||
pageSize: pager.pageSize,
|
||||
});
|
||||
// this.logger.info("获取域名列表成功:", ret);
|
||||
const list = ret.domains.map((item: any) => ({
|
||||
id: item.domain,
|
||||
domain: item.domain,
|
||||
}));
|
||||
return {
|
||||
total:ret.pager?.total || list.length,
|
||||
list,
|
||||
};
|
||||
return await this.access.getDomainListPage(req);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
|
||||
/**
|
||||
* Next Terminal 授权配置
|
||||
*/
|
||||
@IsAccess({
|
||||
name: 'nextTerminal',
|
||||
title: 'Next Terminal 授权',
|
||||
icon: 'clarity:plugin-line',
|
||||
desc: '用于访问 Next Terminal API 的授权配置',
|
||||
})
|
||||
export class NextTerminalAccess extends BaseAccess {
|
||||
|
||||
/**
|
||||
* Next Terminal 系统地址
|
||||
*/
|
||||
@AccessInput({
|
||||
title: '系统地址',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: 'https://nt.example.com:8088',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
baseUrl = '';
|
||||
|
||||
/**
|
||||
* API 令牌
|
||||
*/
|
||||
@AccessInput({
|
||||
title: 'API 令牌',
|
||||
helper: '个人中心->授权令牌->创建令牌',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: 'NT_xxxxx',
|
||||
},
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
apiToken = '';
|
||||
|
||||
/**
|
||||
* 测试按钮
|
||||
*/
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
/**
|
||||
* 测试接口连接
|
||||
*/
|
||||
async onTestRequest() {
|
||||
await this.GetCertificateList({});
|
||||
return "ok";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书列表
|
||||
*/
|
||||
async GetCertificateList(req: PageSearch): Promise<PageRes<any>> {
|
||||
this.ctx.logger.info(`获取 Next Terminal 证书列表,req:${JSON.stringify(req)}`);
|
||||
const pager = new Pager(req);
|
||||
const resp = await this.doRequest({
|
||||
url: '/api/admin/certificates/paging',
|
||||
method: 'GET',
|
||||
params: {
|
||||
pageIndex: pager.pageNo,
|
||||
pageSize: pager.pageSize,
|
||||
sortOrder: 'ascend',
|
||||
sortField: 'notAfter',
|
||||
}
|
||||
});
|
||||
|
||||
const total = resp?.total || 0;
|
||||
const list = resp?.items || [];
|
||||
|
||||
return {
|
||||
total,
|
||||
list
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新证书
|
||||
*/
|
||||
async UpdateCertificate(req: {
|
||||
certId: string;
|
||||
commonName: string;
|
||||
crt: string;
|
||||
key: string;
|
||||
}) {
|
||||
this.ctx.logger.info(`更新 Next Terminal 证书,certId:${req.certId}, commonName:${req.commonName}`);
|
||||
await this.doRequest({
|
||||
url: `/api/admin/certificates/${req.certId}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
commonName: req.commonName,
|
||||
type: 'imported',
|
||||
id: req.certId,
|
||||
certificate: req.crt,
|
||||
privateKey: req.key,
|
||||
renewBefore: 30,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 API 调用方法
|
||||
*/
|
||||
async doRequest(req: {
|
||||
url: string;
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
params?: any;
|
||||
data?: any;
|
||||
}) {
|
||||
const headers = {
|
||||
'X-Auth-Token': `${this.apiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
|
||||
this.ctx.logger.debug(`Next Terminal API 请求: ${req.method} ${this.baseUrl}${req.url}`);
|
||||
|
||||
const resp = await this.ctx.http.request({
|
||||
url: req.url,
|
||||
baseURL: this.baseUrl,
|
||||
method: req.method,
|
||||
headers,
|
||||
params: req.params,
|
||||
data: req.data,
|
||||
validateStatus: () => true, // 不自动抛出异常,让我们自己处理
|
||||
});
|
||||
|
||||
if (resp.code >0) {
|
||||
throw new Error(resp.message);
|
||||
}
|
||||
return resp
|
||||
}
|
||||
}
|
||||
|
||||
new NextTerminalAccess()
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './access.js';
|
||||
export * from './plugins/index.js';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './plugin-refresh-cert.js';
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { CertReader, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'NextTerminalRefreshCert',
|
||||
title: 'NextTerminal-更新证书',
|
||||
icon: 'clarity:plugin-line',
|
||||
desc: '更新 Next Terminal 证书',
|
||||
group: pluginGroups.panel.key,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class NextTerminalRefreshCert extends AbstractTaskPlugin {
|
||||
/**
|
||||
* 证书选择
|
||||
*/
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: ['CertApply'],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
/**
|
||||
* Next Terminal 授权
|
||||
*/
|
||||
@TaskInput({
|
||||
title: 'Next Terminal 授权',
|
||||
helper: '选择 Next Terminal 授权配置',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'nextTerminal',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
/**
|
||||
* 选择要更新的证书
|
||||
*/
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: '选择证书',
|
||||
helper: '选择要更新的 Next Terminal 证书(支持多选),如果这里没有列出,需要先前往控制台上传证书,之后就可以自动更新',
|
||||
action: NextTerminalRefreshCert.prototype.onGetCertList.name,
|
||||
watches: ['accessId'],
|
||||
required: true,
|
||||
multi: true,
|
||||
})
|
||||
)
|
||||
certIds!: string[];
|
||||
|
||||
/**
|
||||
* 获取证书列表
|
||||
*/
|
||||
async onGetCertList(req: PageSearch) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择 Next Terminal 授权');
|
||||
}
|
||||
|
||||
const access = await this.getAccess(this.accessId) as any;
|
||||
const certList = await access.GetCertificateList(req);
|
||||
|
||||
const options = certList.list.map((item: any) => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: `${item.commonName} <${item.id}>`,
|
||||
domain: item.commonName,
|
||||
};
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行证书更新
|
||||
*/
|
||||
async execute(): Promise<void> {
|
||||
const { cert, accessId, certIds } = this;
|
||||
|
||||
try {
|
||||
const access = await this.getAccess(accessId) as any;
|
||||
|
||||
// 确保 certIds 是数组
|
||||
const ids = Array.isArray(certIds) ? certIds : [certIds];
|
||||
|
||||
const certReader = new CertReader(cert);
|
||||
const mainDomain = certReader.getMainDomain();
|
||||
|
||||
for (const certId of ids) {
|
||||
this.logger.info(`更新 Next Terminal 证书: ${certId}`);
|
||||
|
||||
await access.UpdateCertificate({
|
||||
certId,
|
||||
commonName: mainDomain,
|
||||
crt: cert.crt,
|
||||
key: cert.key,
|
||||
});
|
||||
|
||||
this.logger.info(`证书 ${certId} 更新成功`);
|
||||
}
|
||||
|
||||
this.logger.info(`成功更新 ${ids.length} 个 Next Terminal 证书`);
|
||||
} catch (e) {
|
||||
this.logger.error('更新 Next Terminal 证书失败', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export class OnePanelClient {
|
||||
|
||||
async getAccessToken() {
|
||||
if (this.access.type === "apikey") {
|
||||
return this.getAccessTokenByApiKey();
|
||||
return await this.getAccessTokenByApiKey();
|
||||
} else {
|
||||
return await this.getAccessTokenByPassword();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
import { CtyunClient } from "../lib.js";
|
||||
import { HttpClient } from "@certd/basic";
|
||||
|
||||
@IsAccess({
|
||||
name: "ctyun",
|
||||
@@ -27,6 +29,37 @@ export class CtyunAccess extends BaseAccess {
|
||||
helper: "",
|
||||
})
|
||||
securityKey = "";
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCdnDomainList()
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getCdnDomainList() {
|
||||
const http: HttpClient = this.ctx.http;
|
||||
const client = new CtyunClient({
|
||||
access:this,
|
||||
http,
|
||||
logger: this.ctx.logger,
|
||||
});
|
||||
|
||||
// 008 是天翼云的CDN加速域名产品码
|
||||
const all = await client.getDomainList({ productCode: "008" });
|
||||
const list = all || []
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
new CtyunAccess();
|
||||
|
||||
@@ -29,6 +29,40 @@ export class K8sAccess extends BaseAccess {
|
||||
encrypt: false,
|
||||
})
|
||||
skipTLSVerify: boolean;
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
|
||||
const k8sClient = await this.getK8sClient()
|
||||
await k8sClient.getSecrets({
|
||||
namespace: "default",
|
||||
})
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getK8sClient() {
|
||||
const sdk = await import("@certd/lib-k8s");
|
||||
const K8sClient = sdk.K8sClient;
|
||||
|
||||
const k8sClient = new K8sClient({
|
||||
kubeConfigStr: this.kubeconfig,
|
||||
logger: this.ctx.logger,
|
||||
skipTLSVerify: this.skipTLSVerify,
|
||||
});
|
||||
return k8sClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new K8sAccess();
|
||||
|
||||
@@ -30,6 +30,87 @@ export class KuocaiCdnAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
password = "";
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const loginRes = await this.getLoginToken();
|
||||
await this.getDomainList(loginRes);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async getLoginToken() {
|
||||
const url = "https://kuocaicdn.com/login/loginUser";
|
||||
const data = {
|
||||
userAccount: this.username,
|
||||
userPwd: this.password,
|
||||
remember: true,
|
||||
};
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
data,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
returnOriginRes: true,
|
||||
});
|
||||
if (!res.data?.success) {
|
||||
throw new Error(res.data?.message);
|
||||
}
|
||||
|
||||
const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID");
|
||||
const token = res.data?.data;
|
||||
return {
|
||||
jsessionId,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async getDomainList(loginRes: any) {
|
||||
const url = "https://kuocaicdn.com/CdnDomain/queryForDatatables";
|
||||
const data = {
|
||||
draw: 1,
|
||||
start: 0,
|
||||
length: 1000,
|
||||
search: {
|
||||
value: "",
|
||||
regex: false,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await this.doRequest(url, loginRes, data);
|
||||
return res.data?.data;
|
||||
}
|
||||
|
||||
async doRequest(url: string, loginRes: any, data: any) {
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (!res.success) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new KuocaiCdnAccess();
|
||||
|
||||
+4
-64
@@ -54,7 +54,7 @@ export class KuocaiDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
const access = await this.getAccess<KuocaiCdnAccess>(this.accessId);
|
||||
const loginRes = await this.getLoginToken(access);
|
||||
const loginRes = await access.getLoginToken();
|
||||
|
||||
const curl = "https://kuocaicdn.com/CdnDomainHttps/httpsConfiguration";
|
||||
for (const domain of this.domains) {
|
||||
@@ -78,71 +78,11 @@ export class KuocaiDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
private_key: cert.key,
|
||||
},
|
||||
};
|
||||
await this.doRequest(curl, loginRes, update);
|
||||
await access.doRequest(curl, loginRes, update);
|
||||
this.logger.info(`站点${domain}证书更新成功`);
|
||||
}
|
||||
}
|
||||
|
||||
async getLoginToken(access: KuocaiCdnAccess) {
|
||||
const url = "https://kuocaicdn.com/login/loginUser";
|
||||
const data = {
|
||||
userAccount: access.username,
|
||||
userPwd: access.password,
|
||||
remember: true,
|
||||
};
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
data,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
returnOriginRes: true,
|
||||
});
|
||||
if (!res.data?.success) {
|
||||
throw new Error(res.data?.message);
|
||||
}
|
||||
|
||||
const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID");
|
||||
const token = res.data?.data;
|
||||
return {
|
||||
jsessionId,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async getDomainList(loginRes: any) {
|
||||
const url = "https://kuocaicdn.com/CdnDomain/queryForDatatables";
|
||||
const data = {
|
||||
draw: 1,
|
||||
start: 0,
|
||||
length: 1000,
|
||||
search: {
|
||||
value: "",
|
||||
regex: false,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await this.doRequest(url, loginRes, data);
|
||||
return res.data?.data;
|
||||
}
|
||||
|
||||
private async doRequest(url: string, loginRes: any, data: any) {
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (!res.success) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
if (!this.accessId) {
|
||||
@@ -150,9 +90,9 @@ export class KuocaiDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
}
|
||||
const access = await this.getAccess<KuocaiCdnAccess>(this.accessId);
|
||||
|
||||
const loginRes = await this.getLoginToken(access);
|
||||
const loginRes = await access.getLoginToken();
|
||||
|
||||
const list = await this.getDomainList(loginRes);
|
||||
const list = await access.getDomainList(loginRes);
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("您账户下还没有站点域名,请先添加域名");
|
||||
|
||||
@@ -85,6 +85,90 @@ export class LeCDNAccess extends BaseAccess {
|
||||
`,
|
||||
})
|
||||
apiToken = "";
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
_token: string;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCerts();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
|
||||
async getCerts() {
|
||||
// http://cdnadmin.kxfox.com/prod-api/certificate?current_page=1&total=3&page_size=10
|
||||
return await this.doRequest({
|
||||
url: `/prod-api/certificate`,
|
||||
method: "get",
|
||||
params: {
|
||||
current_page: 1,
|
||||
page_size: 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async doRequest(config: any) {
|
||||
|
||||
const token = await this.getToken();
|
||||
const access = this;
|
||||
const Authorization = access.type === "token" ? access.apiToken : `Bearer ${token}`;
|
||||
const res = await this.ctx.http.request({
|
||||
baseURL: access.url,
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
...config,
|
||||
});
|
||||
this.checkRes(res);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
|
||||
async getToken() {
|
||||
if (this.type === "token") {
|
||||
return this.apiToken;
|
||||
}
|
||||
if (this._token){
|
||||
return this._token;
|
||||
}
|
||||
// http://cdnadmin.kxfox.com/prod-api/login
|
||||
const access = this;
|
||||
const res = await this.ctx.http.request({
|
||||
url: `/prod-api/login`,
|
||||
baseURL: access.url,
|
||||
method: "post",
|
||||
data: {
|
||||
//新旧版本不一样,旧版本是username,新版本是email
|
||||
email: access.username,
|
||||
username: access.username,
|
||||
password: access.password,
|
||||
},
|
||||
});
|
||||
this.checkRes(res);
|
||||
//新旧版本不一样,旧版本是access_token,新版本是token
|
||||
const token = res.data.access_token || res.data.token;
|
||||
|
||||
this._token = token;
|
||||
return token;
|
||||
}
|
||||
|
||||
private checkRes(res: any) {
|
||||
if (res.code !== 0 && res.code !== 200) {
|
||||
throw new Error(res.message || JSON.stringify(res));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new LeCDNAccess();
|
||||
|
||||
+5
-55
@@ -58,49 +58,15 @@ export class LeCDNUpdateCertV2 extends AbstractTaskPlugin {
|
||||
|
||||
async onInstance() {
|
||||
this.access = await this.getAccess<LeCDNAccess>(this.accessId);
|
||||
this.token = await this.getToken();
|
||||
this.access.getToken();
|
||||
}
|
||||
|
||||
async doRequest(config: any) {
|
||||
const access = this.access;
|
||||
const Authorization = this.access.type === "token" ? this.access.apiToken : `Bearer ${this.token}`;
|
||||
const res = await this.ctx.http.request({
|
||||
baseURL: access.url,
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
...config,
|
||||
});
|
||||
this.checkRes(res);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async getToken() {
|
||||
if (this.access.type === "token") {
|
||||
return this.access.apiToken;
|
||||
}
|
||||
// http://cdnadmin.kxfox.com/prod-api/login
|
||||
const access = this.access;
|
||||
const res = await this.ctx.http.request({
|
||||
url: `/prod-api/login`,
|
||||
baseURL: access.url,
|
||||
method: "post",
|
||||
data: {
|
||||
//新旧版本不一样,旧版本是username,新版本是email
|
||||
email: access.username,
|
||||
username: access.username,
|
||||
password: access.password,
|
||||
},
|
||||
});
|
||||
this.checkRes(res);
|
||||
//新旧版本不一样,旧版本是access_token,新版本是token
|
||||
return res.data.access_token || res.data.token;
|
||||
}
|
||||
|
||||
async getCertInfo(id: number) {
|
||||
// http://cdnadmin.kxfox.com/prod-api/certificate/9
|
||||
// Bearer edGkiOiIJ8
|
||||
return await this.doRequest({
|
||||
return await this.access.doRequest({
|
||||
url: `/prod-api/certificate/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -117,7 +83,7 @@ export class LeCDNUpdateCertV2 extends AbstractTaskPlugin {
|
||||
|
||||
this.logger.info(`证书名称:${certInfo.name}`);
|
||||
|
||||
return await this.doRequest({
|
||||
return await this.access.doRequest({
|
||||
url: `/prod-api/certificate/${id}`,
|
||||
method: "put",
|
||||
data: certInfo,
|
||||
@@ -134,17 +100,13 @@ export class LeCDNUpdateCertV2 extends AbstractTaskPlugin {
|
||||
this.logger.info(`更新证书完成`);
|
||||
}
|
||||
|
||||
private checkRes(res: any) {
|
||||
if (res.code !== 0 && res.code !== 200) {
|
||||
throw new Error(res.message || JSON.stringify(res));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async onGetCertList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
const res = await this.getCerts();
|
||||
const res = await this.access.getCerts();
|
||||
//新旧版本不一样,一个data 一个是items
|
||||
const list = res.items || res.data;
|
||||
if (!res || list.length === 0) {
|
||||
@@ -158,18 +120,6 @@ export class LeCDNUpdateCertV2 extends AbstractTaskPlugin {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async getCerts() {
|
||||
// http://cdnadmin.kxfox.com/prod-api/certificate?current_page=1&total=3&page_size=10
|
||||
return await this.doRequest({
|
||||
url: `/prod-api/certificate`,
|
||||
method: "get",
|
||||
params: {
|
||||
current_page: 1,
|
||||
page_size: 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new LeCDNUpdateCertV2();
|
||||
|
||||
@@ -49,6 +49,62 @@ export class LuckyAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
openToken = "";
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCertList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async doRequest(req: { urlPath: string; data: any; method?: string }) {
|
||||
const { urlPath, data, method } = req;
|
||||
let url = `${this.url}/${this.safePath || ""}${urlPath}?_=${Math.floor(new Date().getTime())}`;
|
||||
// 从第7个字符起,将//替换成/
|
||||
const protocol = url.substring(0, 7);
|
||||
let suffix = url.substring(7);
|
||||
suffix = suffix.replaceAll("//", "/");
|
||||
suffix = suffix.replaceAll("//", "/");
|
||||
url = protocol + suffix;
|
||||
|
||||
const headers: any = {
|
||||
// Origin: access.url,
|
||||
"Content-Type": "application/json",
|
||||
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36",
|
||||
};
|
||||
headers["openToken"] = this.openToken;
|
||||
const res = await this.ctx.http.request({
|
||||
method: method || "POST",
|
||||
url,
|
||||
data,
|
||||
headers,
|
||||
skipSslVerify: true,
|
||||
});
|
||||
if (res.ret !== 0) {
|
||||
throw new Error(`请求失败:${res.msg}`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async getCertList() {
|
||||
const res = await this.doRequest({
|
||||
urlPath: "/api/ssl",
|
||||
data: {},
|
||||
method: "GET",
|
||||
});
|
||||
const list = res.list || [];
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
new LuckyAccess();
|
||||
|
||||
@@ -82,8 +82,7 @@ export class LuckyUpdateCert extends AbstractPlusTaskPlugin {
|
||||
throw new Error(`没有找到证书:Key=${item},请确认该证书是否存在`);
|
||||
}
|
||||
const remark = old.Remark;
|
||||
const res = await this.doRequest({
|
||||
access,
|
||||
const res = await access.doRequest({
|
||||
urlPath: "/api/ssl",
|
||||
method: "PUT",
|
||||
data: {
|
||||
@@ -107,44 +106,9 @@ export class LuckyUpdateCert extends AbstractPlusTaskPlugin {
|
||||
this.logger.info("部署成功");
|
||||
}
|
||||
|
||||
async doRequest(req: { access: LuckyAccess; urlPath: string; data: any; method?: string }) {
|
||||
const { access, urlPath, data, method } = req;
|
||||
let url = `${access.url}/${access.safePath || ""}${urlPath}?_=${Math.floor(new Date().getTime())}`;
|
||||
// 从第7个字符起,将//替换成/
|
||||
const protocol = url.substring(0, 7);
|
||||
let suffix = url.substring(7);
|
||||
suffix = suffix.replaceAll("//", "/");
|
||||
suffix = suffix.replaceAll("//", "/");
|
||||
url = protocol + suffix;
|
||||
|
||||
const headers: any = {
|
||||
// Origin: access.url,
|
||||
"Content-Type": "application/json",
|
||||
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36",
|
||||
};
|
||||
headers["openToken"] = access.openToken;
|
||||
const res = await this.http.request({
|
||||
method: method || "POST",
|
||||
url,
|
||||
data,
|
||||
headers,
|
||||
skipSslVerify: true,
|
||||
});
|
||||
if (res.ret !== 0) {
|
||||
throw new Error(`请求失败:${res.msg}`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async onGetCertList() {
|
||||
const access: LuckyAccess = await this.getAccess<LuckyAccess>(this.accessId);
|
||||
const res = await this.doRequest({
|
||||
access,
|
||||
urlPath: "/api/ssl",
|
||||
data: {},
|
||||
method: "GET",
|
||||
});
|
||||
const list = res.list;
|
||||
const list = await access.getCertList();
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("没有找到证书,请先在SSL/TLS证书页面中手动上传一次证书");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
import { MaoyunClient } from "@certd/plugin-plus";
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -49,6 +50,45 @@ export class MaoyunAccess extends BaseAccess {
|
||||
required: false,
|
||||
})
|
||||
httpProxy!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCdnDomainList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getCdnDomainList() {
|
||||
const client = new MaoyunClient({
|
||||
http: this.ctx.http,
|
||||
logger: this.ctx.logger,
|
||||
access: this,
|
||||
});
|
||||
await client.login();
|
||||
const res = await client.doRequest({
|
||||
url: "/cdn/domain",
|
||||
data: {},
|
||||
params: {
|
||||
channel_type: "0,1,2",
|
||||
page: 1,
|
||||
page_size: 1000,
|
||||
},
|
||||
method: "GET",
|
||||
});
|
||||
const list = res.data || [];
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
new MaoyunAccess();
|
||||
|
||||
+1
-17
@@ -115,23 +115,7 @@ export class MaoyunDeployToCdn extends AbstractPlusTaskPlugin {
|
||||
|
||||
async onGetDomainList() {
|
||||
const access: MaoyunAccess = await this.getAccess<MaoyunAccess>(this.accessId);
|
||||
const client = new MaoyunClient({
|
||||
http: this.ctx.http,
|
||||
logger: this.logger,
|
||||
access,
|
||||
});
|
||||
await client.login();
|
||||
const res = await client.doRequest({
|
||||
url: "/cdn/domain",
|
||||
data: {},
|
||||
params: {
|
||||
channel_type: "0,1,2",
|
||||
page: 1,
|
||||
page_size: 1000,
|
||||
},
|
||||
method: "GET",
|
||||
});
|
||||
const list = res.data;
|
||||
const list = await access.getCdnDomainList();
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("没有找到加速域名,请先在控制台添加加速域名");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { HttpRequestConfig } from "@certd/basic";
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,48 @@ export class SafelineAccess extends BaseAccess {
|
||||
helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭",
|
||||
})
|
||||
skipSslVerify = true;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCertList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getCertList() {
|
||||
const res = await this.doRequest({
|
||||
url: "/api/open/cert",
|
||||
method: "get",
|
||||
data: {},
|
||||
});
|
||||
const nodes = res?.nodes || [];
|
||||
return nodes
|
||||
}
|
||||
|
||||
|
||||
async doRequest(config: HttpRequestConfig<any>) {
|
||||
config.baseURL = this.baseUrl;
|
||||
config.skipSslVerify = this.skipSslVerify ?? false;
|
||||
config.logRes = false;
|
||||
config.logParams = false;
|
||||
config.headers = {
|
||||
"X-SLCE-API-TOKEN": this.apiToken,
|
||||
};
|
||||
const res = await this.ctx.http.request(config);
|
||||
if (!res.err) {
|
||||
return res.data;
|
||||
}
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new SafelineAccess();
|
||||
|
||||
+4
-18
@@ -1,9 +1,8 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { HttpRequestConfig } from "@certd/basic";
|
||||
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { SafelineAccess } from "../access.js";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { SafelineAccess } from "../access.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "SafelineDeployToWebsitePlugin",
|
||||
@@ -83,7 +82,7 @@ export class SafelineDeployToWebsitePlugin extends AbstractTaskPlugin {
|
||||
data.id = parseInt(certId)
|
||||
type = "更新"
|
||||
}
|
||||
const res = await this.doRequest({
|
||||
const res = await this.access.doRequest({
|
||||
url: "/api/open/cert",
|
||||
method: "post",
|
||||
data:data
|
||||
@@ -91,25 +90,12 @@ export class SafelineDeployToWebsitePlugin extends AbstractTaskPlugin {
|
||||
this.logger.info(`证书<${certId}>${type}成功,ID:${res}`);
|
||||
}
|
||||
|
||||
async doRequest(config: HttpRequestConfig<any>) {
|
||||
config.baseURL = this.access.baseUrl;
|
||||
config.skipSslVerify = this.access.skipSslVerify ?? false;
|
||||
config.logRes = false;
|
||||
config.logParams = false;
|
||||
config.headers = {
|
||||
"X-SLCE-API-TOKEN": this.access.apiToken,
|
||||
};
|
||||
const res = await this.ctx.http.request(config);
|
||||
if (!res.err) {
|
||||
return res.data;
|
||||
}
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
|
||||
|
||||
// requestHandle
|
||||
|
||||
async onGetCertIds() {
|
||||
const res = await this.doRequest({
|
||||
const res = await this.access.doRequest({
|
||||
url: "/api/open/cert",
|
||||
method: "get",
|
||||
data: {},
|
||||
|
||||
@@ -124,6 +124,23 @@ export class SynologyAccess extends BaseAccess {
|
||||
})
|
||||
timeout = 120;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const client = new SynologyClient(this as any, this.ctx.http, this.ctx.logger, this.skipSslVerify);
|
||||
await client.doLogin();
|
||||
await client.getCertList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
onLoginWithOPTCode(data: { otpCode: string }) {
|
||||
const ctx = this.ctx;
|
||||
const client = new SynologyClient(this as any, ctx.http, ctx.logger, this.skipSslVerify);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./plugin-deploy-to-panel.js";
|
||||
export * from "./plugin-keep-alive.js";
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export class SynologyDeployToPanel extends AbstractPlusTaskPlugin {
|
||||
//授权选择框
|
||||
@TaskInput({
|
||||
title: "群晖授权",
|
||||
helper: "群晖登录授权,请确保账户是管理员用户组",
|
||||
helper: "群晖登录授权,请确保账户是管理员用户组\n群晖OTP授权有效期只有30天,您还需要添加“群晖-刷新OTP登录有效期”任务做登录有效期保活",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "synology",
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import { AbstractPlusTaskPlugin, SynologyClient } from "@certd/plugin-plus";
|
||||
import dayjs from "dayjs";
|
||||
import { SynologyAccess } from "../access.js";
|
||||
@IsTaskPlugin({
|
||||
name: "SynologyKeepAlive",
|
||||
title: "群晖-刷新OTP登录有效期",
|
||||
icon: "simple-icons:synology",
|
||||
group: pluginGroups.panel.key,
|
||||
desc: "群晖登录状态可能30天失效,需要在失效之前登录一次,刷新有效期,您可以将其放在“部署到群晖面板”任务之后",
|
||||
showRunStrategy: false,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.AlwaysRun,
|
||||
},
|
||||
},
|
||||
needPlus: true,
|
||||
})
|
||||
export class SynologyKeepAlivePlugin extends AbstractPlusTaskPlugin {
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: "群晖授权",
|
||||
helper: "群晖登录授权,请确保账户是管理员用户组",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "synology",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
|
||||
//授权选择框
|
||||
@TaskInput({
|
||||
title: "间隔天数",
|
||||
helper: "多少天刷新一次,建议15天以内",
|
||||
value: 15,
|
||||
component: {
|
||||
name: "a-input-number",
|
||||
vModel:"value",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
intervalDays!: number;
|
||||
|
||||
@TaskOutput({
|
||||
title: "上次刷新时间",
|
||||
type :"SynologyLastRefreshTime"
|
||||
})
|
||||
lastRefreshTime!: number;
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<any> {
|
||||
this.logger.info("开始刷新群晖登录有效期");
|
||||
const now = dayjs()
|
||||
const status = this.getLastStatus();
|
||||
if (status) {
|
||||
let lastRefreshTime = this.getLastOutput("lastRefreshTime");
|
||||
lastRefreshTime = lastRefreshTime || 0;
|
||||
this.lastRefreshTime = lastRefreshTime;
|
||||
const lastTime = dayjs(lastRefreshTime);
|
||||
const diffDays = now.diff(lastTime, "day");
|
||||
|
||||
this.logger.info(`上次刷新时间${lastTime.format("YYYY-MM-DD")}`);
|
||||
if (diffDays < this.intervalDays) {
|
||||
this.logger.info(`距离上次刷新${diffDays}天,不足${this.intervalDays}天,无需刷新`);
|
||||
this.logger.info(`下一次刷新时间${lastTime.add(this.intervalDays, "day").format("YYYY-MM-DD")}`);
|
||||
return "skip";
|
||||
}else{
|
||||
this.logger.info(`超过${this.intervalDays}天,需要刷新`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const access: SynologyAccess = await this.getAccess<SynologyAccess>(this.accessId);
|
||||
const client = new SynologyClient(access as any, this.ctx.http, this.ctx.logger, access.skipSslVerify);
|
||||
await client.doLogin();
|
||||
await client.getCertList();
|
||||
this.lastRefreshTime = now.valueOf();
|
||||
this.logger.info("刷新群晖登录有效期成功");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
new SynologyKeepAlivePlugin();
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
import { UniCloudClient } from "@certd/plugin-plus";
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -29,6 +30,28 @@ export class UniCloudAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
password = "";
|
||||
|
||||
// await this.getToken();
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const client = new UniCloudClient({
|
||||
access: this,
|
||||
logger: this.ctx.logger,
|
||||
http: this.ctx.http,
|
||||
});
|
||||
await client.getToken();
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
|
||||
new UniCloudAccess();
|
||||
|
||||
@@ -30,6 +30,85 @@ export class YidunRcdnAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
password = "";
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const token = await this.getLoginToken();
|
||||
await this.getDomainList(token);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getDomainList(loginRes: any) {
|
||||
const url = "https://rhcdn.yiduncdn.com/CdnDomain/queryForDatatables";
|
||||
const data = {
|
||||
draw: 1,
|
||||
start: 0,
|
||||
length: 1000,
|
||||
search: {
|
||||
value: "",
|
||||
regex: false,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await this.doRequest(url, loginRes, data);
|
||||
return res.data?.data;
|
||||
}
|
||||
|
||||
async doRequest(url: string, loginRes: any, data: any) {
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (!res.success) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async getLoginToken() {
|
||||
const access: YidunRcdnAccess = this
|
||||
const url = "https://rhcdn.yiduncdn.com/login/loginUser";
|
||||
const data = {
|
||||
userAccount: access.username,
|
||||
userPwd: access.password,
|
||||
remember: true,
|
||||
};
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
data,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
returnOriginRes: true,
|
||||
});
|
||||
if (!res.data?.success) {
|
||||
throw new Error(res.data?.message);
|
||||
}
|
||||
|
||||
const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID");
|
||||
const token = res.data?.data;
|
||||
return {
|
||||
jsessionId,
|
||||
token,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
new YidunRcdnAccess();
|
||||
|
||||
@@ -32,6 +32,47 @@ export class YidunAccess extends BaseAccess {
|
||||
encrypt: true,
|
||||
})
|
||||
apiSecret = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getDomainList(){
|
||||
const siteUrl = "http://user.yiduncdn.com/v1/sites";
|
||||
const res = await this.doRequest(siteUrl, "GET", { });
|
||||
return res.data
|
||||
}
|
||||
|
||||
async doRequest(url: string, method: string, data: any) {
|
||||
const access = this
|
||||
const { apiKey, apiSecret } = access;
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
"api-key": apiKey,
|
||||
"api-secret": apiSecret,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (res.code != 0) {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new YidunAccess();
|
||||
|
||||
+16
-25
@@ -1,5 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { YidunAccess } from "../access.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "YidunDeployToCDN",
|
||||
@@ -60,7 +61,11 @@ export class YidunDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
async onInstance() {}
|
||||
access!: YidunAccess;
|
||||
|
||||
async onInstance() {
|
||||
this.access = await this.getAccess<YidunAccess>(this.accessId);
|
||||
}
|
||||
async execute(): Promise<void> {
|
||||
const { domain, certId, cert } = this;
|
||||
if (!domain && !certId) {
|
||||
@@ -77,35 +82,21 @@ export class YidunDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
private async updateByCertId(cert: CertInfo, certId: number) {
|
||||
this.logger.info(`更新证书,证书ID:${certId}`);
|
||||
const url = `http://user.yiduncdn.com/v1/certs/${certId}`;
|
||||
await this.doRequest(url, "PUT", {
|
||||
|
||||
const access = await this.getAccess<YidunAccess>(this.accessId);
|
||||
|
||||
await access.doRequest(url, "PUT", {
|
||||
cert: cert.crt,
|
||||
key: cert.key,
|
||||
});
|
||||
}
|
||||
|
||||
async doRequest(url: string, method: string, data: any) {
|
||||
const access = await this.getAccess(this.accessId);
|
||||
const { apiKey, apiSecret } = access;
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
"api-key": apiKey,
|
||||
"api-secret": apiSecret,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (res.code != 0) {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private async updateByDomain(cert: CertInfo) {
|
||||
//查询站点
|
||||
const siteUrl = "http://user.yiduncdn.com/v1/sites";
|
||||
const res = await this.doRequest(siteUrl, "GET", { domain: this.domain });
|
||||
const access = this.access
|
||||
const res = await access.doRequest(siteUrl, "GET", { domain: this.domain });
|
||||
if (res.data.length === 0) {
|
||||
throw new Error(`未找到域名相关站点:${this.domain}`);
|
||||
}
|
||||
@@ -127,20 +118,20 @@ export class YidunDeployToCDNPlugin extends AbstractTaskPlugin {
|
||||
this.logger.info(`创建证书,域名:${this.domain}`);
|
||||
const certUrl = `http://user.yiduncdn.com/v1/certs`;
|
||||
const name = this.domain + "_" + new Date().getTime();
|
||||
await this.doRequest(certUrl, "POST", {
|
||||
await access.doRequest(certUrl, "POST", {
|
||||
name,
|
||||
type: "custom",
|
||||
cert: cert.crt,
|
||||
key: cert.key,
|
||||
});
|
||||
|
||||
const certs: any = await this.doRequest(certUrl, "GET", {
|
||||
const certs: any = await access.doRequest(certUrl, "GET", {
|
||||
name,
|
||||
});
|
||||
const certId = certs.data[0].id;
|
||||
|
||||
const siteUrl = "http://user.yiduncdn.com/v1/sites";
|
||||
await this.doRequest(siteUrl, "PUT", { id: site.id, https_listen: { cert: certId } });
|
||||
await access.doRequest(siteUrl, "PUT", { id: site.id, https_listen: { cert: certId } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-63
@@ -54,7 +54,7 @@ export class YidunDeployToRCDNPlugin extends AbstractTaskPlugin {
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
const access = await this.getAccess<YidunRcdnAccess>(this.accessId);
|
||||
const loginRes = await this.getLoginToken(access);
|
||||
const loginRes = await access.getLoginToken();
|
||||
|
||||
const curl = "https://rhcdn.yiduncdn.com/CdnDomainHttps/httpsConfiguration";
|
||||
for (const domain of this.domains) {
|
||||
@@ -78,71 +78,14 @@ export class YidunDeployToRCDNPlugin extends AbstractTaskPlugin {
|
||||
private_key: cert.key,
|
||||
},
|
||||
};
|
||||
await this.doRequest(curl, loginRes, update);
|
||||
await access.doRequest(curl, loginRes, update);
|
||||
this.logger.info(`站点${domain}证书更新成功`);
|
||||
}
|
||||
}
|
||||
|
||||
async getLoginToken(access: YidunRcdnAccess) {
|
||||
const url = "https://rhcdn.yiduncdn.com/login/loginUser";
|
||||
const data = {
|
||||
userAccount: access.username,
|
||||
userPwd: access.password,
|
||||
remember: true,
|
||||
};
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
data,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
returnOriginRes: true,
|
||||
});
|
||||
if (!res.data?.success) {
|
||||
throw new Error(res.data?.message);
|
||||
}
|
||||
|
||||
|
||||
const jsessionId = this.ctx.utils.request.getCookie(res, "JSESSIONID");
|
||||
const token = res.data?.data;
|
||||
return {
|
||||
jsessionId,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async getDomainList(loginRes: any) {
|
||||
const url = "https://rhcdn.yiduncdn.com/CdnDomain/queryForDatatables";
|
||||
const data = {
|
||||
draw: 1,
|
||||
start: 0,
|
||||
length: 1000,
|
||||
search: {
|
||||
value: "",
|
||||
regex: false,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await this.doRequest(url, loginRes, data);
|
||||
return res.data?.data;
|
||||
}
|
||||
|
||||
private async doRequest(url: string, loginRes: any, data: any) {
|
||||
const http = this.ctx.http;
|
||||
const res: any = await http.request({
|
||||
url,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Cookie: `JSESSIONID=${loginRes.jsessionId};kuocai_cdn_token=${loginRes.token}`,
|
||||
},
|
||||
data,
|
||||
});
|
||||
if (!res.success) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
if (!this.accessId) {
|
||||
@@ -150,9 +93,9 @@ export class YidunDeployToRCDNPlugin extends AbstractTaskPlugin {
|
||||
}
|
||||
const access = await this.getAccess<YidunRcdnAccess>(this.accessId);
|
||||
|
||||
const loginRes = await this.getLoginToken(access);
|
||||
const loginRes = await access.getLoginToken();
|
||||
|
||||
const list = await this.getDomainList(loginRes);
|
||||
const list = await access.getDomainList(loginRes);
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("您账户下还没有站点域名,请先添加域名");
|
||||
|
||||
@@ -70,6 +70,47 @@ export class ProxmoxAccess extends BaseAccess {
|
||||
encrypt: false,
|
||||
})
|
||||
realm = '';
|
||||
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getNodeList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
|
||||
async getNodeList() {
|
||||
const client = await this.getClient();
|
||||
const nodesRes = await client.nodes.index();
|
||||
// this.logger.info('nodes:', nodesRes.response);
|
||||
if (!nodesRes.response?.data) {
|
||||
return []
|
||||
}
|
||||
return nodesRes.response.data
|
||||
}
|
||||
|
||||
async getClient() {
|
||||
const pve = await import('@certd/cv4pve-api-javascript');
|
||||
const client = new pve.PveClient(this.host, this.port);
|
||||
const login = await client.login(this.username, this.password, this.realm || 'pam');
|
||||
if (!login) {
|
||||
throw new Error(`Login failed:${JSON.stringify(login)}`);
|
||||
}
|
||||
const versionRes = await client.version.version();
|
||||
this.ctx.logger.info('Proxmox version:', versionRes.response);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
new ProxmoxAccess();
|
||||
|
||||
@@ -66,8 +66,8 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
|
||||
//插件执行方法
|
||||
async execute(): Promise<void> {
|
||||
const { cert } = this;
|
||||
|
||||
const client = await this.getClient();
|
||||
const access = await this.getAccess<ProxmoxAccess>(this.accessId);
|
||||
const client = await access.getClient();
|
||||
|
||||
for (const node of this.nodes) {
|
||||
this.logger.info(`开始上传证书到节点:${node}`);
|
||||
@@ -84,31 +84,17 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
|
||||
this.logger.info('部署成功');
|
||||
}
|
||||
|
||||
async onGetNodeList() {
|
||||
const client = await this.getClient();
|
||||
async onGetNodeList() {
|
||||
|
||||
const nodesRes = await client.nodes.index();
|
||||
// this.logger.info('nodes:', nodesRes.response);
|
||||
return nodesRes.response.data.map((node: any) => {
|
||||
const access = await this.getAccess<ProxmoxAccess>(this.accessId);
|
||||
const nodesRes = await access.getNodeList();
|
||||
return nodesRes.map((node: any) => {
|
||||
return {
|
||||
value: node.node,
|
||||
label: node.node,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getClient() {
|
||||
const access: ProxmoxAccess = await this.getAccess<ProxmoxAccess>(this.accessId);
|
||||
const pve = await import('@certd/cv4pve-api-javascript');
|
||||
const client = new pve.PveClient(access.host, access.port);
|
||||
const login = await client.login(access.username, access.password, access.realm || 'pam');
|
||||
if (!login) {
|
||||
throw new Error(`Login failed:${JSON.stringify(login)}`);
|
||||
}
|
||||
const versionRes = await client.version.version();
|
||||
this.logger.info('Proxmox version:', versionRes.response);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
//实例化一下,注册插件
|
||||
new ProxmoxUploadCert();
|
||||
|
||||
@@ -108,15 +108,10 @@ export class QiniuDeployCertToCDN extends AbstractTaskPlugin {
|
||||
|
||||
async onGetDomainList() {
|
||||
const access = await this.getAccess<QiniuAccess>(this.accessId);
|
||||
const qiniuClient = new QiniuClient({
|
||||
http: this.ctx.http,
|
||||
access,
|
||||
logger: this.logger,
|
||||
});
|
||||
const url = `https://api.qiniu.com/domain?limit=1000`;
|
||||
const res = await qiniuClient.doRequest(url, 'get');
|
||||
|
||||
const domains = await access.getDomainList()
|
||||
|
||||
const options = res.domains.map((item: any) => {
|
||||
const options = domains.map((item: any) => {
|
||||
return {
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {AccessInput, BaseAccess, IsAccess} from '@certd/pipeline';
|
||||
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||
import { UpyunClient } from './client.js';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -30,6 +31,41 @@ export class UpyunAccess extends BaseAccess {
|
||||
})
|
||||
password = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCdnList();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
async getCdnList() {
|
||||
const upyunClient = new UpyunClient({
|
||||
access: this,
|
||||
logger: this.ctx.logger,
|
||||
http: this.ctx.http
|
||||
});
|
||||
const cookie = await upyunClient.getLoginToken();
|
||||
const req = {
|
||||
cookie,
|
||||
url: "https://console.upyun.com/api/account/domains/?limit=1000&business_type=file&security_cdn=false&websocket=false&key=&domain=",
|
||||
method: "GET",
|
||||
data: {}
|
||||
};
|
||||
const res = await upyunClient.doRequest(req);
|
||||
const domains = res.data?.domains || [];
|
||||
return domains
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
new UpyunAccess();
|
||||
|
||||
@@ -160,21 +160,7 @@ export class UpyunDeployToCdn extends AbstractTaskPlugin {
|
||||
}
|
||||
const access = await this.getAccess<UpyunAccess>(this.accessId);
|
||||
|
||||
const upyunClient = new UpyunClient({
|
||||
access,
|
||||
logger: this.logger,
|
||||
http: this.ctx.http
|
||||
});
|
||||
const cookie = await upyunClient.getLoginToken();
|
||||
const req = {
|
||||
cookie,
|
||||
url: "https://console.upyun.com/api/account/domains/?limit=15&business_type=file&security_cdn=false&websocket=false&key=&domain=",
|
||||
method: "GET",
|
||||
data: {}
|
||||
};
|
||||
const res = await upyunClient.doRequest(req);
|
||||
|
||||
const domains = res.data?.domains;
|
||||
const domains = await access.getCdnList();
|
||||
if (!domains || domains.length === 0) {
|
||||
throw new Error("没有找到加速域名");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {AccessInput, BaseAccess, IsAccess} from '@certd/pipeline';
|
||||
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||
import { VolcengineClient } from './ve-client.js';
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
@@ -18,7 +19,7 @@ export class VolcengineAccess extends BaseAccess {
|
||||
component: {
|
||||
placeholder: 'AccessKeyID',
|
||||
},
|
||||
helper:"[获取密钥](https://console.volcengine.com/iam/keymanage/)",
|
||||
helper: "[获取密钥](https://console.volcengine.com/iam/keymanage/)",
|
||||
required: true,
|
||||
})
|
||||
accessKeyId = '';
|
||||
@@ -32,6 +33,50 @@ export class VolcengineAccess extends BaseAccess {
|
||||
})
|
||||
secretAccessKey = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCallerIdentity();
|
||||
return "ok";
|
||||
}
|
||||
|
||||
async getCallerIdentity() {
|
||||
const veClient = new VolcengineClient({
|
||||
access: this,
|
||||
logger: this.ctx.logger,
|
||||
http: this.ctx.http,
|
||||
});
|
||||
const service = await veClient.getStsService();
|
||||
|
||||
const res = await service.request({
|
||||
action: "GetCallerIdentity",
|
||||
});
|
||||
|
||||
const result = res.Result || {};
|
||||
this.ctx.logger.info("✅ 密钥有效!");
|
||||
this.ctx.logger.info(` 账户ID: ${result.AccountId}`);
|
||||
this.ctx.logger.info(` ARN: ${result.Trn}`);
|
||||
this.ctx.logger.info(` 用户ID: ${result.IdentityId}`);
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
accountId: result.AccountId,
|
||||
arn: result.Trn,
|
||||
userId: result.IdentityId
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
new VolcengineAccess();
|
||||
|
||||
@@ -113,6 +113,19 @@ export class VolcengineClient {
|
||||
return service;
|
||||
}
|
||||
|
||||
async getStsService() {
|
||||
const CommonService = await this.getServiceCls();
|
||||
|
||||
const service = new CommonService({
|
||||
serviceName: "sts",
|
||||
defaultVersion: "2018-01-01"
|
||||
});
|
||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||
service.setSecretKey(this.opts.access.secretAccessKey);
|
||||
service.setRegion("cn-north-1");
|
||||
return service;
|
||||
}
|
||||
|
||||
async getServiceCls() {
|
||||
if (this.CommonService) {
|
||||
return this.CommonService;
|
||||
|
||||
@@ -37,6 +37,25 @@ export class XinnetConnectAccess extends BaseAccess {
|
||||
})
|
||||
password = '';
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getDomainList({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
|
||||
async getDomainList(req: PageSearch): Promise<any> {
|
||||
let bodyXml =`
|
||||
|
||||
Reference in New Issue
Block a user