Compare commits

...

4 Commits

Author SHA1 Message Date
xiaojunnuo e913fe509c chore: 优化代码格式 2026-06-14 20:59:38 +08:00
xiaojunnuo a3a215b7ae perf(plugin): 增加 Dynadot DNS and access 插件
新增Dynadot域名解析提供商插件,包含API授权配置和DNS记录增删查功能
2026-06-14 17:23:24 +08:00
xiaojunnuo 56f2949ac5 chore: 1 2026-06-14 15:07:38 +08:00
xiaojunnuo c1b5a35f90 fix: 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug 2026-06-14 14:57:47 +08:00
40 changed files with 401 additions and 169 deletions
+2
View File
@@ -1,5 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.41.3",
"type": "module",
+2 -1
View File
@@ -12,7 +12,8 @@
"dev-build": "npm run build",
"preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish"
"pub": "npm publish",
"compile": "npm run build"
},
"dependencies": {
"axios": "^1.9.0",
+2 -1
View File
@@ -15,7 +15,8 @@
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish"
"pub": "npm publish",
"compile": "npm run build"
},
"dependencies": {
"nanoid": "^4.0.0"
+5 -1
View File
@@ -6,11 +6,15 @@
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
"scripts": {
"before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});fs.rmSync('.rollup.cache',{recursive:true,force:true});\"",
"build": "npm run before-build && rollup -c ",
"dev-build": "npm run build",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish"
"pub": "npm publish",
"compile": "npm run build"
},
"author": "",
"license": "Apache",
+2 -1
View File
@@ -16,7 +16,8 @@
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"cov": "midway-bin cov --ts",
"prepublish": "npm run build",
"pub": "npm publish"
"pub": "npm publish",
"compile": "npm run build"
},
"keywords": [],
"author": "greper",
@@ -11,9 +11,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "DnsPersistRecord",
@@ -24,10 +24,8 @@ const context: any = {
};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
// 页面打开后获取列表数据
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -19,13 +19,14 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
@@ -61,10 +62,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -21,13 +21,14 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
@@ -61,10 +62,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -149,7 +149,7 @@
</template>
<script lang="ts" setup>
import { computed, nextTick, onActivated, onMounted, reactive, ref } from "vue";
import { computed, nextTick, reactive, ref } from "vue";
import { FsIcon, useFs } from "@fast-crud/fast-crud";
import { notification } from "ant-design-vue";
import { useRouter } from "vue-router";
@@ -158,6 +158,7 @@ import createInviteesCrudOptions from "./crud-invitees";
import createLogsCrudOptions from "./crud-logs";
import { useSettingStore } from "/@/store/settings";
import { util } from "/@/utils";
import { useMounted } from "/@/use/use-mounted";
defineOptions({ name: "InviteCommission" });
@@ -314,16 +315,10 @@ async function refreshInvitePage(autoOpenAgreement = true) {
await refreshActiveList();
}
onMounted(async () => {
// 页面打开后获取列表数据
useMounted(async () => {
await refreshInvitePage(true);
});
onActivated(async () => {
if (!loaded.value) {
return;
}
await refreshInvitePage();
});
</script>
<style lang="less">
@@ -55,7 +55,7 @@
</template>
<script lang="ts" setup>
import { computed, onActivated, onMounted, provide, ref } from "vue";
import { computed, provide, ref } from "vue";
import { dict, useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import ChangeGroup from "./components/change-group.vue";
@@ -67,7 +67,7 @@ import BatchRerun from "./components/batch-rerun.vue";
import { Modal, notification } from "ant-design-vue";
import * as api from "./api";
import { useI18n } from "/src/locales";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue";
import { useSettingStore } from "/@/store/settings";
@@ -128,11 +128,7 @@ context.hasActionPermission = hasActionPermission;
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await groupDictRef.reloadDict();
await crudExpose.doRefresh();
});
@@ -26,13 +26,13 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
@@ -67,10 +67,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -27,7 +27,7 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted, Ref, ref } from "vue";
import { onMounted, ref } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
@@ -18,12 +18,12 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
@@ -52,10 +52,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -8,9 +8,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "MyTrade",
@@ -18,10 +18,7 @@ defineOptions({
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -27,7 +27,7 @@
</template>
<script lang="ts" setup>
import { computed, h, onActivated, onMounted, reactive, ref } from "vue";
import { computed, h, reactive, ref } from "vue";
import { compute, dict, useFs } from "@fast-crud/fast-crud";
import { Button, notification } from "ant-design-vue";
import * as api from "./api";
@@ -36,6 +36,7 @@ import createWithdrawCrudOptions from "./crud-withdraw";
import { util } from "/@/utils";
import { useFormDialog } from "/@/use/use-dialog";
import { useUserStore } from "/@/store/user";
import { useMounted } from "/@/use/use-mounted";
defineOptions({ name: "MyWallet" });
@@ -257,14 +258,8 @@ async function refreshWalletPage() {
loaded.value = true;
}
onMounted(refreshWalletPage);
onActivated(async () => {
if (!loaded.value) {
return;
}
await refreshWalletPage();
});
// 页面打开后获取列表数据
useMounted(refreshWalletPage);
</script>
<style lang="less">
@@ -14,27 +14,25 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted, ref } from "vue";
import { defineComponent, ref} from "vue";
import createCrudOptions from "./crud.js";
import FsPermissionTree from "./fs-permission-tree.vue";
import { usePermission } from "/src/plugin/permission";
import { useFs, useUi } from "@fast-crud/fast-crud";
import { useI18n } from "/src/locales";
import { useMounted } from "/@/use/use-mounted";
export default defineComponent({
name: "PermissionManager",
components: { FsPermissionTree },
setup() {
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
// 更多关于按钮权限的源代码设置,请参考 ./src/plugin/fast-crud/index.js 75-77行)
// 更多关于按钮权限的源代码的说明,请参考 ./src/plugin/fast-crud/index.js 75-77行)
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: "sys:auth:per" } });
const { t } = useI18n();
// 页面打开后获取列表数据
onMounted(async () => {
// await crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
@@ -22,12 +22,13 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
const { t } = useI18n();
@@ -55,10 +56,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -22,12 +22,13 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
const { t } = useI18n();
@@ -55,10 +56,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -19,12 +19,13 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useCrudPermission } from "/@/plugin/permission";
import { useRoute } from "vue-router";
const { t } = useI18n();
@@ -61,10 +62,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -17,14 +17,16 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import { useCrudPermission } from "/@/plugin/permission";
import AdminModeIntro from "./intro.vue";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
defineOptions({
@@ -52,10 +54,7 @@ const handleBatchDelete = () => {
};
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -17,12 +17,12 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import { useMounted } from "/@/use/use-mounted";
const { t } = useI18n();
@@ -31,15 +31,11 @@ defineOptions({
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
onActivated(async () => {
await crudExpose.doRefresh();
});
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: t("certd.confirm"),
title: t("certd.pluginManagement"),
content: t("certd.batchDeleteConfirm", { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
@@ -49,13 +45,13 @@ const handleBatchDelete = () => {
},
});
} else {
message.error(t("certd.pleaseSelectRecord"));
message.error(t("certd.selectRecordFirst"));
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -8,10 +8,10 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useSettingStore } from "/@/store/settings";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "SettingsHeaderMenus",
@@ -20,10 +20,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions
const settingStore = useSettingStore();
// 页面打开后获取列表数据
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -11,9 +11,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "SysProductActivationCode",
@@ -21,10 +21,8 @@ defineOptions({
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
onMounted(() => {
// crudExpose.doRefresh();
});
onActivated(async () => {
// 页面打开后获取列表数据
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -3,9 +3,10 @@
</template>
<script lang="ts" setup>
import { defineEmits, onActivated, onMounted, ref } from "vue";
import { ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "ProductManager",
@@ -16,10 +17,7 @@ const context: any = { emit };
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
+1 -1
View File
@@ -57,7 +57,6 @@
"@certd/acme-client": "^1.41.3",
"@certd/basic": "^1.41.3",
"@certd/commercial-core": "^1.41.3",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.41.3",
"@certd/lib-huawei": "^1.41.3",
"@certd/lib-k8s": "^1.41.3",
@@ -68,6 +67,7 @@
"@certd/plugin-lib": "^1.41.3",
"@certd/plugin-plus": "^1.41.3",
"@certd/plus-core": "^1.41.3",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@google-cloud/dns": "^5.3.1",
"@google-cloud/publicca": "^1.3.0",
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
@@ -134,7 +134,5 @@ export class MainConfiguration {
});
logger.info("当前环境:", this.app.getEnv()); // prod
}
}
@@ -98,17 +98,17 @@ export class LegacyAcmeAccountAccessFix {
continue;
}
const name = buildAcmeAccountAccessName(parsedKey.caType, parsedKey.email);
const query = {
userId: record.userId,
type: "acmeAccount",
subtype: parsedKey.caType,
name,
} as any
const query = {
userId: record.userId,
type: "acmeAccount",
subtype: parsedKey.caType,
name,
} as any;
if (record.projectId) {
query.projectId = record.projectId;
}
const exists = await this.accessService.findOne({
where:query,
where: query,
});
if (exists) {
continue;
@@ -192,10 +192,7 @@ describe("CertApplyTemplateService", () => {
await service.setDefault(2, 10, null);
assert.deepEqual((service as any).updateWhereList, [
{ userId: 10 },
{ userId: 10, id: 2 },
]);
assert.deepEqual((service as any).updateWhereList, [{ userId: 10 }, { userId: 10, id: 2 }]);
assert.equal(list[0].isDefault, false);
assert.equal(list[1].isDefault, true);
assert.equal(list[2].isDefault, false);
@@ -897,13 +897,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (param.projectId != null) {
query.projectId = param.projectId;
}
const statusCount = await this.repository
.createQueryBuilder()
.select("status")
.addSelect("count(1)", "count")
.where(query)
.groupBy("status")
.getRawMany();
const statusCount = await this.repository.createQueryBuilder().select("status").addSelect("count(1)", "count").where(query).groupBy("status").getRawMany();
return statusCount;
}
@@ -915,13 +909,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (param.projectId != null) {
query.projectId = param.projectId;
}
const statusCount = await this.repository
.createQueryBuilder()
.select("disabled")
.addSelect("count(1)", "count")
.where(query)
.groupBy("disabled")
.getRawMany();
const statusCount = await this.repository.createQueryBuilder().select("disabled").addSelect("count(1)", "count").where(query).groupBy("disabled").getRawMany();
const result = {
enabled: 0,
disabled: 0,
@@ -1115,14 +1103,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
},
});
for (const item of list) {
const pipeline = JSON.parse(item.content);
if (trigger.props === false) {
//清除trigger
pipeline.triggers = [];
} else {
const start = dayjs().format("YYYY-MM-DD") + " " + trigger.randomRange[0];
let end = dayjs().format("YYYY-MM-DD") + " " + trigger.randomRange[1];
if (trigger.randomRange[1] < trigger.randomRange[0]) {
@@ -1137,19 +1123,20 @@ export class PipelineService extends BaseService<PipelineEntity> {
//随机时间
const randomTime = Math.floor(Math.random() * (endTime - startTime)) + startTime;
const time = dayjs(randomTime).format(" ss:mm:HH").replaceAll(":", " ").replaceAll(" 0", " ").trim();
set(triggerConf, "props.cron", `${time} * * *`)
set(triggerConf, "props.cron", `${time} * * *`);
}
delete triggerConf.random
delete triggerConf.random;
delete triggerConf.randomRange;
pipeline.triggers = [{
id: nanoid(),
title: "定时触发",
...triggerConf
}];
pipeline.triggers = [
{
id: nanoid(),
title: "定时触发",
...triggerConf,
},
];
}
await this.doUpdatePipelineJson(item, pipeline);
}
}
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any, projectId?: number) {
@@ -48,3 +48,4 @@
// export * from './plugin-dnsmgr/index.js'
// export * from './plugin-nginx-proxy-manager/index.js'
// export * from './plugin-hipmdnsmgr/index.js'
// export * from './plugin-dynadot/index.js'
@@ -78,8 +78,8 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
action: DeployCertToAliyunApig.prototype.onGetDomainList.name,
watches: ["region", "accessId", "gatewayType"],
required: true,
pager:true,
search:true,
pager: true,
search: true,
})
)
domainList!: string[];
@@ -196,7 +196,7 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
throw new Error("请选择网关类型");
}
const pager = new Pager(data);
const pager = new Pager(data);
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint);
@@ -69,7 +69,7 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
action: DeployCertToAliyunApiGateway.prototype.onGetGroupList.name,
watches: ["regionEndpoint", "accessId"],
required: true,
single:true
single: true,
})
)
groupId!: string;
@@ -33,10 +33,7 @@ export class AwsClient {
// Split the full PEM chain: first block is the leaf cert, the rest is the intermediate chain
const pemBlocks = certInfo.crt.split(/(?<=-----END CERTIFICATE-----)/);
const cert = pemBlocks[0].trim();
const chain = pemBlocks
.slice(1)
.join("")
.trim();
const chain = pemBlocks.slice(1).join("").trim();
// 构建上传参数
const data = await acmClient.send(
@@ -144,10 +144,10 @@ export class AcmeAccountAccess extends BaseAccess {
action: "GenerateAccount",
buttonText: "生成ACME账号",
successMessage: "ACME账号已生成,请保存授权配置",
type:"textarea",
rows:4,
type: "textarea",
rows: 4,
},
col:{span:24},
col: { span: 24 },
required: true,
helper: "请生成ACME账号,账号一旦生成不允许修改",
encrypt: true,
@@ -170,7 +170,6 @@ export class AcmeAccountAccess extends BaseAccess {
required: false,
helper: "是否开启修改ACME账号,注意,开启后,会影响DNS持久验证记录",
encrypt: false,
})
editAccount = false;
@@ -0,0 +1,143 @@
import { AccessInput, BaseAccess, IsAccess, PageRes, PageSearch, Pager } from "@certd/pipeline";
import { DomainRecord } from "@certd/plugin-lib";
import { createHmac } from "crypto";
export type RequestOptions = {
method: "GET" | "POST" | "PUT" | "DELETE";
path: string;
params?: Record<string, any>;
data?: any;
};
@IsAccess({
name: "dynadot",
title: "Dynadot授权",
desc: "",
icon: "simple-icons:dynatrace",
})
export class DynadotAccess extends BaseAccess {
@AccessInput({
title: "API Key",
component: {
placeholder: "api key",
},
helper: "前往 [Dynadot API设置](https://www.dynadot.cn/zh/account/domain/setting/api.html) 获取API Key",
required: true,
encrypt: true,
})
apiKey = "";
@AccessInput({
title: "API Secret",
component: {
name: "a-input-password",
vModel: "value",
placeholder: "api secret",
},
helper: "前往 [Dynadot API设置](https://www.dynadot.cn/zh/account/domain/setting/api.html) 获取API Secret",
required: true,
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 params: Record<string, any> = {
page: pager.pageNo,
page_size: pager.pageSize,
};
if (req.searchKey) {
params.search = req.searchKey;
}
const res = await this.doRequest({
method: "GET",
path: "/restful/v2/domains",
params,
});
const domainList = res.data?.domain_info_list || [];
const list = domainList.map((item: any) => ({
id: item.domain_name,
domain: item.domain_name,
}));
return {
total: list.length,
list,
};
}
async doRequest(opts: RequestOptions): Promise<any> {
const { method, path, params, data } = opts;
const queryString = params ? "?" + new URLSearchParams(params).toString() : "";
const fullPath = path + queryString;
const body = data ? JSON.stringify(data) : "";
const headers: Record<string, string> = {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${this.apiKey}`,
"X-Signature": this.generateSignature(fullPath, body),
};
try {
const res = await this.ctx.http.request<any, any>({
url: fullPath,
baseURL: "https://api.dynadot.com",
method,
data: body || undefined,
headers,
});
this.checkApiError(res);
return res;
} catch (e: any) {
if (e.response?.data) {
const errData = e.response.data;
this.ctx.logger.error("Dynadot API返回错误:", JSON.stringify(errData));
throw new Error(`Dynadot API错误: ${errData.message || JSON.stringify(errData)}`);
}
throw e;
}
}
private generateSignature(fullPathAndQuery: string, body: string): string {
const stringToSign = this.apiKey + "\n" + fullPathAndQuery + "\n\n" + body;
const hmac = createHmac("sha256", this.apiSecret);
hmac.update(stringToSign, "utf8");
return hmac.digest("base64");
}
private checkApiError(res: any) {
if (!res || typeof res !== "object") {
return;
}
const code = res.code;
if (code !== undefined && code !== null && code !== 200) {
const errorMsg = res.message || JSON.stringify(res);
throw new Error(`Dynadot API错误: ${errorMsg}`);
}
}
}
new DynadotAccess();
@@ -0,0 +1,149 @@
import { PageRes, PageSearch } from "@certd/pipeline";
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { DynadotAccess } from "./access.js";
export type DynadotRecord = {
sub_host: string;
record_type: string;
record_value1: string;
};
type SubRecordItem = DynadotRecord & { record_value2: string };
type MainRecordItem = { record_type: string; record_value1: string; record_value2: string };
@IsDnsProvider({
name: "dynadot",
title: "Dynadot",
desc: "Dynadot DNS提供商",
icon: "simple-icons:dynatrace",
accessType: "dynadot",
})
export class DynadotDnsProvider extends AbstractDnsProvider<DynadotRecord> {
access!: DynadotAccess;
async onInstance() {
this.access = this.ctx.access as DynadotAccess;
}
async createRecord(options: CreateRecordOptions): Promise<DynadotRecord> {
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
try {
const subRecords = [
{
sub_host: hostRecord,
record_type: type.toLowerCase(),
record_value1: value,
record_value2: "",
}
]
await this.postRecords(domain, {subRecords, mainRecords: [], addToCurrent: true});
this.logger.info("添加域名解析成功:", fullRecord, value);
return {
sub_host: hostRecord,
record_type: type.toLowerCase(),
record_value1: value,
};
} catch (error) {
this.logger.error("创建DNS记录失败:", error);
throw new Error(`创建DNS记录失败: ${(error as Error).message}`);
}
}
async removeRecord(options: RemoveRecordOptions<DynadotRecord>): Promise<void> {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
this.logger.info("删除域名解析:", fullRecord, value);
if (!record || !record.sub_host) {
this.logger.info("record为空,不执行删除");
return;
}
const existingRecords = await this.getDnsRecords(domain);
const beforeCount = existingRecords.subRecords.length;
existingRecords.subRecords = existingRecords.subRecords.filter(item => !(item.sub_host === record.sub_host && item.record_type === record.record_type && item.record_value1 === record.record_value1));
if (beforeCount === existingRecords.subRecords.length) {
this.logger.info("未找到要删除的DNS记录,可能已被移除或不存在:", fullRecord);
return;
}
if (existingRecords.mainRecords.length == 0) {
existingRecords.mainRecords = [
{
record_type: "txt",
record_value1: "init_txt_by_certd",
record_value2: "",
}
]
}
await this.postRecords(domain, {
...existingRecords,
addToCurrent: false,
});
this.logger.info("删除域名解析成功:", fullRecord, value);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
return await this.access.getDomainListPage(req);
}
private async getDnsRecords(domain: string): Promise<{
mainRecords: MainRecordItem[];
subRecords: SubRecordItem[];
}> {
let res: any;
try {
res = await this.access.doRequest({
method: "GET",
path: `/restful/v2/domains/${domain}/records`,
});
} catch (e: any) {
this.logger.info("获取DNS记录失败,域名可能尚未配置DNS记录,将视为空记录:");
return { mainRecords: [], subRecords: [] };
}
const glueInfo = res.data?.glue_info || {};
const subRecords: SubRecordItem[] = (glueInfo.dns_sub_list || [])
.filter((item: any) => item.sub_host && item.record_value1)
.map((item: any) => ({
sub_host: item.sub_host || "",
record_type: item.record_type || "",
record_value1: item.record_value1 || "",
record_value2: item.record_value2 || "",
}));
const mainRecords: MainRecordItem[] = (glueInfo.dns_main_list || [])
.filter((item: any) => item.record_value1)
.map((item: any) => ({
record_type: item.record_type || "",
record_value1: String(item.record_value1),
record_value2: String(item.record_value2 || ""),
}));
return { mainRecords, subRecords };
}
private async postRecords(domain: string, records: { mainRecords: MainRecordItem[]; subRecords: SubRecordItem[] ,addToCurrent: boolean}): Promise<void> {
await this.access.doRequest({
method: "POST",
path: `/restful/v2/domains/${domain}/records`,
data: {
dns_main_list: records.mainRecords,
dns_sub_list: records.subRecords,
ttl: 300,
add_dns_to_current_setting: records.addToCurrent || false,
},
});
}
}
new DynadotDnsProvider();
@@ -0,0 +1,2 @@
export * from "./dns-provider.js";
export * from "./access.js";
@@ -53,12 +53,12 @@ export class HipmDnsmgrAccess extends BaseAccess {
*/
async getDomainId(domainName: string): Promise<string> {
this.ctx.logger.info(`[HiPM DNSMgr] 尝试通过keyword查询域名: ${domainName}`);
// 方案1: 使用 keyword 参数直接查询
try {
const resp = await this.doRequest({
method: 'GET',
path: '/domains',
method: "GET",
path: "/domains",
params: {
page: 1,
pageSize: 1,
@@ -80,7 +80,7 @@ export class HipmDnsmgrAccess extends BaseAccess {
// 方案2: 如果 keyword 查询未找到,使用列表匹配作为冗余
this.ctx.logger.info(`[HiPM DNSMgr] keyword查询未找到,尝试列表匹配: ${domainName}`);
const domainList = await this.getDomainList();
const domainInfo = domainList.find((item: any) => item.domain === domainName);
@@ -29,7 +29,7 @@ export class HipmDnsmgrDnsProvider extends AbstractDnsProvider<{ domainId: strin
// 1. 获取域名 ID(双层查询策略)
const domainId = await this.access.getDomainId(domain);
this.logger.debug('[HiPM DNSMgr] 找到域名:', domain, 'ID:', domainId);
this.logger.debug("[HiPM DNSMgr] 找到域名:", domain, "ID:", domainId);
// 2. 创建 DNS 记录
const name = hostRecord; // 使用子域名,如 _acme-challenge
@@ -21,7 +21,7 @@ const regionOptions = [
icon: "svg:icon-volcengine",
group: pluginGroups.volcengine.key,
desc: "替换火山引擎VKE集群中的TLS Secret证书",
needPlus:true,
needPlus: true,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -495,7 +495,6 @@ export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin {
await this.deleteKubeconfig(vkeService, kubeconfigId);
}
}
}
new VolcengineDeployToVKE();
new VolcengineDeployToVKE();