feat: make the builtin providers access field non mandatory

This commit is contained in:
Fu Diwei 2025-03-30 13:53:32 +08:00
parent 6ad0d8e42f
commit 09b5a21af1
13 changed files with 74 additions and 82 deletions

View File

@ -32,18 +32,23 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
} }
nodeConfig := node.GetConfigForDeploy() nodeConfig := node.GetConfigForDeploy()
options := &deployerOptions{
accessRepo := repository.NewAccessRepository() Provider: domain.DeployProviderType(nodeConfig.Provider),
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId) ProviderAccessConfig: make(map[string]any),
if err != nil { ProviderDeployConfig: nodeConfig.ProviderConfig,
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
} }
deployer, err := createDeployer(&deployerOptions{ accessRepo := repository.NewAccessRepository()
Provider: domain.DeployProviderType(nodeConfig.Provider), if nodeConfig.ProviderAccessId != "" {
ProviderAccessConfig: access.Config, access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
ProviderDeployConfig: nodeConfig.ProviderConfig, if err != nil {
}) return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
} else {
options.ProviderAccessConfig = access.Config
}
}
deployer, err := createDeployer(options)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -144,8 +144,6 @@ type AccessConfigForKubernetes struct {
KubeConfig string `json:"kubeConfig,omitempty"` KubeConfig string `json:"kubeConfig,omitempty"`
} }
type AccessConfigForLocal struct{}
type AccessConfigForNamecheap struct { type AccessConfigForNamecheap struct {
Username string `json:"username"` Username string `json:"username"`
ApiKey string `json:"apiKey"` ApiKey string `json:"apiKey"`

View File

@ -87,11 +87,11 @@ type WorkflowNodeConfigForUpload struct {
} }
type WorkflowNodeConfigForDeploy struct { type WorkflowNodeConfigForDeploy struct {
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate” Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
Provider string `json:"provider"` // 主机提供商 Provider string `json:"provider"` // 主机提供商
ProviderAccessId string `json:"providerAccessId"` // 主机提供商授权记录 ID ProviderAccessId string `json:"providerAccessId,omitempty"` // 主机提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // 主机提供商额外配置 ProviderConfig map[string]any `json:"providerConfig,omitempty"` // 主机提供商额外配置
SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过 SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过
} }
type WorkflowNodeConfigForNotify struct { type WorkflowNodeConfigForNotify struct {

View File

@ -35,7 +35,6 @@ import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServices
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig"; import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig"; import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig"; import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
import AccessFormLocalConfig from "./AccessFormLocalConfig";
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig"; import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
@ -159,8 +158,6 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormJDCloudConfig {...nestedFormProps} />; return <AccessFormJDCloudConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.KUBERNETES: case ACCESS_PROVIDERS.KUBERNETES:
return <AccessFormKubernetesConfig {...nestedFormProps} />; return <AccessFormKubernetesConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.LOCAL:
return <AccessFormLocalConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.NAMECHEAP: case ACCESS_PROVIDERS.NAMECHEAP:
return <AccessFormNamecheapConfig {...nestedFormProps} />; return <AccessFormNamecheapConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.NAMEDOTCOM: case ACCESS_PROVIDERS.NAMEDOTCOM:

View File

@ -1,36 +0,0 @@
import { Form, type FormInstance } from "antd";
import { type AccessConfigForLocal } from "@/domain/access";
type AccessFormLocalConfigFieldValues = Nullish<AccessConfigForLocal>;
export type AccessFormLocalConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessFormLocalConfigFieldValues;
onValuesChange?: (values: AccessFormLocalConfigFieldValues) => void;
};
const initFormModel = (): AccessFormLocalConfigFieldValues => {
return {};
};
const AccessFormLocalConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormLocalConfigProps) => {
const handleFormChange = (_: unknown, values: any) => {
onValuesChange?.(values);
};
return (
<Form
form={formInst}
disabled={disabled}
initialValues={initialValues ?? initFormModel()}
layout="vertical"
name={formName}
onValuesChange={handleFormChange}
></Form>
);
};
export default AccessFormLocalConfig;

View File

@ -24,6 +24,7 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
key: item.type, key: item.type,
value: item.type, value: item.type,
label: t(item.name), label: t(item.name),
disabled: item.builtin,
data: item, data: item,
})) }))
); );
@ -35,7 +36,7 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
<div className="flex max-w-full items-center justify-between gap-4 overflow-hidden"> <div className="flex max-w-full items-center justify-between gap-4 overflow-hidden">
<Space className="max-w-full grow truncate" size={4}> <Space className="max-w-full grow truncate" size={4}>
<Avatar src={provider?.icon} size="small" /> <Avatar src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis> <Typography.Text className="leading-loose" type={provider?.builtin ? "secondary" : undefined} delete={provider?.builtin ? true : undefined} ellipsis>
{t(provider?.name ?? "")} {t(provider?.name ?? "")}
</Typography.Text> </Typography.Text>
</Space> </Space>

