mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-12 07:29:51 +00:00
feat: support configuring renewal interval in application
This commit is contained in:
parent
60a13aaf17
commit
c71d14cafa
@ -68,7 +68,7 @@ type WorkflowNodeConfigForApply struct {
|
|||||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
|
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
|
||||||
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商)
|
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商)
|
||||||
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
|
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
|
||||||
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // TODO: 证书到期前多少天前跳过续期(默认值:30)
|
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(默认值:30)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowNodeConfigForDeploy struct {
|
type WorkflowNodeConfigForDeploy struct {
|
||||||
|
@ -109,9 +109,6 @@ func (a *applyNode) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.WorkflowOutput) (skip bool, reason string) {
|
||||||
const validityDuration = time.Hour * 24 * 10
|
|
||||||
|
|
||||||
// TODO: 可控制是否强制申请
|
|
||||||
if lastOutput != nil && lastOutput.Succeeded {
|
if lastOutput != nil && lastOutput.Succeeded {
|
||||||
// 比较和上次申请时的关键配置(即影响证书签发的)参数是否一致
|
// 比较和上次申请时的关键配置(即影响证书签发的)参数是否一致
|
||||||
currentNodeConfig := a.node.GetConfigForApply()
|
currentNodeConfig := a.node.GetConfigForApply()
|
||||||
@ -133,7 +130,8 @@ func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
||||||
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > validityDuration {
|
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
||||||
|
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > renewalInterval {
|
||||||
return true, "已申请过证书,且证书尚未临近过期"
|
return true, "已申请过证书,且证书尚未临近过期"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,22 @@ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } f
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { AutoComplete, type AutoCompleteProps, Button, Divider, Form, type FormInstance, Input, Select, Space, Switch, Tooltip, Typography } from "antd";
|
import {
|
||||||
|
AutoComplete,
|
||||||
|
type AutoCompleteProps,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
type FormInstance,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -43,6 +58,7 @@ const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
|||||||
return {
|
return {
|
||||||
keyAlgorithm: "RSA2048",
|
keyAlgorithm: "RSA2048",
|
||||||
disableFollowCNAME: true,
|
disableFollowCNAME: true,
|
||||||
|
skipBeforeExpiryDays: 20,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +105,11 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
])
|
])
|
||||||
.nullish(),
|
.nullish(),
|
||||||
disableFollowCNAME: z.boolean().nullish(),
|
disableFollowCNAME: z.boolean().nullish(),
|
||||||
|
skipBeforeExpiryDays: z
|
||||||
|
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
||||||
|
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||||
|
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||||
|
.lte(60, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
const { form: formInst, formProps } = useAntdForm({
|
const { form: formInst, formProps } = useAntdForm({
|
||||||
@ -329,7 +350,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
min={0}
|
min={0}
|
||||||
max={3600}
|
max={3600}
|
||||||
placeholder={t("workflow_node.apply.form.dns_propagation_timeout.placeholder")}
|
placeholder={t("workflow_node.apply.form.dns_propagation_timeout.placeholder")}
|
||||||
addonAfter={t("workflow_node.apply.form.dns_propagation_timeout.suffix")}
|
addonAfter={t("workflow_node.apply.form.dns_propagation_timeout.unit")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -345,7 +366,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
min={0}
|
min={0}
|
||||||
max={86400}
|
max={86400}
|
||||||
placeholder={t("workflow_node.apply.form.dns_ttl.placeholder")}
|
placeholder={t("workflow_node.apply.form.dns_ttl.placeholder")}
|
||||||
addonAfter={t("workflow_node.apply.form.dns_ttl.suffix")}
|
addonAfter={t("workflow_node.apply.form.dns_ttl.unit")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -358,6 +379,33 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
<Divider className="my-1">
|
||||||
|
<Typography.Text className="text-xs font-normal" type="secondary">
|
||||||
|
{t("workflow_node.apply.form.strategy_config.label")}
|
||||||
|
</Typography.Text>
|
||||||
|
</Divider>
|
||||||
|
|
||||||
|
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("workflow_node.apply.form.skip_before_expiry_days.label")}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.skip_before_expiry_days.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap={8}>
|
||||||
|
<div>{t("workflow_node.apply.form.skip_before_expiry_days.prefix")}</div>
|
||||||
|
<Form.Item name="skipBeforeExpiryDays" noStyle rules={[formRule]}>
|
||||||
|
<InputNumber
|
||||||
|
className="flex-1"
|
||||||
|
min={1}
|
||||||
|
max={60}
|
||||||
|
placeholder={t("workflow_node.apply.form.skip_before_expiry_days.placeholder")}
|
||||||
|
addonAfter={t("workflow_node.apply.form.skip_before_expiry_days.unit")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<div>{t("workflow_node.apply.form.skip_before_expiry_days.suffix")}</div>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
</Form.Provider>
|
</Form.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ export type WorkflowNodeConfigForApply = {
|
|||||||
dnsPropagationTimeout?: number;
|
dnsPropagationTimeout?: number;
|
||||||
dnsTTL?: number;
|
dnsTTL?: number;
|
||||||
disableFollowCNAME?: boolean;
|
disableFollowCNAME?: boolean;
|
||||||
|
skipBeforeExpiryDays: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowNodeConfigForDeploy = {
|
export type WorkflowNodeConfigForDeploy = {
|
||||||
|
@ -51,19 +51,26 @@
|
|||||||
"workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
"workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
||||||
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
|
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
|
||||||
"workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
"workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
||||||
"workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">Learn more</a>.",
|
"workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">Learn more</a>.",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "Change DNS rcursive nameservers",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "Change DNS rcursive nameservers",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "Please enter DNS recursive nameserver",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "Please enter DNS recursive nameserver",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation timeout (Optional)",
|
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation timeout (Optional)",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.placeholder": "Please enter DNS propagation timeout",
|
"workflow_node.apply.form.dns_propagation_timeout.placeholder": "Please enter DNS propagation timeout",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.suffix": "seconds",
|
"workflow_node.apply.form.dns_propagation_timeout.unit": "seconds",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the maximum waiting time for DNS propagation checks during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
"workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the maximum waiting time for DNS propagation checks during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
||||||
"workflow_node.apply.form.dns_ttl.label": "DNS TTL (Optional)",
|
"workflow_node.apply.form.dns_ttl.label": "DNS TTL (Optional)",
|
||||||
"workflow_node.apply.form.dns_ttl.placeholder": "Please enter DNS TTL",
|
"workflow_node.apply.form.dns_ttl.placeholder": "Please enter DNS TTL",
|
||||||
"workflow_node.apply.form.dns_ttl.suffix": "seconds",
|
"workflow_node.apply.form.dns_ttl.unit": "seconds",
|
||||||
"workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
"workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
||||||
"workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following",
|
"workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following",
|
||||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">Learn more</a>.",
|
"workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">Learn more</a>.",
|
||||||
|
"workflow_node.apply.form.strategy_config.label": "Strategy settings",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.label": "Renewal interval",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "Please enter renewal interval",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.prefix": "Skip when the certificate expiration time exceeds",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.suffix": "",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.unit": "days",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "Be careful not to exceed the validity period limit of the CA, otherwise the certificate may never be renewed.",
|
||||||
|
|
||||||
"workflow_node.deploy.label": "Deployment",
|
"workflow_node.deploy.label": "Deployment",
|
||||||
"workflow_node.deploy.search.provider.placeholder": "Search deploy target ...",
|
"workflow_node.deploy.search.provider.placeholder": "Search deploy target ...",
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
"workflow_node.apply.form.domains.multiple_input_modal.placeholder": "请输入域名",
|
"workflow_node.apply.form.domains.multiple_input_modal.placeholder": "请输入域名",
|
||||||
"workflow_node.apply.form.contact_email.label": "联系邮箱",
|
"workflow_node.apply.form.contact_email.label": "联系邮箱",
|
||||||
"workflow_node.apply.form.contact_email.placeholder": "请输入联系邮箱",
|
"workflow_node.apply.form.contact_email.placeholder": "请输入联系邮箱",
|
||||||
"workflow_node.apply.form.contact_email.tooltip": "申请签发 SSL 证书时所需的联系方式。请注意 Let's Encrypt 账户注册的速率限制。<br><a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">点此了解更多</a>。",
|
"workflow_node.apply.form.contact_email.tooltip": "申请签发 SSL 证书时所需的联系方式。请注意 Let's Encrypt 账户注册的速率限制。<a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">点此了解更多</a>。",
|
||||||
"workflow_node.apply.form.provider.label": "DNS 提供商",
|
"workflow_node.apply.form.provider.label": "DNS 提供商",
|
||||||
"workflow_node.apply.form.provider.placeholder": "请选择 DNS 提供商",
|
"workflow_node.apply.form.provider.placeholder": "请选择 DNS 提供商",
|
||||||
"workflow_node.apply.form.provider_access.label": "DNS 提供商授权",
|
"workflow_node.apply.form.provider_access.label": "DNS 提供商授权",
|
||||||
@ -51,19 +51,26 @@
|
|||||||
"workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法",
|
"workflow_node.apply.form.key_algorithm.placeholder": "请选择数字证书算法",
|
||||||
"workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)",
|
"workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)",
|
||||||
"workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)",
|
"workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)",
|
||||||
"workflow_node.apply.form.nameservers.tooltip": "在 ACME DNS-01 认证时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。<br><a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">点此了解更多</a>。",
|
"workflow_node.apply.form.nameservers.tooltip": "在 ACME DNS-01 认证时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。<a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">点此了解更多</a>。",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "修改 DNS 递归服务器",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "修改 DNS 递归服务器",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "请输入 DNS 递归服务器",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "请输入 DNS 递归服务器",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS 传播检查超时时间(可选)",
|
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS 传播检查超时时间(可选)",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间",
|
"workflow_node.apply.form.dns_propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.suffix": "秒",
|
"workflow_node.apply.form.dns_propagation_timeout.unit": "秒",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.tooltip": "在 ACME DNS-01 认证时等待 DNS 传播检查的最长时间。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
"workflow_node.apply.form.dns_propagation_timeout.tooltip": "在 ACME DNS-01 认证时等待 DNS 传播检查的最长时间。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
||||||
"workflow_node.apply.form.dns_ttl.label": "DNS 解析 TTL(可选)",
|
"workflow_node.apply.form.dns_ttl.label": "DNS 解析 TTL(可选)",
|
||||||
"workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL",
|
"workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL",
|
||||||
"workflow_node.apply.form.dns_ttl.suffix": "秒",
|
"workflow_node.apply.form.dns_ttl.unit": "秒",
|
||||||
"workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
"workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
||||||
"workflow_node.apply.form.disable_follow_cname.label": "禁止 CNAME 跟随",
|
"workflow_node.apply.form.disable_follow_cname.label": "禁止 CNAME 跟随",
|
||||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 认证时是否禁止 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<br><a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 认证时是否禁止 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
||||||
|
"workflow_node.apply.form.strategy_config.label": "执行策略",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.label": "续期间隔",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "请输入续期间隔",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当距上次申请的证书到期时间前",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时跳过执行",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
|
||||||
|
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过 CA 的证书有效期限制,否则证书可能永远不会续期。",
|
||||||
|
|
||||||
"workflow_node.deploy.label": "部署",
|
"workflow_node.deploy.label": "部署",
|
||||||
"workflow_node.deploy.search.provider.placeholder": "搜索部署目标……",
|
"workflow_node.deploy.search.provider.placeholder": "搜索部署目标……",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user