mirror of
https://github.com/certd/certd.git
synced 2026-04-18 00:20:56 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f075a991f0 | ||
|
|
edeb817c39 | ||
|
|
23b4658672 | ||
|
|
5f95ee987f | ||
|
|
cc73f156a7 | ||
|
|
ee72d10718 | ||
|
|
831871d37f | ||
|
|
6072550ec1 |
@@ -145,6 +145,20 @@ async doRequest(req: { action: string, data?: any }) {
|
||||
utils: typeof utils;
|
||||
accessService: IAccessService;
|
||||
}
|
||||
|
||||
// this.ctx.http 只有request方法
|
||||
// 方法参数
|
||||
export type HttpRequestConfig<D = any> = {
|
||||
skipSslVerify?: boolean;
|
||||
skipCheckRes?: boolean;
|
||||
logParams?: boolean;
|
||||
logRes?: boolean;
|
||||
logData?: boolean;
|
||||
httpProxy?: string;
|
||||
returnOriginRes?: boolean;
|
||||
} & AxiosRequestConfig<D>;
|
||||
|
||||
|
||||
*/
|
||||
const res = await this.ctx.http.request({
|
||||
url: "https://api.demo.cn/api/",
|
||||
|
||||
@@ -105,6 +105,28 @@ async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 实现 getDomainListPage 方法
|
||||
```typescript
|
||||
/**
|
||||
* 实现获取域名列表
|
||||
*/
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const res = await this.http.request({
|
||||
// 请求接口获取域名列表
|
||||
})
|
||||
const list = res.Domains?.map(item => ({
|
||||
id: item.Id,
|
||||
domain: item.DomainName,
|
||||
})) || []
|
||||
|
||||
return {
|
||||
list,
|
||||
total: res.Total,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 实例化插件
|
||||
|
||||
```typescript
|
||||
@@ -204,11 +226,28 @@ export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
|
||||
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现获取域名列表
|
||||
*/
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const pager = new Pager(req);
|
||||
const res = await this.http.request({
|
||||
// 请求接口获取域名列表
|
||||
})
|
||||
const list = res.Domains?.map(item => ({
|
||||
id: item.Id,
|
||||
domain: item.DomainName,
|
||||
})) || []
|
||||
|
||||
return {
|
||||
list,
|
||||
total: res.Total,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实例化这个 provider,将其自动注册到系统中
|
||||
if (isDev()) {
|
||||
// 你的实现 要去掉这个 if,不然生产环境将不会显示
|
||||
new DemoDnsProvider();
|
||||
}
|
||||
new DemoDnsProvider();
|
||||
|
||||
```
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复创建流水线无法选择通知的bug ([a88d0a6](https://github.com/certd/certd/commit/a88d0a6ae15cb6170d0b36e21daf89f0dbd5f681))
|
||||
* 修复流水线任务编辑页面复制粘贴按钮在夜间模式显示问题 ([1e549df](https://github.com/certd/certd/commit/1e549dfd431ed74e2bcdfce63e5f640c51603af3))
|
||||
* 修复用户管理添加用户无法上传头像的bug ([557e98c](https://github.com/certd/certd/commit/557e98c33f5462167d8f6289f70dad68bb114a97))
|
||||
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
|
||||
* 修复spaceship创建record报错的bug ([70b46d4](https://github.com/certd/certd/commit/70b46d4a8f89cf8eded21ebb237e8c8ce6c40d30))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
|
||||
* 部署到1panel面板支持mux模式 ([d05129e](https://github.com/certd/certd/commit/d05129ec67893b0b639003a4bca6878d128f56ad))
|
||||
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
|
||||
* 修复检查全部某些情况下无效的bug,优化公共触发站点证书检查定时逻辑 ([ee53589](https://github.com/certd/certd/commit/ee535895a3166c6f9046963e28fa8f22f018b574))
|
||||
* 增加域名管理 子域名检查提醒 ([2bdf183](https://github.com/certd/certd/commit/2bdf1832da73a3728f3ac415837bc26e70531cd6))
|
||||
* 站点监控域名气泡增加端口显示 ([6ee718a](https://github.com/certd/certd/commit/6ee718a25265a9db2115343af9a1a01958f34b81))
|
||||
|
||||
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -70,5 +70,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ const defaultOpts = {
|
||||
},
|
||||
challengeRemoveFn: async () => {
|
||||
throw new Error("Missing challengeRemoveFn()");
|
||||
}
|
||||
},
|
||||
waitDnsDiffuseTime: 30,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -577,7 +577,7 @@ class AcmeClient {
|
||||
|
||||
const verifyFn = async (abort) => {
|
||||
if (this.opts.signal && this.opts.signal.aborted) {
|
||||
abort();
|
||||
abort(true);
|
||||
throw new CancelError('用户取消');
|
||||
}
|
||||
|
||||
|
||||
@@ -50,15 +50,18 @@ class Backoff {
|
||||
|
||||
async function retryPromise(fn, attempts, backoff, logger = log) {
|
||||
let aborted = false;
|
||||
let abortedFromUser = false;
|
||||
|
||||
try {
|
||||
const setAbort = () => { aborted = true; }
|
||||
const setAbort = (fromUser = false) => { aborted = true; abortedFromUser = fromUser; }
|
||||
const data = await fn(setAbort);
|
||||
return data;
|
||||
}
|
||||
catch (e) {
|
||||
if (aborted){
|
||||
logger(`用户取消重试`);
|
||||
if (abortedFromUser){
|
||||
logger(`用户取消重试`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if ( ((backoff.attempts + 1) >= attempts)) {
|
||||
|
||||
1
packages/core/acme-client/types/index.d.ts
vendored
1
packages/core/acme-client/types/index.d.ts
vendored
@@ -68,6 +68,7 @@ export interface ClientAutoOptions {
|
||||
preferredChain?: string;
|
||||
signal?: AbortSignal;
|
||||
profile?:string;
|
||||
waitDnsDiffuseTime?: number;
|
||||
}
|
||||
|
||||
export class Client {
|
||||
|
||||
@@ -47,5 +47,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -56,5 +56,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -64,5 +64,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1c634a702af9298d25542acc270d68f71d9b1049"
|
||||
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-select :value="value" @update:value="onChange">
|
||||
<a-select :value="value" :filter-option="true" @update:value="onChange">
|
||||
<a-select-option v-for="item of options" :key="item.value" :value="item.value" :label="item.label">
|
||||
<span class="flex-o">
|
||||
<fs-icon :icon="item.icon" class="fs-16 color-blue mr-5" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<icon-select class="dns-provider-selector" :value="modelValue" :options="options" @update:value="atChange"> </icon-select>
|
||||
<icon-select class="dns-provider-selector" :value="modelValue" :options="options" :filter-option="true" @update:value="atChange"> </icon-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { onActivated, onMounted, ref, Ref } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { siteIpApi } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "SiteIpCertMonitor",
|
||||
@@ -23,11 +22,6 @@ const { crudBinding, crudRef, crudExpose } = useFs({
|
||||
},
|
||||
});
|
||||
|
||||
const siteInfoRef: Ref<any> = ref({});
|
||||
onMounted(async () => {
|
||||
siteInfoRef.value = await siteIpApi.GetObj(props.siteId);
|
||||
});
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
|
||||
@@ -32,6 +32,27 @@ export class ApisixAccess extends BaseAccess {
|
||||
})
|
||||
apiKey = '';
|
||||
|
||||
@AccessInput({
|
||||
title: '版本',
|
||||
component: {
|
||||
name:"a-select",
|
||||
options: [
|
||||
{
|
||||
label: "v3.x",
|
||||
value: "3",
|
||||
},
|
||||
{
|
||||
label: "v2.x",
|
||||
value: "2",
|
||||
},
|
||||
]
|
||||
},
|
||||
helper: "apisix系统的版本",
|
||||
value:"3",
|
||||
required: true,
|
||||
})
|
||||
version = '3';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
@@ -49,17 +70,24 @@ export class ApisixAccess extends BaseAccess {
|
||||
}
|
||||
|
||||
async getCertList(){
|
||||
const sslPath = this.getSslPath();
|
||||
const req = {
|
||||
url :"/apisix/admin/ssls",
|
||||
url :`/apisix/admin/${sslPath}`,
|
||||
method: "get",
|
||||
}
|
||||
return await this.doRequest(req);
|
||||
}
|
||||
|
||||
getSslPath(){
|
||||
const sslPath = this.version === '3' ? 'ssls' : 'ssl';
|
||||
return sslPath;
|
||||
}
|
||||
|
||||
async createCert(opts:{cert:CertInfo}){
|
||||
const certReader = new CertReader(opts.cert)
|
||||
const sslPath = this.getSslPath();
|
||||
const req = {
|
||||
url :"/apisix/admin/ssls",
|
||||
url :`/apisix/admin/${sslPath}`,
|
||||
method: "post",
|
||||
data:{
|
||||
cert: opts.cert.crt,
|
||||
@@ -72,8 +100,9 @@ export class ApisixAccess extends BaseAccess {
|
||||
|
||||
async updateCert (opts:{cert:CertInfo,id:string}){
|
||||
const certReader = new CertReader(opts.cert)
|
||||
const sslPath = this.getSslPath();
|
||||
const req = {
|
||||
url :`/apisix/admin/ssls/${opts.id}`,
|
||||
url :`/apisix/admin/${sslPath}/${opts.id}`,
|
||||
method: "put",
|
||||
data:{
|
||||
cert: opts.cert.crt,
|
||||
|
||||
@@ -423,6 +423,7 @@ export class AcmeService {
|
||||
signal: this.options.signal,
|
||||
profile,
|
||||
preferredChain,
|
||||
waitDnsDiffuseTime: this.options.waitDnsDiffuseTime,
|
||||
});
|
||||
|
||||
const crtString = crt.toString();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
|
||||
import { DemoAccess } from './access.js';
|
||||
import { PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { isDev } from '../../utils/env.js';
|
||||
import { DemoAccess } from './access.js';
|
||||
|
||||
type DemoRecord = {
|
||||
// 这里定义Record记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到id就行,用于删除txt解析记录,清理申请痕迹
|
||||
@@ -16,7 +17,7 @@ type DemoRecord = {
|
||||
icon: 'clarity:plugin-line',
|
||||
// 这里是对应的云平台的access类型名称
|
||||
accessType: 'demo',
|
||||
order:99,
|
||||
order: 99,
|
||||
})
|
||||
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
|
||||
access!: DemoAccess;
|
||||
@@ -74,6 +75,28 @@ export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
|
||||
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param req 实现获取域名列表
|
||||
* @returns
|
||||
*/
|
||||
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
const res = await this.http.request({
|
||||
// 请求接口获取域名列表
|
||||
})
|
||||
const list = []
|
||||
// const list = res.Domains?.map(item => ({
|
||||
// id: item.Id,
|
||||
// domain: item.DomainName,
|
||||
// })) || []
|
||||
|
||||
|
||||
return {
|
||||
list,
|
||||
total: res.Total,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO 实例化这个provider,将其自动注册到系统中
|
||||
|
||||
@@ -82,7 +82,7 @@ export class SshAccess extends BaseAccess {
|
||||
|
||||
@AccessInput({
|
||||
title: "伪终端",
|
||||
helper: "如果登录报错:all authentication methods failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响",
|
||||
helper: "如果登录报错:all authentication methods failed / unable to exec,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响",
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
|
||||
@@ -208,7 +208,7 @@ export class AsyncSsh2Client {
|
||||
let hasErrorLog = false;
|
||||
stream
|
||||
.on("close", (code: any, signal: any) => {
|
||||
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
||||
this.logger.info(`[${this.connConf.host}][close]:code=${code}`);
|
||||
/**
|
||||
* ]pipeline 执行命令:[10.123.0.2][exec]:cd /d D:\nginx-1.27.5 && D:\nginx-1.27.5\nginx.exe -t && D:\nginx-1.27.5\nginx.exe -s reload
|
||||
* [2025-07-09T10:24:11.219] [ERROR]pipeline - [10. 123.0. 2][error]: nginx: the configuration file D: \nginx-1.27. 5/conf/nginx. conf syntax is ok
|
||||
@@ -279,7 +279,7 @@ export class AsyncSsh2Client {
|
||||
}
|
||||
stream
|
||||
.on("close", (code: any) => {
|
||||
this.logger.info("Stream :: close,code: " + code);
|
||||
this.logger.info("Stream :: close,code = " + code);
|
||||
resolve(output);
|
||||
})
|
||||
.on("data", (ret: Buffer) => {
|
||||
|
||||
7493
packages/ui/certd-server/src/plugins/plugin-technitium/APIDOCS.md
Normal file
7493
packages/ui/certd-server/src/plugins/plugin-technitium/APIDOCS.md
Normal file
File diff suppressed because it is too large
Load Diff
179
packages/ui/certd-server/src/plugins/plugin-technitium/access.ts
Normal file
179
packages/ui/certd-server/src/plugins/plugin-technitium/access.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||
import { DomainRecord } from '@certd/plugin-lib';
|
||||
|
||||
/**
|
||||
* Technitium DNS Server 授权配置
|
||||
*/
|
||||
@IsAccess({
|
||||
name: 'technitium',
|
||||
title: 'Technitium DNS Server',
|
||||
icon: 'clarity:server-line',
|
||||
desc: 'Technitium DNS Server 自建DNS服务器授权',
|
||||
})
|
||||
export class TechnitiumAccess extends BaseAccess {
|
||||
|
||||
/**
|
||||
* API地址
|
||||
*/
|
||||
@AccessInput({
|
||||
title: 'API地址',
|
||||
value: 'http://localhost:5380',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: 'http://localhost:5380',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
apiUrl = 'http://localhost:5380';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@AccessInput({
|
||||
title: '用户名',
|
||||
component: {
|
||||
name: "a-input",
|
||||
allowClear: true,
|
||||
placeholder: 'admin',
|
||||
},
|
||||
required: false,
|
||||
})
|
||||
username = 'admin';
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@AccessInput({
|
||||
title: '密码',
|
||||
component: {
|
||||
name: "a-input",
|
||||
type: "password",
|
||||
allowClear: true,
|
||||
placeholder: '密码',
|
||||
},
|
||||
required: false,
|
||||
encrypt: true,
|
||||
})
|
||||
password = '';
|
||||
|
||||
/**
|
||||
* 测试按钮
|
||||
*/
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
token = '';
|
||||
|
||||
/**
|
||||
* 通用API调用方法
|
||||
*/
|
||||
async doRequest(options: { url: string; method: 'get' | 'post'; params?: URLSearchParams }) {
|
||||
// 每次请求前都获取最新的token
|
||||
if (!options.url.includes('/api/user/login')) {
|
||||
await this.getToken();
|
||||
}
|
||||
|
||||
// 复制参数并添加token
|
||||
const params = new URLSearchParams(options.params || '');
|
||||
if (this.token && !options.url.includes('/api/user/login')) {
|
||||
params.append('token', this.token);
|
||||
}
|
||||
|
||||
let fullUrl = options.url;
|
||||
if (params.toString()) {
|
||||
fullUrl = `${options.url}?${params.toString()}`;
|
||||
}
|
||||
|
||||
const response = await this.ctx.http.request({
|
||||
url: fullUrl,
|
||||
method: options.method,
|
||||
});
|
||||
|
||||
if (response.status !== 'ok') {
|
||||
throw new Error(`${response.errorMessage || 'API调用失败'}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试API连接
|
||||
*/
|
||||
async onTestRequest() {
|
||||
// 测试获取区域列表
|
||||
await this.GetDomainList({});
|
||||
return "连接成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取域名列表
|
||||
*/
|
||||
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
|
||||
const pager = new Pager(req);
|
||||
|
||||
// 构建API URL
|
||||
const apiUrl = `${this.apiUrl}/api/zones/list`;
|
||||
|
||||
// 构建查询参数
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// 调用API获取区域列表
|
||||
const response = await this.doRequest({ url: apiUrl, method: 'get', params: params });
|
||||
|
||||
const zones = response.response.zones || [];
|
||||
const total = zones.length;
|
||||
|
||||
// 转换为DomainRecord格式
|
||||
let list = zones.map((zone: any) => ({
|
||||
id: zone.name,
|
||||
domain: zone.name,
|
||||
}));
|
||||
|
||||
// 应用分页
|
||||
list = list.slice(pager.getOffset(), pager.getOffset() + pager.pageSize);
|
||||
|
||||
return {
|
||||
total,
|
||||
list
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API Token
|
||||
*/
|
||||
async getToken() {
|
||||
const apiUrl = `${this.apiUrl}/api/user/login`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
user: this.username,
|
||||
pass: this.password,
|
||||
});
|
||||
|
||||
// 直接使用ctx.http.request,避免递归调用doRequest
|
||||
const response = await this.ctx.http.request({
|
||||
url: `${apiUrl}?${params.toString()}`,
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
if (response.status !== 'ok') {
|
||||
throw new Error(`登录失败: ${response.errorMessage || '未知错误'}`);
|
||||
}
|
||||
|
||||
this.token = response.token;
|
||||
return this.token;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
|
||||
import { TechnitiumAccess } from "./access.js"
|
||||
|
||||
type TechnitiumRecord = {
|
||||
// 记录创建时返回的数据结构
|
||||
zone: {
|
||||
name: string;
|
||||
type: string;
|
||||
internal: boolean;
|
||||
dnssecStatus: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
addedRecord: {
|
||||
disabled: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
ttl: number;
|
||||
rData: {
|
||||
text: string;
|
||||
};
|
||||
dnssecStatus: string;
|
||||
lastUsedOn: string;
|
||||
};
|
||||
};
|
||||
|
||||
// 注册Technitium DNS Server的DNS提供商
|
||||
@IsDnsProvider({
|
||||
name: 'technitium',
|
||||
title: 'Technitium DNS Server',
|
||||
desc: 'Technitium DNS Server 自建DNS服务器',
|
||||
icon: 'clarity:server-line',
|
||||
accessType: 'technitium',
|
||||
order: 10,
|
||||
})
|
||||
export class TechnitiumDnsProvider extends AbstractDnsProvider<TechnitiumRecord> {
|
||||
access!: TechnitiumAccess;
|
||||
|
||||
async onInstance() {
|
||||
this.access = this.ctx.access as TechnitiumAccess;
|
||||
this.logger.debug('access', this.access);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建DNS解析记录,用于验证域名所有权
|
||||
*/
|
||||
async createRecord(options: CreateRecordOptions): Promise<TechnitiumRecord> {
|
||||
const { fullRecord, value, type, domain } = options;
|
||||
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
|
||||
|
||||
// 构建API URL
|
||||
const apiUrl = `${this.access.apiUrl}/api/zones/records/add`;
|
||||
|
||||
// 构建查询参数
|
||||
const params = new URLSearchParams({
|
||||
zone: domain,
|
||||
domain: fullRecord,
|
||||
type: type,
|
||||
text: value,
|
||||
ttl: "60",
|
||||
});
|
||||
|
||||
// 调用Technitium API创建TXT记录
|
||||
const response = await this.access.doRequest({ url: apiUrl, method: 'post', params: params });
|
||||
|
||||
this.logger.info('创建域名解析成功:', fullRecord, value);
|
||||
return response as TechnitiumRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除DNS解析记录,清理申请痕迹
|
||||
*/
|
||||
async removeRecord(options: RemoveRecordOptions<TechnitiumRecord>): Promise<void> {
|
||||
const { fullRecord, value, domain } = options.recordReq;
|
||||
const record = options.recordRes;
|
||||
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
|
||||
|
||||
// 构建API URL
|
||||
const apiUrl = `${this.access.apiUrl}/api/zones/records/delete`;
|
||||
|
||||
// 构建查询参数
|
||||
const params = new URLSearchParams({
|
||||
zone: domain,
|
||||
domain: fullRecord,
|
||||
type: 'TXT',
|
||||
text: value,
|
||||
});
|
||||
|
||||
// 调用Technitium API删除TXT记录
|
||||
await this.access.doRequest({ url: apiUrl, method: 'post', params: params });
|
||||
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 实例化这个provider,将其自动注册到系统中
|
||||
new TechnitiumDnsProvider();
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dns-provider.js';
|
||||
export * from './access.js';
|
||||
@@ -1 +1 @@
|
||||
01:26
|
||||
23:47
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:53
|
||||
00:29
|
||||
|
||||
Reference in New Issue
Block a user