View File

@ -97,7 +97,9 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
.nullish() .nullish()
.refine((v) => { .refine((v) => {
if (!fieldCAProvider) return true; if (!fieldCAProvider) return true;
return !!v;
const provider = applyCAProvidersMap.get(fieldCAProvider);
return !!provider?.builtin || !!v;
}, t("workflow_node.apply.form.ca_provider_access.placeholder")), }, t("workflow_node.apply.form.ca_provider_access.placeholder")),
caProviderConfig: z.any().nullish(), caProviderConfig: z.any().nullish(),
keyAlgorithm: z keyAlgorithm: z
@ -158,8 +160,10 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
const [showCAProviderAccess, setShowCAProviderAccess] = useState(false); const [showCAProviderAccess, setShowCAProviderAccess] = useState(false);
useEffect(() => { useEffect(() => {
// 内置的 CA 提供商(如 Let's Encrypt无需显示授权信息字段
if (fieldCAProvider) { if (fieldCAProvider) {
setShowCAProviderAccess(true); const provider = applyCAProvidersMap.get(fieldCAProvider);
setShowCAProviderAccess(!provider?.builtin);
} else { } else {
setShowCAProviderAccess(false); setShowCAProviderAccess(false);
} }

View File

@ -125,8 +125,14 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
provider: z.string({ message: t("workflow_node.deploy.form.provider.placeholder") }).nonempty(t("workflow_node.deploy.form.provider.placeholder")), provider: z.string({ message: t("workflow_node.deploy.form.provider.placeholder") }).nonempty(t("workflow_node.deploy.form.provider.placeholder")),
providerAccessId: z providerAccessId: z
.string({ message: t("workflow_node.deploy.form.provider_access.placeholder") }) .string({ message: t("workflow_node.deploy.form.provider_access.placeholder") })
.nonempty(t("workflow_node.deploy.form.provider_access.placeholder")), .nullish()
providerConfig: z.any(), .refine((v) => {
if (!fieldProvider) return true;
const provider = deployProvidersMap.get(fieldProvider);
return !!provider?.builtin || !!v;
}, t("workflow_node.deploy.form.provider_access.placeholder")),
providerConfig: z.any().nullish(),
skipOnLastSucceeded: z.boolean().nullish(), skipOnLastSucceeded: z.boolean().nullish(),
}); });
const formRule = createSchemaFieldRule(formSchema); const formRule = createSchemaFieldRule(formSchema);
@ -137,6 +143,17 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true });
const [showProviderAccess, setShowProviderAccess] = useState(false);
useEffect(() => {
// 内置的部署提供商(如本地部署)无需显示授权信息字段
if (fieldProvider) {
const provider = deployProvidersMap.get(fieldProvider);
setShowProviderAccess(!provider?.builtin);
} else {
setShowProviderAccess(false);
}
}, [fieldProvider]);
const [nestedFormInst] = Form.useForm(); const [nestedFormInst] = Form.useForm();
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeDeployConfigFormProviderConfigForm" }); const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "workflowNodeDeployConfigFormProviderConfigForm" });
const nestedFormEl = useMemo(() => { const nestedFormEl = useMemo(() => {
@ -368,7 +385,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
/> />
</Form.Item> </Form.Item>
<Form.Item className="mb-0"> <Form.Item className="mb-0" hidden={!showProviderAccess}>
<label className="mb-1 block"> <label className="mb-1 block">
<div className="flex w-full items-center justify-between gap-4"> <div className="flex w-full items-center justify-between gap-4">
<div className="max-w-full grow truncate"> <div className="max-w-full grow truncate">

View File

@ -32,7 +32,6 @@ export interface AccessModel extends BaseModel {
| AccessConfigForHuaweiCloud | AccessConfigForHuaweiCloud
| AccessConfigForJDCloud | AccessConfigForJDCloud
| AccessConfigForKubernetes | AccessConfigForKubernetes
| AccessConfigForLocal
| AccessConfigForNamecheap | AccessConfigForNamecheap
| AccessConfigForNameDotCom | AccessConfigForNameDotCom
| AccessConfigForNameSilo | AccessConfigForNameSilo
@ -184,8 +183,6 @@ export type AccessConfigForKubernetes = {
kubeConfig?: string; kubeConfig?: string;
}; };
export type AccessConfigForLocal = NonNullable<unknown>;
export type AccessConfigForNamecheap = { export type AccessConfigForNamecheap = {
username: string; username: string;
apiKey: string; apiKey: string;

View File

@ -69,6 +69,7 @@ export type AccessProvider = {
name: string; name: string;
icon: string; icon: string;
usages: AccessUsageType[]; usages: AccessUsageType[];
builtin: boolean;
}; };
export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProvider> = new Map( export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProvider> = new Map(
@ -135,6 +136,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
name: e[1] as string, name: e[1] as string,
icon: e[2] as string, icon: e[2] as string,
usages: e[3] as AccessUsageType[], usages: e[3] as AccessUsageType[],
builtin: ([ACCESS_PROVIDERS.LOCAL, ACCESS_PROVIDERS.LETSENCRYPT, ACCESS_PROVIDERS.LETSENCRYPTSTAGING] as string[]).includes(e[0] as string),
}, },
]) ])
); );
@ -159,6 +161,7 @@ export type ApplyCAProvider = {
name: string; name: string;
icon: string; icon: string;
provider: AccessProviderType; provider: AccessProviderType;
builtin: boolean;
}; };
export const applyCAProvidersMap: Map<ApplyCAProvider["type"] | string, ApplyCAProvider> = new Map( export const applyCAProvidersMap: Map<ApplyCAProvider["type"] | string, ApplyCAProvider> = new Map(
@ -166,17 +169,21 @@ export const applyCAProvidersMap: Map<ApplyCAProvider["type"] | string, ApplyCAP
NOTICE: The following order determines the order displayed at the frontend. NOTICE: The following order determines the order displayed at the frontend.
*/ */
[[APPLY_CA_PROVIDERS.LETSENCRYPT], [APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING], [APPLY_CA_PROVIDERS.ZEROSSL], [APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES]].map( [
([type]) => [ [APPLY_CA_PROVIDERS.LETSENCRYPT, "true"],
type, [APPLY_CA_PROVIDERS.LETSENCRYPTSTAGING, "true"],
{ [APPLY_CA_PROVIDERS.ZEROSSL],
type: type as ApplyCAProviderType, [APPLY_CA_PROVIDERS.GOOGLETRUSTSERVICES],
name: accessProvidersMap.get(type.split("-")[0])!.name, ].map(([type, builtin]) => [
icon: accessProvidersMap.get(type.split("-")[0])!.icon, type,
provider: type.split("-")[0] as AccessProviderType, {
}, type: type as ApplyCAProviderType,
] name: accessProvidersMap.get(type.split("-")[0])!.name,
) icon: accessProvidersMap.get(type.split("-")[0])!.icon,
provider: type.split("-")[0] as AccessProviderType,
builtin: builtin === "true",
},
])
); );
// #endregion // #endregion
@ -379,6 +386,7 @@ export type DeployProvider = {
icon: string; icon: string;
provider: AccessProviderType; provider: AccessProviderType;
category: DeployCategoryType; category: DeployCategoryType;
builtin: boolean;
}; };
export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProvider> = new Map( export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProvider> = new Map(
@ -387,7 +395,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
NOTICE: The following order determines the order displayed at the frontend. NOTICE: The following order determines the order displayed at the frontend.
*/ */
[ [
[DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.LOCAL, "provider.local", DEPLOY_CATEGORIES.OTHER, "true"],
[DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.SSH, "provider.ssh", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.WEBHOOK, "provider.webhook", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.KUBERNETES_SECRET, "provider.kubernetes.secret", DEPLOY_CATEGORIES.OTHER],
@ -457,7 +465,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
[DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE], [DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE],
[DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER], [DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL], [DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL],
].map(([type, name, category]) => [ ].map(([type, name, category, builtin]) => [
type, type,
{ {
type: type as DeployProviderType, type: type as DeployProviderType,
@ -465,6 +473,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
icon: accessProvidersMap.get(type.split("-")[0])!.icon, icon: accessProvidersMap.get(type.split("-")[0])!.icon,
provider: type.split("-")[0] as AccessProviderType, provider: type.split("-")[0] as AccessProviderType,
category: category as DeployCategoryType, category: category as DeployCategoryType,
builtin: builtin === "true",
}, },
]) ])
); );

