diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index e4467a40..56234b95 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -25,6 +25,7 @@ import ( pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" pAliyunVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-vod" pAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" + pAPISIX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/apisix" pAWSACM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-acm" pAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" pAWSIAM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-iam" @@ -333,6 +334,23 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeAPISIX: + { + access := domain.AccessConfigForAPISIX{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pAPISIX.NewDeployer(&pAPISIX.DeployerConfig{ + ServerUrl: access.ServerUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pAPISIX.ResourceType(maputil.GetString(options.ProviderServiceConfig, "resourceType")), + CertificateId: maputil.GetString(options.ProviderServiceConfig, "certificateId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeAWSACM, domain.DeploymentProviderTypeAWSCloudFront, domain.DeploymentProviderTypeAWSIAM: { access := domain.AccessConfigForAWS{} diff --git a/internal/domain/access.go b/internal/domain/access.go index c6071aef..36e9fb40 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -41,6 +41,12 @@ type AccessConfigForAliyun struct { ResourceGroupId string `json:"resourceGroupId,omitempty"` } +type AccessConfigForAPISIX struct { + ServerUrl string `json:"serverUrl"` + ApiKey string `json:"apiKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForAWS struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index e0ace3ac..2ca69241 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -14,6 +14,7 @@ const ( AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") + AccessProviderTypeAPISIX = AccessProviderType("apisix") AccessProviderTypeAWS = AccessProviderType("aws") AccessProviderTypeAzure = AccessProviderType("azure") AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") @@ -193,6 +194,7 @@ const ( DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss") DeploymentProviderTypeAliyunVOD = DeploymentProviderType(AccessProviderTypeAliyun + "-vod") DeploymentProviderTypeAliyunWAF = DeploymentProviderType(AccessProviderTypeAliyun + "-waf") + DeploymentProviderTypeAPISIX = DeploymentProviderType(AccessProviderTypeAWS + "-apisix") DeploymentProviderTypeAWSACM = DeploymentProviderType(AccessProviderTypeAWS + "-acm") DeploymentProviderTypeAWSCloudFront = DeploymentProviderType(AccessProviderTypeAWS + "-cloudfront") DeploymentProviderTypeAWSIAM = DeploymentProviderType(AccessProviderTypeAWS + "-iam") diff --git a/internal/pkg/core/deployer/providers/apisix/apisix.go b/internal/pkg/core/deployer/providers/apisix/apisix.go new file mode 100644 index 00000000..922b3597 --- /dev/null +++ b/internal/pkg/core/deployer/providers/apisix/apisix.go @@ -0,0 +1,125 @@ +package apisix + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + apisixsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/apisix" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" + typeutil "github.com/usual2970/certimate/internal/pkg/utils/type" +) + +type DeployerConfig struct { + // APISIX 服务地址。 + ServerUrl string `json:"serverUrl"` + // APISIX Admin API Key。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId string `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *apisixsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ServerUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, fmt.Errorf("failed to create sdk client: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == "" { + return errors.New("config `certificateId` is required") + } + + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + // 更新 SSL 证书 + // REF: https://apisix.apache.org/zh/docs/apisix/admin-api/#ssl + updateSSLReq := &apisixsdk.UpdateSSLRequest{ + ID: d.config.CertificateId, + Cert: typeutil.ToPtr(certPEM), + Key: typeutil.ToPtr(privkeyPEM), + SNIs: typeutil.ToPtr(certX509.DNSNames), + Type: typeutil.ToPtr("server"), + Status: typeutil.ToPtr(int32(1)), + } + updateSSLResp, err := d.sdkClient.UpdateSSL(updateSSLReq) + d.logger.Debug("sdk request 'apisix.UpdateSSL'", slog.Any("request", updateSSLReq), slog.Any("response", updateSSLResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'apisix.UpdateSSL': %w", err) + } + + return nil +} + +func createSdkClient(serverUrl, apiKey string, skipTlsVerify bool) (*apisixsdk.Client, error) { + if _, err := url.Parse(serverUrl); err != nil { + return nil, errors.New("invalid apisix server url") + } + + if apiKey == "" { + return nil, errors.New("invalid apisix api key") + } + + client := apisixsdk.NewClient(serverUrl, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/apisix/apisix_test.go b/internal/pkg/core/deployer/providers/apisix/apisix_test.go new file mode 100644 index 00000000..d7d7dffd --- /dev/null +++ b/internal/pkg/core/deployer/providers/apisix/apisix_test.go @@ -0,0 +1,77 @@ +package apisix_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/apisix" +) + +var ( + fInputCertPath string + fInputKeyPath string + fServerUrl string + fApiKey string + fCertificateId string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_APISIX_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") + flag.StringVar(&fCertificateId, argsPrefix+"CERTIFICATEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./apisix_test.go -args \ + --CERTIMATE_DEPLOYER_APISIX_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_APISIX_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_APISIX_SERVERURL="http://127.0.0.1:9080" \ + --CERTIMATE_DEPLOYER_APISIX_APIKEY="your-api-key" \ + --CERTIMATE_DEPLOYER_APISIX_CERTIFICATEID="your-cerficiate-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SERVERURL: %v", fServerUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ServerUrl: fServerUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: fCertificateId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/apisix/consts.go b/internal/pkg/core/deployer/providers/apisix/consts.go new file mode 100644 index 00000000..75aa1b60 --- /dev/null +++ b/internal/pkg/core/deployer/providers/apisix/consts.go @@ -0,0 +1,8 @@ +package apisix + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/sdk3rd/apisix/api.go b/internal/pkg/sdk3rd/apisix/api.go new file mode 100644 index 00000000..7ebfba04 --- /dev/null +++ b/internal/pkg/sdk3rd/apisix/api.go @@ -0,0 +1,16 @@ +package apisix + +import ( + "fmt" + "net/http" +) + +func (c *Client) UpdateSSL(req *UpdateSSLRequest) (*UpdateSSLResponse, error) { + if req.ID == "" { + return nil, fmt.Errorf("1panel api error: invalid parameter: ID") + } + + resp := &UpdateSSLResponse{} + err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/ssls/%s", req.ID), req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/apisix/client.go b/internal/pkg/sdk3rd/apisix/client.go new file mode 100644 index 00000000..66784824 --- /dev/null +++ b/internal/pkg/sdk3rd/apisix/client.go @@ -0,0 +1,87 @@ +package apisix + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + client *resty.Client +} + +func NewClient(serverUrl, apiKey string) *Client { + client := resty.New(). + SetBaseURL(strings.TrimRight(serverUrl, "/")+"/apisix/admin"). + SetHeader("User-Agent", "certimate"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + req.Header.Set("X-API-KEY", apiKey) + + return nil + }) + + return &Client{ + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("apisix api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("apisix api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result interface{}) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("apisix api error: failed to unmarshal response: %w", err) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/apisix/models.go b/internal/pkg/sdk3rd/apisix/models.go new file mode 100644 index 00000000..960c8489 --- /dev/null +++ b/internal/pkg/sdk3rd/apisix/models.go @@ -0,0 +1,12 @@ +package apisix + +type UpdateSSLRequest struct { + ID string `json:"-"` + Cert *string `json:"cert,omitempty"` + Key *string `json:"key,omitempty"` + SNIs *[]string `json:"snis,omitempty"` + Type *string `json:"type,omitempty"` + Status *int32 `json:"status,omitempty"` +} + +type UpdateSSLResponse struct{} diff --git a/ui/public/imgs/providers/apisix.svg b/ui/public/imgs/providers/apisix.svg new file mode 100644 index 00000000..55b6e4f2 --- /dev/null +++ b/ui/public/imgs/providers/apisix.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 4bb1d439..7b7640d3 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -15,6 +15,7 @@ import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; import AccessFormACMECAConfig from "./AccessFormACMECAConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; +import AccessFormAPISIXConfig from "./AccessFormAPISIXConfig"; import AccessFormAWSConfig from "./AccessFormAWSConfig"; import AccessFormAzureConfig from "./AccessFormAzureConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; @@ -194,6 +195,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.ALIYUN: return ; + case ACCESS_PROVIDERS.APISIX: + return ; case ACCESS_PROVIDERS.AWS: return ; case ACCESS_PROVIDERS.AZURE: diff --git a/ui/src/components/access/AccessFormAPISIXConfig.tsx b/ui/src/components/access/AccessFormAPISIXConfig.tsx new file mode 100644 index 00000000..856a6565 --- /dev/null +++ b/ui/src/components/access/AccessFormAPISIXConfig.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForAPISIX } from "@/domain/access"; + +type AccessFormAPISIXConfigFieldValues = Nullish; + +export type AccessFormAPISIXConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormAPISIXConfigFieldValues; + onValuesChange?: (values: AccessFormAPISIXConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormAPISIXConfigFieldValues => { + return { + serverUrl: "http://:9180/", + apiKey: "", + }; +}; + +const AccessFormAPISIXConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormAPISIXConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serverUrl: z.string().url(t("common.errmsg.url_invalid")), + apiKey: z.string().trim().nonempty(t("access.form.apisix_api_key.placeholder")), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormAPISIXConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index d9a34629..59e5146e 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -33,6 +33,7 @@ import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLB import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; import DeployNodeConfigFormAliyunVODConfig from "./DeployNodeConfigFormAliyunVODConfig"; import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; +import DeployNodeConfigFormAPISIXConfig from "./DeployNodeConfigFormAPISIXConfig"; import DeployNodeConfigFormAWSACMConfig from "./DeployNodeConfigFormAWSACMConfig"; import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig"; import DeployNodeConfigFormAWSIAMConfig from "./DeployNodeConfigFormAWSIAMConfig"; @@ -234,6 +235,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.ALIYUN_WAF: return ; + case DEPLOYMENT_PROVIDERS.APISIX: + return ; case DEPLOYMENT_PROVIDERS.AWS_ACM: return ; case DEPLOYMENT_PROVIDERS.AWS_CLOUDFRONT: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAPISIXConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAPISIXConfig.tsx new file mode 100644 index 00000000..0fd67674 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAPISIXConfig.tsx @@ -0,0 +1,81 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormAPISIXConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string; +}>; + +export type DeployNodeConfigFormAPISIXConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAPISIXConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAPISIXConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormAPISIXConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormAPISIXConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAPISIXConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.apisix_resource_type.placeholder"), + }), + certificateId: z + .string() + .nullish() + .refine((v) => fieldResourceType !== RESOURCE_TYPE_CERTIFICATE || !!v?.trim(), t("workflow_node.deploy.form.apisix_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormAPISIXConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 1ab660a3..cdf01c89 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -10,6 +10,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForACMECA | AccessConfigForACMEHttpReq | AccessConfigForAliyun + | AccessConfigForAPISIX | AccessConfigForAWS | AccessConfigForAzure | AccessConfigForBaiduCloud @@ -105,6 +106,12 @@ export type AccessConfigForAliyun = { resourceGroupId?: string; }; +export type AccessConfigForAPISIX = { + serverUrl: string; + apiKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForAWS = { accessKeyId: string; secretAccessKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index d57cc380..9956a8c9 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -8,6 +8,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ ACMECA: "acmeca", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", + APISIX: "apisix", AWS: "aws", AZURE: "azure", BAIDUCLOUD: "baiducloud", @@ -141,6 +142,7 @@ export const accessProvidersMap: Map [ type, diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 23946f6d..63860919 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -72,6 +72,11 @@ "access.form.aliyun_resource_group_id.label": "Aliyun resource group ID (Optional)", "access.form.aliyun_resource_group_id.placeholder": "Please enter Aliyun resource group ID", "access.form.aliyun_resource_group_id.tooltip": "For more information, see https://www.alibabacloud.com/help/en/resource-management/product-overview", + "access.form.apisix_server_url.label": "APISIX server URL", + "access.form.apisix_server_url.placeholder": "Please enter APISIX server URL", + "access.form.apisix_api_key.label": "APISIX Admin API key", + "access.form.apisix_api_key.placeholder": "Please enter APISIX Admin API key", + "access.form.apisix_api_key.tooltip": "For more information, see https://apisix.apache.org/docs/apisix/admin-api/", "access.form.aws_access_key_id.label": "AWS AccessKeyId", "access.form.aws_access_key_id.placeholder": "Please enter AWS AccessKeyId", "access.form.aws_access_key_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index b2f5ce54..f72c0ca1 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -24,6 +24,7 @@ "provider.aliyun.waf": "Alibaba Cloud - WAF (Web Application Firewall)", "provider.akamai": "Akamai", "provider.akamai.cdn": "Akamai - CDN (Content Delivery Network)", + "provider.apisix": "Apache APISIX", "provider.aws": "AWS", "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index a482f835..ac8b8c60 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -285,6 +285,12 @@ "workflow_node.deploy.form.aliyun_waf_domain.label": "Alibaba Cloud WAF domain (Optional)", "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "Please enter Alibaba Cloud WAF domain name", "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "For more information, see https://waf.console.aliyun.com", + "workflow_node.deploy.form.apisix_resource_type.label": "Resource type", + "workflow_node.deploy.form.apisix_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.apisix_resource_type.option.certificate.label": "SSL certificate", + "workflow_node.deploy.form.apisix_certificate_id.label": "APISIX certificate ID", + "workflow_node.deploy.form.apisix_certificate_id.placeholder": "Please enter APISIX certificate ID", + "workflow_node.deploy.form.apisix_certificate_id.tooltip": "You can find it on APISIX WebUI.", "workflow_node.deploy.form.aws_acm_region.label": "AWS ACM Region", "workflow_node.deploy.form.aws_acm_region.placeholder": "Please enter AWS ACM region (e.g. us-east-1)", "workflow_node.deploy.form.aws_acm_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index b4cc3846..d55c3066 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -72,6 +72,11 @@ "access.form.aliyun_resource_group_id.label": "阿里云资源组 ID(可选)", "access.form.aliyun_resource_group_id.placeholder": "请输入阿里云资源组 ID", "access.form.aliyun_resource_group_id.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/resource-management/resource-group/product-overview", + "access.form.apisix_server_url.label": "APISIX 服务地址", + "access.form.apisix_server_url.placeholder": "请输入 APISIX 服务地址", + "access.form.apisix_api_key.label": "APISIX Admin API Key", + "access.form.apisix_api_key.placeholder": "请输入 APISIX Admin API Key", + "access.form.apisix_api_key.tooltip": "这是什么?请参阅 https://apisix.apache.org/zh/docs/apisix/admin-api/", "access.form.aws_access_key_id.label": "AWS AccessKeyId", "access.form.aws_access_key_id.placeholder": "请输入 AWS AccessKeyId", "access.form.aws_access_key_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index a5ff9e05..bf542b34 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -24,6 +24,7 @@ "provider.aliyun.waf": "阿里云 - Web 应用防火墙 WAF", "provider.akamai": "Akamai", "provider.akamai.cdn": "Akamai - 内容分发网络 CDN", + "provider.apisix": "Apache APISIX", "provider.aws": "AWS", "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index df6b0a1b..f88b055a 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -284,6 +284,12 @@ "workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 接入域名(可选)", "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 接入域名(支持泛域名)", "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "这是什么?请参阅 waf.console.aliyun.com

不填写时,将替换实例的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.apisix_resource_type.label": "证书部署方式", + "workflow_node.deploy.form.apisix_resource_type.placeholder": "请选择证书部署方式", + "workflow_node.deploy.form.apisix_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.apisix_certificate_id.label": "APISIX 证书 ID", + "workflow_node.deploy.form.apisix_certificate_id.placeholder": "请输入 APISIX 证书 ID", + "workflow_node.deploy.form.apisix_certificate_id.tooltip": "请登录 APISIX 控制台查看。", "workflow_node.deploy.form.aws_acm_region.label": "AWS ACM 服务区域", "workflow_node.deploy.form.aws_acm_region.placeholder": "请输入 AWS ACM 服务区域(例如:us-east-1)", "workflow_node.deploy.form.aws_acm_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints",