mirror of
https://github.com/certd/certd.git
synced 2026-10-24 02:02:20 +08:00
perf: 新增更新金山云KCM SLB证书插件
This commit is contained in:
@@ -122,7 +122,7 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
|
||||
|
||||
## YAML 和脚本规范
|
||||
|
||||
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
|
||||
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。`content` 必须使用 YAML 字面块 `|` 保留 JavaScript 换行,禁止使用折叠块 `>`,避免语句拼接导致 `Unexpected token 'const'` 等解析错误。
|
||||
|
||||
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
|
||||
- 统一使用 `await _ctx.import(...)` 引用模块。
|
||||
@@ -133,6 +133,13 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
|
||||
- 使用 `this.ctx.http` 访问 HTTP 能力;默认返回响应体 `response.data`,需要响应头、状态码或 Cookie 时必须设置 `returnOriginRes: true`,再从 `response.data` 读取响应体。详见 `references/context-utils.md`。
|
||||
- 优先复用 `this.ctx.utils` 中的现成工具,尤其是 `this.ctx.utils.hash` 的摘要和编码方法;完整分类见 `references/context-utils.md`。
|
||||
- 失败时抛出 `Error`。
|
||||
- 日志打印对象时必须使用 `JSON.stringify(value)`,禁止直接字符串拼接对象导致输出 `[object Object]`;需要可读日志时先构造明确的 JSON 字符串。
|
||||
|
||||
### 证书域名匹配与列表组件
|
||||
|
||||
- 需要从前置证书获取域名时,优先在 `input` 前置配置 `certDomains` 字段,使用 `cert-domains-getter`,并通过 `inputKey` 指向证书字段。
|
||||
- 远程证书或资源列表如果需要按证书域名匹配,必须为每个选项提供 `domain` 字段;`domain` 可以是单个域名字符串,也可以是域名数组。只有满足此前提,才能使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)` 返回分组后的列表。
|
||||
- 如果服务商接口不支持分页或服务端搜索,列表组件不要设置 `pager: true` 或 `search: true`;确需搜索时在插件中获取完整列表后执行本地过滤。
|
||||
|
||||
需要字段格式时读取 `references/online-yaml-format.md`。
|
||||
需要组件示例时读取 `references/component-examples.md`。
|
||||
|
||||
@@ -47,6 +47,8 @@ content: |
|
||||
|
||||
### `content` 规则
|
||||
|
||||
- `content` 必须使用 YAML 字面块 `|` 保留脚本换行,禁止使用折叠块 `>`;折叠换行可能导致多个 JavaScript 语句拼接,出现 `Unexpected token 'const'` 等解析错误。
|
||||
|
||||
- 统一使用 `await _ctx.import(...)` 加载模块。
|
||||
- 使用 `_ctx.import("/@/...")` 以绝对路径加载 `certd-server/src/` 下的模块,`/@` 代表 `certd-server/src` 根路径。
|
||||
- 需要确认模块时使用 `_ctx.logger`,插件执行过程使用 `this.logger`。
|
||||
@@ -56,9 +58,16 @@ content: |
|
||||
- HTTP 使用 `this.ctx.http`。默认 `request()` 返回响应体 `response.data`;需要 headers、status 或 Set-Cookie 时设置 `returnOriginRes: true`,此时返回原始响应对象,响应体从 `response.data` 读取。请求扩展参数 `skipSslVerify`、`skipCheckRes`、`logReq`、`logParams`、`logRes`、`logData`、`httpProxy`、`returnOriginRes` 的说明见 `context-utils.md`。
|
||||
- 优先使用 `this.ctx.utils` 中已有的哈希、编码、字符串、域名和请求工具,详见 `context-utils.md`。
|
||||
- 不要使用 `console.log`。
|
||||
- 日志打印对象时使用 `JSON.stringify(value)`,不要直接拼接对象输出 `[object Object]`。
|
||||
- 读取授权使用 `await this.getAccess(accessId)` 或目标基类规定的授权方式。
|
||||
- 失败时抛出 `Error`,不要吞掉错误。
|
||||
|
||||
### 证书域名与列表选项
|
||||
|
||||
- 需要使用前置证书域名时,在列表字段之前配置 `certDomains`,组件使用 `cert-domains-getter`,并通过 `inputKey` 绑定证书字段。
|
||||
- 列表选项可按证书域名匹配时,必须为每个选项设置 `domain`;该字段支持单个域名字符串或域名数组。只有配置该字段后,才使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)`。
|
||||
- 服务商接口不支持翻页或服务端搜索时,远程选择组件不要配置 `pager: true`、`search: true`;本地搜索可在获取完整列表后过滤。
|
||||
|
||||
### 编辑规则
|
||||
|
||||
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default { setSuccess: "Settings saved", editTitle: "Edit Certificate Application Template", addTitle: "Add Certificate Application Template", name: "Template Name", default: "Default", setDefault: "Set as Default", disabled: "Disabled", createTime: "Created At", config: "Configuration" };
|
||||
@@ -8,6 +8,7 @@ import { usePluginStore } from "/@/store/plugin";
|
||||
|
||||
export default {
|
||||
name: "OutputSelector",
|
||||
// inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
@@ -30,6 +31,9 @@ export default {
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
|
||||
// const attrs = ctx.attrs;
|
||||
// console.log(attrs);
|
||||
|
||||
async function onCreate() {
|
||||
await pluginStore.init();
|
||||
options.value = pluginStore.group.getPreStepOutputOptions({
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
|
||||
import { defineComponent, inject, ref, useAttrs, watch, Ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { PluginDefine } from "@certd/pipeline";
|
||||
import { getInputFromForm } from "./utils";
|
||||
@@ -81,6 +81,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
function updateValue(value: any) {
|
||||
debugger;
|
||||
if (props.single === true) {
|
||||
const last = value?.[value.length - 1];
|
||||
emit("update:value", last);
|
||||
@@ -103,8 +104,18 @@ function emitSelectedChange(value: any) {
|
||||
emit("selected-change", props.single === true ? options[0] : options);
|
||||
}
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const originalAttrs = useAttrs();
|
||||
const attrs = computed(() => {
|
||||
const tags = {
|
||||
mode: "tags",
|
||||
allowClear: true,
|
||||
single: false,
|
||||
};
|
||||
return {
|
||||
...tags,
|
||||
...originalAttrs,
|
||||
};
|
||||
});
|
||||
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
@@ -77,7 +77,6 @@ export default {
|
||||
searchPluginMarket: "搜索插件市场",
|
||||
searchLocalPlugins: "搜索本地插件",
|
||||
loadingPluginMarket: "正在加载插件市场...",
|
||||
noPluginFound: "没有找到插件",
|
||||
triggerManually: "手动触发",
|
||||
triggerSource: "触发源",
|
||||
scheduledTrigger: "触发源(定时)",
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
import { request } from "/src/api/service";
|
||||
import * as _ from "lodash-es";
|
||||
import { PluginConfigBean, PluginSysSetting } from "/@/views/sys/plugin/api";
|
||||
import { initComponentDefine } from "./define";
|
||||
import { PluginConfigBean } from "/@/views/sys/plugin/api";
|
||||
import { request } from "/src/api/service";
|
||||
const apiPrefix = "/pi/plugin";
|
||||
|
||||
const defaultInputDefine = {
|
||||
component: {
|
||||
name: "a-input",
|
||||
vModel: "modelValue",
|
||||
},
|
||||
};
|
||||
|
||||
function initPlugins(plugins: any) {
|
||||
const checkedComponents = ["a-checkbox", "a-radio", "a-switch"];
|
||||
for (const plugin of plugins) {
|
||||
for (const key in plugin.input) {
|
||||
const field = _.merge({}, defaultInputDefine, plugin.input[key]);
|
||||
const componentName = field.component.name;
|
||||
if (componentName.startsWith("a-")) {
|
||||
if (checkedComponents.includes(componentName)) {
|
||||
field.component.vModel = "checked";
|
||||
} else {
|
||||
field.component.vModel = "value";
|
||||
}
|
||||
}
|
||||
const field = initComponentDefine(plugin.input[key]);
|
||||
//嵌套对象
|
||||
field.key = ["input", key];
|
||||
if (field.required) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
const defaultColumn = {
|
||||
component: {
|
||||
name: "a-input",
|
||||
},
|
||||
};
|
||||
|
||||
const vModelMapping: Record<string, string> = {
|
||||
"remote-select": "value",
|
||||
RemoteSelect: "value",
|
||||
"a-switch": "checked",
|
||||
"a-checkbox": "checked",
|
||||
"a-radio": "checked",
|
||||
};
|
||||
|
||||
export function initComponentDefine(column: any) {
|
||||
const field = merge({}, defaultColumn, column);
|
||||
const componentName = field.component.name;
|
||||
if (field.component.vModel == null) {
|
||||
if (componentName?.startsWith("a-")) {
|
||||
field.component.vModel = vModelMapping[componentName] || "value";
|
||||
} else {
|
||||
field.component.vModel = vModelMapping[componentName] || "modelValue";
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DynamicType, FormItemProps, useMerge } from "@fast-crud/fast-crud";
|
||||
import { cloneDeep, merge, set } from "lodash-es";
|
||||
import { cloneDeep, merge } from "lodash-es";
|
||||
import { defineStore } from "pinia";
|
||||
import * as api from "./api.plugin";
|
||||
import { initComponentDefine } from "./define";
|
||||
import { i18n } from "/src/locales/i18n";
|
||||
interface PluginState {
|
||||
group?: PluginGroups;
|
||||
@@ -29,17 +30,7 @@ export function resolvePluginLocale(plugin: any, locale = i18n.global.locale.val
|
||||
export function fillPluginDefine(plugin: any) {
|
||||
const columns = plugin.input;
|
||||
for (const key in columns) {
|
||||
const column = columns[key];
|
||||
if (!column.component) {
|
||||
column.component = {
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
};
|
||||
} else {
|
||||
if (column.component.vModel == null && column.component.name?.startsWith("a-")) {
|
||||
column.component.vModel = "value";
|
||||
}
|
||||
}
|
||||
columns[key] = initComponentDefine(columns[key]);
|
||||
}
|
||||
|
||||
if (plugin.sysSetting?.metadata) {
|
||||
@@ -207,22 +198,6 @@ export class PluginGroups {
|
||||
}
|
||||
}
|
||||
|
||||
function filterInstalledPluginGroups(groups: { [key: string]: PluginGroup }) {
|
||||
const filteredGroups: { [key: string]: PluginGroup } = {};
|
||||
for (const [key, group] of Object.entries(groups)) {
|
||||
filteredGroups[key] = {
|
||||
...group,
|
||||
plugins: group.plugins.filter(plugin => {
|
||||
if (plugin.type !== "store") {
|
||||
return true;
|
||||
}
|
||||
return plugin.installed === true;
|
||||
}),
|
||||
};
|
||||
}
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
export const usePluginStore = defineStore({
|
||||
id: "app.plugin",
|
||||
state: (): PluginState => ({
|
||||
|
||||
@@ -30,6 +30,9 @@ export default defineComponent({
|
||||
const { t } = useI18n();
|
||||
const statusRef = computed(() => {
|
||||
const status = statusUtil.get(props.status as string);
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
return { ...status, label: t(`certd.pipeline.editor.status${String(status.value).replace(/(^|_)(\w)/g, (_, _separator, letter) => letter.toUpperCase())}`) };
|
||||
});
|
||||
|
||||
|
||||
+2
@@ -272,6 +272,8 @@ function useStepForm() {
|
||||
currentStep.input[key] = pluginSysConfig.sysSetting?.input[key];
|
||||
}
|
||||
}
|
||||
|
||||
console.log("currentPluginDefine:", currentPluginDefine.value);
|
||||
};
|
||||
|
||||
const stepSave = async (e: any) => {
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
"why-is-node-running": "^3.2.2"
|
||||
},
|
||||
"lazyDependencies": {
|
||||
"ksyun-sdk-node": "1.5.78",
|
||||
"@alicloud/fc20230330": "^4.1.7",
|
||||
"@alicloud/tea-typescript": "^1.8.0",
|
||||
"@alicloud/openapi-client": "^0.4.12",
|
||||
|
||||
@@ -90,7 +90,6 @@ export class KsyunAccess extends BaseAccess {
|
||||
this.ctx.logger.info(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async getCert(opts: { client: KsyunClient; certId: string }) {
|
||||
const res = await opts.client.doRequest({
|
||||
action: "GetCertificates",
|
||||
|
||||
@@ -21,22 +21,35 @@ export class KsyunClient {
|
||||
}
|
||||
|
||||
async doRequest(opts: { action: string; version: string } & HttpRequestConfig) {
|
||||
const method = (opts.method || "GET").toUpperCase();
|
||||
const requestData = {
|
||||
...(opts.data || {}),
|
||||
Action: opts.action,
|
||||
Version: opts.version,
|
||||
};
|
||||
let url = opts.url || "/";
|
||||
let data: any;
|
||||
if (method === "GET") {
|
||||
const query = querystring.stringify(requestData);
|
||||
url = `${url}${url.includes("?") ? "&" : "?"}${query}`;
|
||||
} else {
|
||||
const query = querystring.stringify({ Action: opts.action, Version: opts.version });
|
||||
url = `${url}${url.includes("?") ? "&" : "?"}${query}`;
|
||||
data = JSON.stringify(opts.data || {});
|
||||
}
|
||||
const config = this.signRequest({
|
||||
method: opts.method || "GET",
|
||||
url: opts.url || "/2016-09-01/domain/GetCdnDomains",
|
||||
method,
|
||||
url,
|
||||
baseURL: `https://${this.endpoint}`,
|
||||
params: opts.params,
|
||||
headers: {
|
||||
"X-Action": opts.action,
|
||||
"X-Version": opts.version,
|
||||
},
|
||||
data: opts.data,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data,
|
||||
});
|
||||
|
||||
try {
|
||||
return await this.http.request({
|
||||
...config,
|
||||
data: opts.data,
|
||||
data: config.data,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.error(e.request);
|
||||
|
||||
Reference in New Issue
Block a user