View File

@ -148,8 +148,8 @@ export type WorkflowNodeConfigForUpload = {
export type WorkflowNodeConfigForDeploy = { export type WorkflowNodeConfigForDeploy = {
certificate: string; certificate: string;
provider: string; provider: string;
providerAccessId: string; providerAccessId?: string;
providerConfig: Record<string, unknown>; providerConfig?: Record<string, unknown>;
skipOnLastSucceeded: boolean; skipOnLastSucceeded: boolean;
}; };

View File

@ -96,7 +96,7 @@
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider", "workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of host provider",
"workflow_node.deploy.form.provider_access.tooltip": "Used to deploy certificates.", "workflow_node.deploy.form.provider_access.tooltip": "Used to deploy certificates.",
"workflow_node.deploy.form.provider_access.button": "Create", "workflow_node.deploy.form.provider_access.button": "Create",
"workflow_node.deploy.form.provider_access.guide_for_local": "Tips: Due to the form validations, youe need to select an authorization for local deployment also, even if it means nothing.", "workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.",
"workflow_node.deploy.form.certificate.label": "Certificate", "workflow_node.deploy.form.certificate.label": "Certificate",
"workflow_node.deploy.form.certificate.placeholder": "Please select certificate", "workflow_node.deploy.form.certificate.placeholder": "Please select certificate",
"workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.", "workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.",

View File

@ -96,7 +96,7 @@
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权", "workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书,注意与申请阶段所需的 DNS 提供商相区分。", "workflow_node.deploy.form.provider_access.tooltip": "用于部署证书,注意与申请阶段所需的 DNS 提供商相区分。",
"workflow_node.deploy.form.provider_access.button": "新建", "workflow_node.deploy.form.provider_access.button": "新建",
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。<br>请注意,如果你使用 Docker 安装 Certimate“本地部署”将会部署到容器内而非宿主机上。", "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate“本地”指的是容器内而非宿主机。",
"workflow_node.deploy.form.certificate.label": "待部署证书", "workflow_node.deploy.form.certificate.label": "待部署证书",
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书", "workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
"workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。",