Merge branch 'v2-dev' into v2_admin_mode

This commit is contained in:
xiaojunnuo
2026-02-16 00:48:23 +08:00
172 changed files with 3500 additions and 981 deletions
+19
View File
@@ -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.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Bug Fixes
* 修复1panel 请求失败的bug ([0283662](https://github.com/certd/certd/commit/0283662931ff47d6b5d49f91a30c4a002fe1d108))
* 修复保存站点监控dns设置,偶尔无法保存成功的bug ([8387fe0](https://github.com/certd/certd/commit/8387fe0d5b2e77b8c2788a10791e5389d97a3e41))
* 修复任务步骤标题过长导致错位的问题 ([9fb9805](https://github.com/certd/certd/commit/9fb980599f96ccbf61bd46019411db2f13c70e57))
### Performance Improvements
* 监控设置支持逗号分割 ([c23d1d1](https://github.com/certd/certd/commit/c23d1d11b58a6cdfe431a7e8abbd5d955146c38d))
* 列表中支持下次执行时间显示 ([a3cabd5](https://github.com/certd/certd/commit/a3cabd5f36ed41225ad418098596e9b2c44e31a1))
* 模版编辑页面,hover反色过亮问题优化 ([e55a3a8](https://github.com/certd/certd/commit/e55a3a82fc6939b940f0c3be4529d74a625f6f4e))
* 所有授权增加测试按钮 ([7a3e68d](https://github.com/certd/certd/commit/7a3e68d656c1dcdcd814b69891bd2c2c6fe3098a))
* 优化网络测试页面,夜间模式显示效果 ([305da86](https://github.com/certd/certd/commit/305da86f97d918374819ecd6c50685f09b94ea59))
* 支持next-terminal ([6f3fd78](https://github.com/certd/certd/commit/6f3fd785e77a33c72bdf4115dc5d498e677d1863))
* 主题默认跟随系统颜色(自动切换深色浅色模式) ([32c3ce5](https://github.com/certd/certd/commit/32c3ce5c9868569523901a9a939ca5b535ec3277))
* http校验方式支持scp上传 ([4eb940f](https://github.com/certd/certd/commit/4eb940ffe765a0330331bc6af8396315e36d4e4a))
## [1.38.9](https://github.com/certd/certd/compare/v1.38.8...v1.38.9) (2026-02-09)
### Performance Improvements
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.38.9",
"version": "1.38.10",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.38.9",
"@certd/pipeline": "^1.38.9",
"@certd/lib-iframe": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -13,6 +13,7 @@
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { ref, inject } from "vue";
import { Form } from "ant-design-vue";
import { getInputFromForm } from "./utils";
defineOptions({
name: "ApiTest",
@@ -45,13 +46,15 @@ const doTest = async () => {
message.value = "";
hasError.value = false;
loading.value = true;
const { input, record } = getInputFromForm(form, pluginType);
try {
const res = await doRequest(
{
type: pluginType,
typeName: form.type,
action: props.action,
input: pluginType === "plugin" ? form.input : form,
input,
record,
},
{
onError(err: any) {
@@ -16,6 +16,7 @@
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
defineOptions({
name: "RemoteAutoComplete",
@@ -48,18 +49,6 @@ const message = ref("");
const hasError = ref(false);
const loading = ref(false);
function getInputFromForm(form: any, pluginType: string) {
let input: any = {};
if (pluginType === "plugin") {
input = form?.input || {};
} else if (pluginType === "access") {
input = form?.access || {};
} else {
input = form || {};
}
return input;
}
const getOptions = async () => {
if (loading.value) {
return;
@@ -75,7 +64,7 @@ const getOptions = async () => {
}
const pluginType = getPluginType();
const { form } = getScope();
const input = getInputFromForm(form, pluginType);
const { input, record } = getInputFromForm(form, pluginType);
for (let key in define.input) {
const inWatches = props.watches?.includes(key);
const inputDefine = define.input[key];
@@ -99,6 +88,7 @@ const getOptions = async () => {
action: props.action,
input,
data: {},
record,
},
{
onError(err: any) {
@@ -140,7 +130,7 @@ watch(
() => {
const pluginType = getPluginType();
const { form, key } = getScope();
const input = getInputFromForm(form, pluginType);
const { input, record } = getInputFromForm(form, pluginType);
const watches: any = {};
if (props.watches && props.watches.length > 0) {
for (const key of props.watches) {
@@ -9,6 +9,7 @@ import { doRequest } from "/@/components/plugins/lib";
import { inject, ref, useAttrs } from "vue";
import { useFormWrapper } from "@fast-crud/fast-crud";
import { notification } from "ant-design-vue";
import { getInputFromForm } from "./utils";
defineOptions({
name: "RemoteInput",
@@ -71,15 +72,18 @@ const doPluginFormSubmit = async (data: any) => {
}
loading.value = true;
try {
const pluginType = getPluginType();
const { form } = getScope();
const { input, record } = getInputFromForm(form, pluginType);
const res = await doRequest({
type: pluginType,
typeName: form.type,
action: props.action,
input: pluginType === "plugin" ? form.input : form,
input,
data: data,
record,
});
//获取返回值 填入到input中
emit("update:modelValue", res);
@@ -38,6 +38,7 @@
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
defineOptions({
name: "RemoteSelect",
@@ -79,17 +80,6 @@ const getPluginType: any = inject("get:plugin:type", () => {
return "plugin";
});
function getInputFromForm(form: any, pluginType: string) {
let input: any = {};
if (pluginType === "plugin") {
input = form?.input || {};
} else if (pluginType === "access") {
input = form?.access || {};
} else {
input = form || {};
}
return input;
}
const searchKeyRef = ref("");
const optionsRef = ref([]);
const message = ref("");
@@ -115,7 +105,7 @@ const getOptions = async () => {
}
const pluginType = getPluginType();
const { form } = getScope();
const input = getInputFromForm(form, pluginType);
const { input, record } = getInputFromForm(form, pluginType);
for (let key in define.input) {
const inWatches = props.watches?.includes(key);
@@ -141,6 +131,7 @@ const getOptions = async () => {
typeName: form.type,
action: props.action,
input,
record,
data: {
searchKey: props.search ? searchKeyRef.value : "",
pageNo,
@@ -211,7 +202,7 @@ watch(
() => {
const pluginType = getPluginType();
const { form, key } = getScope();
const input = getInputFromForm(form, pluginType);
const { input, record } = getInputFromForm(form, pluginType);
const watches: any = {};
if (props.watches && props.watches.length > 0) {
for (const key of props.watches) {
@@ -15,6 +15,7 @@
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
defineOptions({
name: "RemoteTreeSelect",
@@ -67,7 +68,7 @@ const getOptions = async () => {
}
const pluginType = getPluginType();
const { form } = getScope();
const input = (pluginType === "plugin" ? form?.input : form) || {};
const { input, record } = getInputFromForm(form, pluginType);
for (let key in define.input) {
const inWatches = props.watches?.includes(key);
@@ -98,6 +99,7 @@ const getOptions = async () => {
pageNo,
pageSize,
},
record,
},
{
onError(err: any) {
@@ -0,0 +1,26 @@
import { cloneDeep } from "lodash-es";
export function getInputFromForm(form: any, pluginType: string) {
form = cloneDeep(form);
let input: any = {};
const record: any = form;
if (pluginType === "plugin") {
input = form?.input || {};
delete form.input;
} else if (pluginType === "access") {
input = form?.access || {};
delete form.access;
} else if (pluginType === "notification") {
input = form?.body || {};
delete form.body;
} else if (pluginType === "addon") {
input = form?.body || {};
delete form.body;
} else {
throw new Error(`pluginType ${pluginType} not support`);
}
return {
input,
record,
};
}
@@ -20,6 +20,7 @@ export const Dicts = {
uploaderTypeDict: dict({
data: [
{ label: "SFTP", value: "sftp" },
{ label: "SCP", value: "scp" },
{ label: "FTP", value: "ftp" },
{ label: "阿里云OSS", value: "alioss" },
{ label: "腾讯云COS", value: "tencentcos" },
@@ -12,11 +12,12 @@ export type RequestHandleReq<T = any> = {
action: string;
data?: any;
input: T;
record?: any;
};
export async function doRequest(req: RequestHandleReq, opts: any = {}) {
const url = `/pi/handle/${req.type}`;
const { typeName, action, data, input } = req;
const { typeName, action, data, input, record } = req;
const res = await request({
url,
method: "post",
@@ -25,6 +26,7 @@ export async function doRequest(req: RequestHandleReq, opts: any = {}) {
action,
data,
input,
record,
},
...opts,
});
@@ -160,6 +160,7 @@ export default {
updateTime: "Update Time",
triggerType: "Trigger Type",
pipelineId: "Pipeline Id",
nextRunTime: "Next Run Time",
projectName: "Project",
adminId: "Admin",
},
@@ -167,6 +167,7 @@ export default {
updateTime: "更新时间",
triggerType: "触发类型",
pipelineId: "流水线Id",
nextRunTime: "下次运行时间",
projectName: "项目",
adminId: "管理员",
},
@@ -117,3 +117,4 @@ span.fs-icon-svg {
margin: 0 !important;
}
}
@@ -5,6 +5,7 @@
@import "./fix-windicss.less";
@import "./antdv4.less";
@import "./certd.less";
@import "./dark.less";
html,
body {
@@ -0,0 +1,7 @@
.dark{
.fs-page-header{
.title {
color: #d5d5d5 !important;
}
}
}
@@ -94,7 +94,7 @@ const defaultPreferences: Preferences = {
colorPrimary: "hsl(212 100% 45%)",
colorSuccess: "hsl(144 57% 58%)",
colorWarning: "hsl(42 84% 61%)",
mode: "light",
mode: "auto",
radius: "0.5",
semiDarkHeader: false,
semiDarkSidebar: false,
@@ -67,7 +67,10 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
set(form, key, column.value);
}
//字段配置赋值
columnsRef.value[key] = column;
if (columnsRef.value) {
columnsRef.value[key] = column;
}
console.log("form", columnsRef.value);
});
}
@@ -25,7 +25,7 @@
</a-form-item>
<a-form-item :label="t('certd.monitor.setting.dnsServer')" :name="['dnsServer']">
<div class="flex">
<a-select v-model:value="formState.dnsServer" mode="tags" :open="false" />
<a-select v-model:value="formState.dnsServer" :token-separators="[' ', ',', '', '', '|']" mode="tags" :open="false" />
</div>
<div class="helper">{{ t("certd.monitor.setting.dnsServerHelper") }}</div>
</a-form-item>
@@ -75,7 +75,7 @@ async function loadUserSettings() {
loadUserSettings();
const doSave = async (form: any) => {
await utils.sleep(1);
await utils.sleep(300);
await api.SiteMonitorSettingsSave({
...formState,
});
@@ -358,6 +358,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
column: {
align: "center",
width: 120,
show: false,
sorter: true,
},
form: {
@@ -471,6 +472,18 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
align: "center",
},
},
nextRunTime: {
title: t("certd.fields.nextRunTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 150,
align: "center",
},
},
disabled: {
title: t("certd.fields.enabled"),
type: "dict-switch",
@@ -885,6 +885,7 @@ export default defineComponent({
saveLoading.value = false;
}
};
const edit = () => {
pipeline.value = cloneDeep(currentPipeline.value);
currentHistory.value = null;
@@ -36,7 +36,7 @@
<a-collapse v-if="detail?.template?.pipelineId > 0" v-model:active-key="activeKey">
<a-collapse-panel v-for="(step, stepId) in steps" :key="stepId" class="step-item" :header="step.title">
<div class="step-inputs flex flex-wrap">
<div v-for="(input, key) of step.input" :key="key" class="hover:bg-gray-100 p-5 w-full xl:w-[50%]">
<div v-for="(input, key) of step.input" :key="key" class="hover:bg-gray-100 dark:hover:bg-[#2d2d2d] p-5 w-full xl:w-[50%]">
<div class="flex flex-between" :title="input.define.helper">
<div class="flex flex-1 overflow-hidden mr-5">
<span style="min-width: 140px" class="bas">
@@ -7,7 +7,7 @@
</template>
<script setup lang="ts">
import { get, set } from "lodash-es";
import { computed, reactive, ref, defineProps } from "vue";
import { computed, reactive, ref } from "vue";
import { useStepHelper } from "./utils";
import { usePluginStore } from "/@/store/plugin";
@@ -221,14 +221,12 @@ onMounted(() => {
border: 1px solid #e8e8e8;
border-radius: 4px;
overflow: hidden;
background-color: #fff;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #fafafa;
border-bottom: 1px solid #e8e8e8;
}
@@ -245,13 +243,11 @@ onMounted(() => {
.input-form {
margin-bottom: 12px;
padding: 12px;
background-color: #fafafa;
border-radius: 4px;
}
.domain-info {
padding: 5.5px 12px;
background-color: #f0f0f0;
border-radius: 4px;
display: flex;
gap: 16px;
@@ -272,7 +268,6 @@ onMounted(() => {
.summary {
margin-top: 16px;
padding: 12px;
background-color: #f8f9fa;
border-radius: 4px;
.summary-text {
}
@@ -110,7 +110,6 @@ onMounted(() => {
}
.info-item {
background-color: #fafafa;
border-radius: 4px;
padding: 12px;
@@ -138,7 +138,6 @@ const resultError = computed(() => {
.port-info {
font-size: 12px;
color: #999;
background-color: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
margin-right: 8px;
@@ -154,7 +153,6 @@ const resultError = computed(() => {
.error-message,
.object-result,
.text-result {
background-color: #f8f8f8;
padding: 8px 10px;
border-radius: 3px;
overflow-x: auto;
@@ -170,7 +168,6 @@ const resultError = computed(() => {
}
.test-log {
background-color: #f8f8f8;
padding: 8px 10px;
border-radius: 3px;
overflow-x: auto;
@@ -30,7 +30,6 @@ import ServerInfoCard from "./ServerInfoCard.vue";
.page-sys-nettest {
.nettest-container {
padding: 16px;
background-color: #fff;
}
.test-areas {
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.10](https://github.com/certd/certd/compare/v1.38.9...v1.38.10) (2026-02-15)
### Bug Fixes
* 修复1panel 请求失败的bug ([0283662](https://github.com/certd/certd/commit/0283662931ff47d6b5d49f91a30c4a002fe1d108))
* 修复阿里云dcdn使用上传到cas的id引用错误的bug ([61800b2](https://github.com/certd/certd/commit/61800b23e2be324169990810d1176c18decabb23))
* 修复保存站点监控dns设置,偶尔无法保存成功的bug ([8387fe0](https://github.com/certd/certd/commit/8387fe0d5b2e77b8c2788a10791e5389d97a3e41))
### Performance Improvements
* 备份支持scp上传 ([66ac471](https://github.com/certd/certd/commit/66ac4716f2565d7ee827461b625397ae21599451))
* 列表中支持下次执行时间显示 ([a3cabd5](https://github.com/certd/certd/commit/a3cabd5f36ed41225ad418098596e9b2c44e31a1))
* 群晖支持刷新登录有效期 ([42c7ec2](https://github.com/certd/certd/commit/42c7ec2f75947e2b8298d6605d4dbcd441aacd51))
* 所有授权增加测试按钮 ([7a3e68d](https://github.com/certd/certd/commit/7a3e68d656c1dcdcd814b69891bd2c2c6fe3098a))
* 新网互联支持查询域名列表 ([e7e54bc](https://github.com/certd/certd/commit/e7e54bc19e3a734913a93a94e25db3bb06d2ab0f))
* 优化京东云报错详情显示 ([1195417](https://github.com/certd/certd/commit/1195417b9714d2fcb540e43c0a20809b7ee2052b))
* 增加部署证书到certd本身插件 ([3cd1aae](https://github.com/certd/certd/commit/3cd1aaeb035f8af79714030889b2b4dc259b700e))
* 支持next-terminal ([6f3fd78](https://github.com/certd/certd/commit/6f3fd785e77a33c72bdf4115dc5d498e677d1863))
* http校验方式支持scp上传 ([4eb940f](https://github.com/certd/certd/commit/4eb940ffe765a0330331bc6af8396315e36d4e4a))
## [1.38.9](https://github.com/certd/certd/compare/v1.38.8...v1.38.9) (2026-02-09)
### Bug Fixes
@@ -17,6 +17,12 @@ input:
placeholder: 密码
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-51dns/access.js
@@ -19,6 +19,12 @@ input:
component:
placeholder: totp key
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-cachefly/access.js
@@ -19,6 +19,12 @@ input:
component:
placeholder: totp key
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-gcore/access.js
@@ -24,6 +24,12 @@ input:
value: ap-southeast-1
helper: 请选择ESA地区
required: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-lib/aliyun/access/aliesa-access.js
@@ -17,6 +17,12 @@ input:
required: true
encrypt: true
helper: 注意:证书申请需要dns解析权限;其他阿里云插件,需要对应的权限,比如证书上传需要证书管理权限;嫌麻烦就用主账号的全量权限的accessKey
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-lib/aliyun/access/aliyun-access.js
@@ -165,6 +165,12 @@ input:
value: me-south-1
- label: sa-east-1
value: sa-east-1
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-aws/access.js
@@ -19,6 +19,12 @@ input:
helper: 是否使用http代理
required: false
encrypt: false
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-cloudflare/access.js
@@ -17,6 +17,12 @@ input:
required: true
encrypt: true
helper: ''
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/ctyun/access/ctyun-access.js
@@ -17,6 +17,12 @@ input:
helper: ''
required: false
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-dnsla/access.js
@@ -17,6 +17,12 @@ input:
helper: 请前往[多吉云-密钥管理](https://console.dogecloud.com/user/keys)获取
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 测试授权是否正确
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-doge/access.js
@@ -17,6 +17,12 @@ input:
placeholder: accessKeySecret
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-huawei/access/huawei-access.js
@@ -16,6 +16,12 @@ input:
placeholder: SecretAccessKey
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-jdcloud/access.js
@@ -18,6 +18,12 @@ input:
vModel: checked
required: false
encrypt: false
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/k8s/access.js
@@ -15,6 +15,12 @@ input:
placeholder: password
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/kuocai/access.js
@@ -64,6 +64,12 @@ input:
})
}
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/lecdn/access.js
@@ -24,6 +24,12 @@ input:
helper: 设置->最下面开发者设置->启用OpenToken
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/lucky/access.js
@@ -30,6 +30,12 @@ input:
vModel: value
encrypt: false
required: false
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/maoyun/access.js
@@ -14,6 +14,12 @@ input:
然后点击Generate按钮
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-namesilo/access.js
@@ -0,0 +1,30 @@
name: nextTerminal
title: Next Terminal 授权
icon: clarity:plugin-line
desc: 用于访问 Next Terminal API 的授权配置
input:
baseUrl:
title: 系统地址
component:
name: a-input
allowClear: true
placeholder: https://nt.example.com:8088
required: true
apiToken:
title: API 令牌
helper: 个人中心->授权令牌->创建令牌
component:
name: a-input
allowClear: true
placeholder: NT_xxxxx
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: TestRequest
helper: 点击测试接口是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-next-terminal/access.js
@@ -37,6 +37,12 @@ input:
helper: pam 或 pve。默认值 pam
required: false
encrypt: false
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-proxmox/access.js
@@ -13,6 +13,12 @@ input:
title: SecretKey
encrypt: true
helper: SK
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
order: 2
pluginType: access
type: builtIn
@@ -21,6 +21,12 @@ input:
name: a-switch
vModel: checked
helper: 如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/safeline/access.js
@@ -83,6 +83,12 @@ input:
name: a-input-number
vModel: value
helper: 请求超时时间,单位:秒
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/synology/access.js
@@ -41,6 +41,12 @@ input:
component:
name: a-switch
vModel: checked
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-lib/tencent/access.js
@@ -16,6 +16,12 @@ input:
placeholder: 密码
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/unicloud/access.js
@@ -14,6 +14,12 @@ input:
placeholder: 又拍云密码
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-upyun/access.js
@@ -16,6 +16,12 @@ input:
placeholder: SecretAccessKey
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-volcengine/access.js
@@ -18,6 +18,12 @@ input:
placeholder: 密码
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-xinnetconnet/access.js
@@ -17,6 +17,12 @@ input:
helper: http://user.yiduncdn.com/console/index.html#/account/config/api,点击开启后获取
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/yidun/access.js
@@ -15,6 +15,12 @@ input:
placeholder: password
required: true
encrypt: true
testRequest:
title: 测试
component:
name: api-test
action: onTestRequest
helper: 点击测试接口看是否正常
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-plus/yidun/access-rcdn.js
@@ -18,10 +18,11 @@ input:
options:
- label: 本地复制
value: local
- label: ssh上传
value: ssh
- label: oss上传
- label: oss上传(推荐)
value: oss
- label: ssh上传(请使用oss上传方式)
value: ssh
disabled: true
placeholder: ''
helper: 支持本地复制、ssh上传
required: true
@@ -58,6 +59,8 @@ input:
label: Ftp
- value: sftp
label: Sftp
- value: scp
label: SCP
mergeScript: |2-
return {
@@ -0,0 +1,24 @@
showRunStrategy: false
default:
strategy:
runStrategy: 1
name: DeployToCertd
title: 部署证书到Certd本身
icon: mdi:restart
desc: 【仅管理员可用】 部署证书到 certd的https服务,用于更新 Certd 的 ssl 证书,建议将此任务放在流水线的最后一步
group: admin
onlyAdmin: true
input:
cert:
title: 域名证书
helper: 请选择前置任务输出的域名证书
component:
name: output-selector
from:
- ':cert:'
required: true
order: 0
output: {}
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-admin/plugin-deploy-to-certd.js
@@ -0,0 +1,58 @@
showRunStrategy: false
default:
strategy:
runStrategy: 1
name: NextTerminalRefreshCert
title: NextTerminal-更新证书
icon: clarity:plugin-line
desc: 更新 Next Terminal 证书
group: panel
input:
cert:
title: 域名证书
helper: 请选择前置任务输出的域名证书
component:
name: output-selector
from:
- CertApply
required: true
order: 0
accessId:
title: Next Terminal 授权
helper: 选择 Next Terminal 授权配置
component:
name: access-selector
type: nextTerminal
required: true
order: 0
certIds:
title: 选择证书
component:
name: remote-select
vModel: value
mode: tags
type: plugin
action: onGetCertList
search: false
pager: false
watches:
- certDomains
- accessId
- accessId
required: true
mergeScript: |2-
return {
component:{
form: ctx.compute(({form})=>{
return form
})
},
}
helper: 选择要更新的 Next Terminal 证书(支持多选),如果这里没有列出,需要先前往控制台上传证书,之后就可以自动更新
order: 0
output: {}
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-next-terminal/plugins/plugin-refresh-cert.js
@@ -3,9 +3,9 @@ default:
strategy:
runStrategy: 1
name: SafelineDeployToWebsitePlugin
title: 雷池-更新证书
title: 雷池-更新证书(支持控制台和防护应用)
icon: svg:icon-safeline
desc: 更新长亭雷池WAF的证书
desc: 更新长亭雷池WAF的证书,支持更新控制台和防护应用的证书。
group: panel
needPlus: false
input:
@@ -68,7 +68,9 @@ input:
},
}
helper: 请选择要更新的雷池的证书Id,需要先手动到雷池控制台上传一次
helper: |-
请选择要更新的雷池的证书Id,需要先手动到雷池控制台上传一次
如果输入0,则表示新增证书,运行一次之后可以在雷池中使用该证书,最后记得在此处选择新上传的这个证书id,后续将进行自动更新
order: 0
output: {}
pluginType: deploy
@@ -31,7 +31,9 @@ input:
order: 0
accessId:
title: 群晖授权
helper: 群晖登录授权,请确保账户是管理员用户组
helper: |-
群晖登录授权,请确保账户是管理员用户组
群晖OTP授权有效期只有30天,您还需要添加“群晖-刷新OTP登录有效期”任务做登录有效期保活
component:
name: access-selector
type: synology
@@ -0,0 +1,35 @@
showRunStrategy: false
default:
strategy:
runStrategy: 0
name: SynologyKeepAlive
title: 群晖-刷新OTP登录有效期
icon: simple-icons:synology
group: panel
desc: 群晖登录状态可能30天失效,需要在失效之前登录一次,刷新有效期,您可以将其放在“部署到群晖面板”任务之后
needPlus: true
input:
accessId:
title: 群晖授权
helper: 群晖登录授权,请确保账户是管理员用户组
component:
name: access-selector
type: synology
required: true
order: 0
intervalDays:
title: 间隔天数
helper: 多少天刷新一次,建议15天以内
value: 15
component:
name: a-input-number
vModel: value
required: true
order: 0
output:
lastRefreshTime:
title: 上次刷新时间
type: SynologyLastRefreshTime
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-plus/synology/plugins/plugin-keep-alive.js
+16 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.38.9",
"version": "1.38.10",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -41,6 +41,7 @@
"@alicloud/openapi-client": "^0.4.12",
"@alicloud/openapi-util": "^0.3.2",
"@alicloud/pop-core": "^1.7.10",
"@alicloud/sts-sdk": "^1.0.2",
"@alicloud/tea-typescript": "^1.8.0",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-acm": "^3.964.0",
@@ -48,20 +49,21 @@
"@aws-sdk/client-iam": "^3.964.0",
"@aws-sdk/client-route-53": "^3.964.0",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.38.9",
"@certd/basic": "^1.38.9",
"@certd/commercial-core": "^1.38.9",
"@aws-sdk/client-sts": "^3.990.0",
"@certd/acme-client": "^1.38.10",
"@certd/basic": "^1.38.10",
"@certd/commercial-core": "^1.38.10",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.38.9",
"@certd/lib-huawei": "^1.38.9",
"@certd/lib-k8s": "^1.38.9",
"@certd/lib-server": "^1.38.9",
"@certd/midway-flyway-js": "^1.38.9",
"@certd/pipeline": "^1.38.9",
"@certd/plugin-cert": "^1.38.9",
"@certd/plugin-lib": "^1.38.9",
"@certd/plugin-plus": "^1.38.9",
"@certd/plus-core": "^1.38.9",
"@certd/jdcloud": "^1.38.10",
"@certd/lib-huawei": "^1.38.10",
"@certd/lib-k8s": "^1.38.10",
"@certd/lib-server": "^1.38.10",
"@certd/midway-flyway-js": "^1.38.10",
"@certd/pipeline": "^1.38.10",
"@certd/plugin-cert": "^1.38.10",
"@certd/plugin-lib": "^1.38.10",
"@certd/plugin-plus": "^1.38.10",
"@certd/plus-core": "^1.38.10",
"@google-cloud/publicca": "^1.3.0",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.185",
@@ -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();
@@ -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();
@@ -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';
}
}

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