mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-22 21:05:48 +08:00
Merge pull request #957 from fudiwei/dev
This commit is contained in:
commit
ea979510ce
@ -1,5 +1,11 @@
|
||||
# 贡献指南
|
||||
|
||||
<div align="center">
|
||||
|
||||
中文 | [English](CONTRIBUTING_EN.md)
|
||||
|
||||
</div>
|
||||
|
||||
非常感谢你抽出时间来帮助改进 Certimate!以下是向 Certimate 提交 Pull Request 时的操作指南。
|
||||
|
||||
我们需要保持敏捷和快速迭代,同时也希望确保贡献者能获得尽可能流畅的参与体验。这份贡献指南旨在帮助你熟悉代码库和我们的工作方式,让你可以尽快进入有趣的开发环节。
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
# Contribution Guide
|
||||
|
||||
<div align="center">
|
||||
|
||||
[中文](CONTRIBUTING.md) | English
|
||||
|
||||
</div>
|
||||
|
||||
Thank you for taking the time to improve Certimate! Below is a guide for submitting a PR (Pull Request) to the Certimate repository.
|
||||
|
||||
We need to be nimble and ship fast given where we are, but we also want to make sure that contributors like you get as smooth an experience at contributing as possible. We've assembled this contribution guide for that purpose, aiming at getting you familiarized with the codebase & how we work with contributors, so you could quickly jump to the fun part.
|
||||
|
||||
@ -44,10 +44,10 @@ func init() {
|
||||
PreCommand: xmaps.GetString(options.ProviderExtendedConfig, "preCommand"),
|
||||
PostCommand: xmaps.GetString(options.ProviderExtendedConfig, "postCommand"),
|
||||
OutputFormat: ssh.OutputFormatType(xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(ssh.OUTPUT_FORMAT_PEM))),
|
||||
OutputKeyPath: xmaps.GetString(options.ProviderExtendedConfig, "keyPath"),
|
||||
OutputCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPath"),
|
||||
OutputServerCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"),
|
||||
OutputIntermediaCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"),
|
||||
OutputKeyPath: xmaps.GetString(options.ProviderExtendedConfig, "keyPath"),
|
||||
PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||
JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||
JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"),
|
||||
|
||||
@ -101,6 +101,26 @@ func (wd *workflowDispatcher) Bootup(ctx context.Context) error {
|
||||
}
|
||||
|
||||
wd.booted = true
|
||||
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// 无需准确获取,不用加锁
|
||||
if len(wd.processingTasks) < wd.concurrency && len(wd.pendingRunQueue) > 0 {
|
||||
wd.tryNextAsync()
|
||||
}
|
||||
default:
|
||||
if !wd.booted {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -305,9 +325,9 @@ func (wd *workflowDispatcher) tryExecuteAsync(task *taskInfo) {
|
||||
})
|
||||
|
||||
// 执行工作流
|
||||
wd.syslog.Info(fmt.Sprintf("workflow run #%s was started", task.RunId))
|
||||
wd.syslog.Info(fmt.Sprintf("workflow run #%s (work#%s) was started", task.RunId, task.WorkflowId))
|
||||
we.Invoke(task.ctx, workflowRun.WorkflowId, workflowRun.Id, workflowRun.Graph)
|
||||
wd.syslog.Info(fmt.Sprintf("workflow run #%s was stopped", task.RunId))
|
||||
wd.syslog.Info(fmt.Sprintf("workflow run #%s (work#%s) was stopped", task.RunId, task.WorkflowId))
|
||||
}
|
||||
|
||||
func (wd *workflowDispatcher) tryNextAsync() {
|
||||
@ -341,6 +361,7 @@ func (wd *workflowDispatcher) tryNextAsync() {
|
||||
task := &taskInfo{WorkflowId: workflowRun.WorkflowId, RunId: workflowRun.Id, ctx: ctxRun, cancel: ctxCancel}
|
||||
wd.pendingRunQueue = append(wd.pendingRunQueue[:i], wd.pendingRunQueue[i+1:]...)
|
||||
wd.processingTasks[pendingRunId] = task
|
||||
wd.syslog.Info(fmt.Sprintf("workflow run #%s (work#%s) is being dispatched ...", task.RunId, task.WorkflowId))
|
||||
go func() { wd.tryExecuteAsync(task) }()
|
||||
return
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@ -239,54 +238,17 @@ func (p *provider) createSshClient(conn net.Conn, host string, port int32, authM
|
||||
}
|
||||
}
|
||||
|
||||
authentications := make([]ssh.AuthMethod, 0)
|
||||
switch authMethod {
|
||||
case AUTH_METHOD_NONE:
|
||||
{
|
||||
}
|
||||
return xssh.NewClient(conn, host, int(port), username)
|
||||
|
||||
case AUTH_METHOD_PASSWORD:
|
||||
{
|
||||
authentications = append(authentications, ssh.Password(password))
|
||||
authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
if len(questions) == 1 {
|
||||
return []string{password}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", "))
|
||||
}))
|
||||
}
|
||||
return xssh.NewClientWithPassword(conn, host, int(port), username, password)
|
||||
|
||||
case AUTH_METHOD_KEY:
|
||||
{
|
||||
var signer ssh.Signer
|
||||
var err error
|
||||
|
||||
if keyPassphrase != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase))
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey([]byte(key))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authentications = append(authentications, ssh.PublicKeys(signer))
|
||||
}
|
||||
return xssh.NewClientWithKey(conn, host, int(port), username, key, keyPassphrase)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: authentications,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssh.NewClient(sshConn, chans, reqs), nil
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
@ -52,6 +51,8 @@ type SSLDeployerProviderConfig struct {
|
||||
PostCommand string `json:"postCommand,omitempty"`
|
||||
// 输出证书格式。
|
||||
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
||||
// 输出私钥文件路径。
|
||||
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||
// 输出证书文件路径。
|
||||
OutputCertPath string `json:"outputCertPath,omitempty"`
|
||||
// 输出服务器证书文件路径。
|
||||
@ -60,8 +61,6 @@ type SSLDeployerProviderConfig struct {
|
||||
// 输出中间证书文件路径。
|
||||
// 选填。
|
||||
OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"`
|
||||
// 输出私钥文件路径。
|
||||
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||
// PFX 导出密码。
|
||||
// 证书格式为 PFX 时必填。
|
||||
PfxPassword string `json:"pfxPassword,omitempty"`
|
||||
@ -192,6 +191,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
|
||||
// 上传证书和私钥文件
|
||||
switch d.config.OutputFormat {
|
||||
case OUTPUT_FORMAT_PEM:
|
||||
if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
|
||||
|
||||
if err := xssh.WriteRemoteString(client, d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
@ -211,11 +215,6 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
|
||||
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath))
|
||||
}
|
||||
|
||||
if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
|
||||
|
||||
case OUTPUT_FORMAT_PFX:
|
||||
pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword)
|
||||
if err != nil {
|
||||
@ -282,56 +281,19 @@ func createSshClient(conn net.Conn, host string, port int32, authMethod string,
|
||||
}
|
||||
}
|
||||
|
||||
authentications := make([]ssh.AuthMethod, 0)
|
||||
switch authMethod {
|
||||
case AUTH_METHOD_NONE:
|
||||
{
|
||||
}
|
||||
return xssh.NewClient(conn, host, int(port), username)
|
||||
|
||||
case AUTH_METHOD_PASSWORD:
|
||||
{
|
||||
authentications = append(authentications, ssh.Password(password))
|
||||
authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
if len(questions) == 1 {
|
||||
return []string{password}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", "))
|
||||
}))
|
||||
}
|
||||
return xssh.NewClientWithPassword(conn, host, int(port), username, password)
|
||||
|
||||
case AUTH_METHOD_KEY:
|
||||
{
|
||||
var signer ssh.Signer
|
||||
var err error
|
||||
|
||||
if keyPassphrase != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase))
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey([]byte(key))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authentications = append(authentications, ssh.PublicKeys(signer))
|
||||
}
|
||||
return xssh.NewClientWithKey(conn, host, int(port), username, key, keyPassphrase)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported auth method '%s'", authMethod)
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: authentications,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssh.NewClient(sshConn, chans, reqs), nil
|
||||
}
|
||||
|
||||
func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) {
|
||||
|
||||
59
pkg/utils/ssh/client.go
Normal file
59
pkg/utils/ssh/client.go
Normal file
@ -0,0 +1,59 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func NewClient(conn net.Conn, host string, port int, username string) (*ssh.Client, error) {
|
||||
authentications := make([]ssh.AuthMethod, 0)
|
||||
return newClientWithAuthMethods(conn, host, port, username, authentications)
|
||||
}
|
||||
|
||||
func NewClientWithPassword(conn net.Conn, host string, port int, username string, password string) (*ssh.Client, error) {
|
||||
authentications := make([]ssh.AuthMethod, 0)
|
||||
authentications = append(authentications, ssh.Password(password))
|
||||
authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
if len(questions) == 1 {
|
||||
return []string{password}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", "))
|
||||
}))
|
||||
return newClientWithAuthMethods(conn, host, port, username, authentications)
|
||||
}
|
||||
|
||||
func NewClientWithKey(conn net.Conn, host string, port int, username string, key, keyPassphrase string) (*ssh.Client, error) {
|
||||
var signer ssh.Signer
|
||||
var err error
|
||||
if keyPassphrase != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase))
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey([]byte(key))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authentications := make([]ssh.AuthMethod, 0)
|
||||
authentications = append(authentications, ssh.PublicKeys(signer))
|
||||
return newClientWithAuthMethods(conn, host, port, username, authentications)
|
||||
}
|
||||
|
||||
func newClientWithAuthMethods(conn net.Conn, host string, port int, username string, authMethods []ssh.AuthMethod) (*ssh.Client, error) {
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: authMethods,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssh.NewClient(sshConn, chans, reqs), nil
|
||||
}
|
||||
@ -4,10 +4,63 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Certimate - Your Trusted Partner in SSL Automation</title>
|
||||
</head>
|
||||
<body style="pointer-events: auto !important">
|
||||
<div id="root"></div>
|
||||
<div id="root">
|
||||
<div class="apploader"></div>
|
||||
<style>
|
||||
.apploader {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 100%;
|
||||
background-color: #f97316;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
-moz-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-animation: apploader infinite linear 0.75s;
|
||||
-moz-animation: apploader infinite linear 0.75s;
|
||||
animation: apploader infinite linear 0.75s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes apploader {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes apploader {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-moz-transform: scale(0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
-moz-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes apploader {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(0.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useContext, useMemo, useRef } from "react";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { yaml } from "@codemirror/lang-yaml";
|
||||
import { StreamLanguage } from "@codemirror/language";
|
||||
@ -9,6 +9,7 @@ import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
|
||||
import CodeMirror, { type ReactCodeMirrorProps, type ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||
import { useFocusWithin, useHover } from "ahooks";
|
||||
import { theme } from "antd";
|
||||
import DisabledContext from "antd/es/config-provider/DisabledContext";
|
||||
|
||||
import { useBrowserTheme } from "@/hooks";
|
||||
import { mergeCls } from "@/utils/css";
|
||||
@ -16,13 +17,17 @@ import { mergeCls } from "@/utils/css";
|
||||
export interface CodeInputProps extends Omit<ReactCodeMirrorProps, "extensions" | "lang" | "theme"> {
|
||||
disabled?: boolean;
|
||||
language?: string | string[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const CodeInput = ({ className, style, disabled, language, ...props }: CodeInputProps) => {
|
||||
const CodeInput = ({ className, style, disabled, language, readOnly, ...props }: CodeInputProps) => {
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const { theme: browserTheme } = useBrowserTheme();
|
||||
|
||||
const injectedDisabled = useContext(DisabledContext);
|
||||
const mergedDisabled = disabled ?? injectedDisabled;
|
||||
|
||||
const cmRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const isFocusing = useFocusWithin(cmRef.current?.editor);
|
||||
const isHovering = useHover(cmRef.current?.editor);
|
||||
@ -74,8 +79,8 @@ const CodeInput = ({ className, style, disabled, language, ...props }: CodeInput
|
||||
paddingInline: themeToken.Input?.paddingInline,
|
||||
fontSize: themeToken.Input?.inputFontSize,
|
||||
lineHeight: themeToken.lineHeight,
|
||||
color: disabled ? themeToken.colorTextDisabled : themeToken.colorText,
|
||||
backgroundColor: disabled
|
||||
color: mergedDisabled ? themeToken.colorTextDisabled : themeToken.colorText,
|
||||
backgroundColor: mergedDisabled
|
||||
? themeToken.colorBgContainerDisabled
|
||||
: isFocusing
|
||||
? (themeToken.Input?.activeBg ?? themeToken.colorBgContainer)
|
||||
@ -106,6 +111,7 @@ const CodeInput = ({ className, style, disabled, language, ...props }: CodeInput
|
||||
indentOnInput: false,
|
||||
}}
|
||||
extensions={cmExtensions}
|
||||
readOnly={readOnly || mergedDisabled}
|
||||
theme={cmTheme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { type ChangeEvent, useRef } from "react";
|
||||
import { type ChangeEvent, useContext, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconFileImport } from "@tabler/icons-react";
|
||||
import { Button, type ButtonProps, Input, type UploadProps } from "antd";
|
||||
import DisabledContext from "antd/es/config-provider/DisabledContext";
|
||||
import { type TextAreaProps } from "antd/es/input/TextArea";
|
||||
|
||||
import { readFileAsText } from "@/utils/file";
|
||||
@ -16,6 +17,9 @@ export interface TextFileInputProps extends Omit<TextAreaProps, "onChange"> {
|
||||
const TextFileInput = ({ className, style, accept, disabled, readOnly, uploadText, uploadButtonProps, onChange, ...props }: TextFileInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const injectedDisabled = useContext(DisabledContext);
|
||||
const mergedDisabled = disabled ?? injectedDisabled;
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
@ -35,10 +39,10 @@ const TextFileInput = ({ className, style, accept, disabled, readOnly, uploadTex
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Input.TextArea {...props} disabled={disabled} readOnly={readOnly} onChange={(e) => onChange?.(e.target.value)} />
|
||||
<Input.TextArea {...props} disabled={mergedDisabled} readOnly={readOnly} onChange={(e) => onChange?.(e.target.value)} />
|
||||
{!readOnly && (
|
||||
<>
|
||||
<Button {...uploadButtonProps} block disabled={disabled} icon={<IconFileImport size="1.25em" />} onClick={handleButtonClick}>
|
||||
<Button {...uploadButtonProps} block disabled={mergedDisabled} icon={<IconFileImport size="1.25em" />} onClick={handleButtonClick}>
|
||||
{uploadText ?? t("common.text.import_from_file")}
|
||||
</Button>
|
||||
<input ref={fileInputRef} type="file" style={{ display: "none" }} accept={accept} onChange={handleFileChange} />
|
||||
|
||||
@ -27,13 +27,7 @@ const AccessConfigFormFieldsProviderACMECA = () => {
|
||||
<Input placeholder={t("access.form.acmeca_endpoint.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.acmeca_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmeca_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.acmeca_eab_kid.label")} rules={[formRule]}>
|
||||
<Input allowClear autoComplete="new-password" placeholder={t("access.form.acmeca_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
@ -42,7 +36,6 @@ const AccessConfigFormFieldsProviderACMECA = () => {
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.acmeca_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmeca_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.acmeca_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderActalisSSL = () => {
|
||||
@ -17,24 +19,21 @@ const AccessConfigFormFieldsProviderActalisSSL = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.actalisssl_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.actalisssl_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.actalisssl_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.actalisssl_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.actalisssl_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.actalisssl_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.actalisssl_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -53,11 +52,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
return z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.actalisssl_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.actalisssl_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -40,12 +40,12 @@ const AccessConfigFormFieldsProviderBaotaPanel = () => {
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
initialValue={initialValues.allowInsecureConnections}
|
||||
label={t("access.form.common_allow_insecure_conns.label")}
|
||||
label={t("access.form.shared_allow_insecure_conns.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
|
||||
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderGobalSignAtlas = () => {
|
||||
@ -17,24 +19,21 @@ const AccessConfigFormFieldsProviderGobalSignAtlas = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.globalsignatlas_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.globalsignatlas_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.globalsignatlas_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.globalsignatlas_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.globalsignatlas_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.globalsignatlas_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.globalsignatlas_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -53,11 +52,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
return z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.globalsignatlas_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.globalsignatlas_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderGoogleTrustServices = () => {
|
||||
@ -17,24 +19,21 @@ const AccessConfigFormFieldsProviderGoogleTrustServices = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.googletrustservices_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.googletrustservices_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.googletrustservices_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.googletrustservices_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.googletrustservices_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.googletrustservices_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.googletrustservices_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -53,11 +52,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
return z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.googletrustservices_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.googletrustservices_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -39,12 +39,12 @@ const AccessConfigFormFieldsProviderPowerDNS = () => {
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
initialValue={initialValues.allowInsecureConnections}
|
||||
label={t("access.form.common_allow_insecure_conns.label")}
|
||||
label={t("access.form.shared_allow_insecure_conns.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
|
||||
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@ -50,12 +50,12 @@ const AccessConfigFormFieldsProviderRatPanel = () => {
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
initialValue={initialValues.allowInsecureConnections}
|
||||
label={t("access.form.common_allow_insecure_conns.label")}
|
||||
label={t("access.form.shared_allow_insecure_conns.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
|
||||
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderSSLCom = () => {
|
||||
@ -17,24 +19,21 @@ const AccessConfigFormFieldsProviderSSLCom = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.sslcom_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sslcom_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.sslcom_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.sslcom_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sslcom_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.sslcom_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.sslcom_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -53,11 +52,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
return z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.sslcom_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.sslcom_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -39,12 +39,12 @@ const AccessConfigFormFieldsProviderSafeLine = () => {
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
initialValue={initialValues.allowInsecureConnections}
|
||||
label={t("access.form.common_allow_insecure_conns.label")}
|
||||
label={t("access.form.shared_allow_insecure_conns.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
|
||||
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderSectigo = () => {
|
||||
@ -33,24 +35,21 @@ const AccessConfigFormFieldsProviderSectigo = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.sectigo_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sectigo_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.sectigo_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.sectigo_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.sectigo_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.sectigo_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.sectigo_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -71,11 +70,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
validationType: z.string().nonempty(t("access.form.sectigo_validation_type.placeholder")),
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.sectigo_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.sectigo_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -300,12 +300,12 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF
|
||||
<Form.Item
|
||||
name={[parentNamePath, "allowInsecureConnections"]}
|
||||
initialValue={initialValues.allowInsecureConnections}
|
||||
label={t("access.form.common_allow_insecure_conns.label")}
|
||||
label={t("access.form.shared_allow_insecure_conns.label")}
|
||||
rules={[formRule]}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren={t("access.form.common_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.common_allow_insecure_conns.switch.off")}
|
||||
checkedChildren={t("access.form.shared_allow_insecure_conns.switch.on")}
|
||||
unCheckedChildren={t("access.form.shared_allow_insecure_conns.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@ -3,6 +3,8 @@ import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Tips from "@/components/Tips";
|
||||
|
||||
import { useFormNestedFieldsContext } from "./_context";
|
||||
|
||||
const AccessConfigFormFieldsProviderZeroSSL = () => {
|
||||
@ -17,24 +19,21 @@ const AccessConfigFormFieldsProviderZeroSSL = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabKid"]}
|
||||
initialValue={initialValues.eabKid}
|
||||
label={t("access.form.zerossl_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.zerossl_eab_kid.placeholder")} />
|
||||
<Form.Item name={[parentNamePath, "eabKid"]} initialValue={initialValues.eabKid} label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "eabHmacKey"]}
|
||||
initialValue={initialValues.eabHmacKey}
|
||||
label={t("access.form.zerossl_eab_hmac_key.label")}
|
||||
label={t("access.form.shared_acme_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.zerossl_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -53,11 +52,11 @@ const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) =
|
||||
return z.object({
|
||||
eabKid: z
|
||||
.string()
|
||||
.min(1, t("access.form.zerossl_eab_kid.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string()
|
||||
.min(1, t("access.form.zerossl_eab_hmac_key.placeholder"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
};
|
||||
|
||||
@ -42,6 +42,10 @@
|
||||
"access.form.provider.placeholder": "Please select a provider",
|
||||
"access.form.provider.help": "DNS provider: The provider that hosts your domain names and manages your DNS records. <br>Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.",
|
||||
"access.form.provider.search.placeholder": "Search provider ...",
|
||||
"access.form.shared_acme_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.shared_acme_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.shared_acme_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.shared_acme_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.shared_allow_insecure_conns.label": "Insecure SSL/TLS connections",
|
||||
"access.form.shared_allow_insecure_conns.switch.on": "Allow",
|
||||
"access.form.shared_allow_insecure_conns.switch.off": "Disallow",
|
||||
@ -78,12 +82,7 @@
|
||||
"access.form.acmehttpreq_password.label": "HTTP Basic Auth password (Optional)",
|
||||
"access.form.acmehttpreq_password.placeholder": "Please enter HTTP Basic Auth password",
|
||||
"access.form.acmehttpreq_password.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.actalisssl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.actalisssl_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.actalisssl_eab_kid.tooltip": "For more information, see <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.actalisssl_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.actalisssl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.actalisssl_eab.guide": "Learn more about using EAB key in Actalis SSL: <br><a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.aliyun_access_key_id.label": "Aliyun AccessKeyId",
|
||||
"access.form.aliyun_access_key_id.placeholder": "Please enter Aliyun AccessKeyId",
|
||||
"access.form.aliyun_access_key_id.tooltip": "For more information, see <a href=\"https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair\" target=\"_blank\">https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair</a>",
|
||||
@ -284,18 +283,8 @@
|
||||
"access.form.goedge_access_key.label": "GoEdge AccessKey",
|
||||
"access.form.goedge_access_key.placeholder": "Please enter GoEdge AccessKey",
|
||||
"access.form.goedge_access_key.tooltip": "For more information, see <a href=\"https://goedge.cloud/docs/API/Auth.md\" target=\"_blank\">https://goedge.cloud/docs/API/Auth.md</a>",
|
||||
"access.form.globalsignatlas_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.globalsignatlas_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.globalsignatlas_eab_kid.tooltip": "For more information, see <a href=\"https://www.globalsign.com/en/acme-automated-certificate-management\" target=\"_blank\">https://www.globalsign.com/en/acme-automated-certificate-management</a>",
|
||||
"access.form.globalsignatlas_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.globalsignatlas_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.globalsignatlas_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.globalsign.com/en/acme-automated-certificate-management\" target=\"_blank\">https://www.globalsign.com/en/acme-automated-certificate-management</a>",
|
||||
"access.form.googletrustservices_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.globalsignatlas_eab.guide": "Learn more about using EAB key in GlobalSign Atlas: <br><a href=\"https://www.globalsign.com/en/acme-automated-certificate-management\" target=\"_blank\">https://www.globalsign.com/en/acme-automated-certificate-management</a>",
|
||||
"access.form.googletrustservices_eab.guide": "Learn more about using EAB key in Google Trust Services: <br><a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.hetzner_api_token.label": "Hetzner API token",
|
||||
"access.form.hetzner_api_token.placeholder": "Please enter Hetzner API token",
|
||||
"access.form.hetzner_api_token.tooltip": "For more information, see <a href=\"https://docs.hetzner.com/cloud/api/getting-started/generating-api-token\" target=\"_blank\">https://docs.hetzner.com/cloud/api/getting-started/generating-api-token</a>",
|
||||
@ -425,12 +414,7 @@
|
||||
"access.form.sectigo_validation_type.option.dv.label": "DV (Domain Validation)",
|
||||
"access.form.sectigo_validation_type.option.ov.label": "OV (Organization Validation)",
|
||||
"access.form.sectigo_validation_type.option.ev.label": "EV (Extended Validation)",
|
||||
"access.form.sectigo_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sectigo_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.sectigo_eab_kid.tooltip": "For more information, see <a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.sectigo_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.sectigo_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.sectigo_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.sectigo_eab.guide": "Learn more about using EAB key in Sectigo: <br><a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.slackbot_token.label": "Slack bot token",
|
||||
"access.form.slackbot_token.placeholder": "Please enter Slack bot token",
|
||||
"access.form.slackbot_token.tooltip": "For more information, see <a href=\"https://docs.slack.dev/authentication/tokens#bot\" target=\"_blank\">https://docs.slack.dev/authentication/tokens#bot</a>",
|
||||
@ -465,12 +449,7 @@
|
||||
"access.form.ssh_jump_servers.errmsg.invalid": "Please configure valid jump servers",
|
||||
"access.form.ssh_jump_servers.item.label": "Jump server",
|
||||
"access.form.ssh_jump_servers.add": "Add jump server",
|
||||
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.sslcom_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab.guide": "Learn more about using EAB key in SSL.com: <br><a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.telegrambot_token.label": "Telegram bot token",
|
||||
"access.form.telegrambot_token.placeholder": "Please enter Telegram bot token",
|
||||
"access.form.telegrambot_token.tooltip": "How to get it? Please refer to <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||
@ -559,10 +538,5 @@
|
||||
"access.form.westcn_api_password.label": "West.cn API password",
|
||||
"access.form.westcn_api_password.placeholder": "Please enter West.cn API password",
|
||||
"access.form.westcn_api_password.tooltip": "For more information, see <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.zerossl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.placeholder": "Please enter ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"access.form.zerossl_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.zerossl_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.zerossl_eab_hmac_key.tooltip": "For more information, see <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
"access.form.zerossl_eab.guide": "Learn more about using EAB key in ZeroSSL: <br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
||||
@ -21,11 +21,11 @@
|
||||
"workflow.action.enable.button": "Activate",
|
||||
"workflow.action.enable.errmsg.unpublished": "Please complete the orchestration and publish the changes first",
|
||||
"workflow.action.disable.button": "Deactivate",
|
||||
"workflow.action.run.button": "Run",
|
||||
"workflow.action.run.menu": "Run",
|
||||
"workflow.action.run.modal.title": "Run workflow",
|
||||
"workflow.action.run.modal.content": "You have unpublished changes. Do you really want to run this workflow based on the last published version?",
|
||||
"workflow.action.run.prompt": "Running... Please check the history later",
|
||||
"workflow.action.execute.button": "Execute",
|
||||
"workflow.action.execute.menu": "Execute",
|
||||
"workflow.action.execute.modal.title": "Execute workflow",
|
||||
"workflow.action.execute.modal.content": "You have unpublished changes. Do you really want to execute this workflow based on the last published version?",
|
||||
"workflow.action.execute.prompt": "Running... Please check the history later",
|
||||
|
||||
"workflow.props.name": "Name",
|
||||
"workflow.props.description": "Description",
|
||||
@ -42,13 +42,14 @@
|
||||
|
||||
"workflow.new.title": "Create Workflow",
|
||||
"workflow.new.subtitle": "Using a workflow to monitor, apply, deploy and notify.",
|
||||
"workflow.new.templates.title": "Choose a Workflow Template",
|
||||
"workflow.new.button.create": "Create from blank",
|
||||
"workflow.new.button.import": "Import from file",
|
||||
"workflow.new.templates.title": "Choose a Template",
|
||||
"workflow.new.templates.subtitle": "Use these template workflows to quickly initialize your automated certificate management workflow.",
|
||||
"workflow.new.templates.template.standard.title": "Standard template",
|
||||
"workflow.new.templates.template.standard.description": "A standard operating procedure that includes application, deployment, and notification steps.",
|
||||
"workflow.new.templates.template.certtest.title": "Monitoring template",
|
||||
"workflow.new.templates.template.certtest.description": "A monitoring operating procedure that includes monitoring, and notification steps.",
|
||||
"workflow.new.templates.template.empty.title": "Empty template",
|
||||
"workflow.new.templates.template.empty.description": "Customize all the contents of the workflow from the beginning.",
|
||||
"workflow.new.templates.default_name": "Untitled workflow",
|
||||
"workflow.new.templates.default_description": "Created at {{date}}",
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@
|
||||
"access.form.provider.placeholder": "请选择提供商",
|
||||
"access.form.provider.help": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。",
|
||||
"access.form.provider.search.placeholder": "搜索提供商……",
|
||||
"access.form.shared_acme_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.shared_acme_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.shared_acme_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.shared_acme_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.shared_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
|
||||
"access.form.shared_allow_insecure_conns.switch.on": "允许",
|
||||
"access.form.shared_allow_insecure_conns.switch.off": "不允许",
|
||||
@ -77,12 +81,7 @@
|
||||
"access.form.acmehttpreq_password.label": "HTTP 基本认证密码(可选)",
|
||||
"access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码",
|
||||
"access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||
"access.form.actalisssl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.actalisssl_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.actalisssl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.actalisssl_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.actalisssl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.actalisssl_eab.guide": "点击下方链接了解如何获取 Actalis SSL EAB:<br><a href=\"https://www.actalis.com/manage-with-acme\" target=\"_blank\">https://www.actalis.com/manage-with-acme</a>",
|
||||
"access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId",
|
||||
"access.form.aliyun_access_key_id.placeholder": "请输入阿里云 AccessKeyId",
|
||||
"access.form.aliyun_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair\" target=\"_blank\">https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair</a>",
|
||||
@ -283,18 +282,8 @@
|
||||
"access.form.goedge_access_key.label": "GoEdge AccessKey",
|
||||
"access.form.goedge_access_key.placeholder": "请输入 GoEdge AccessKey",
|
||||
"access.form.goedge_access_key.tooltip": "这是什么?请参阅 <a href=\"https://goedge.cloud/docs/API/Auth.md\" target=\"_blank\">https://goedge.cloud/docs/API/Auth.md</a>",
|
||||
"access.form.globalsignatlas_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.globalsignatlas_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.globalsignatlas_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://globalsign.cn/acme-automated-certificate-management\" target=\"_blank\">https://globalsign.cn/acme-automated-certificate-management</a>",
|
||||
"access.form.globalsignatlas_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.globalsignatlas_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.globalsignatlas_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://globalsign.cn/acme-automated-certificate-management\" target=\"_blank\">https://globalsign.cn/acme-automated-certificate-management</a>",
|
||||
"access.form.googletrustservices_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.googletrustservices_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.googletrustservices_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.googletrustservices_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.globalsignatlas_eab.guide": "点击下方链接了解如何获取 GlobalSign Atlas EAB:<br><a href=\"https://globalsign.cn/acme-automated-certificate-management\" target=\"_blank\">https://globalsign.cn/acme-automated-certificate-management</a>",
|
||||
"access.form.googletrustservices_eab.guide": "点击下方链接了解如何获取 Google Trust Services EAB:<br><a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"access.form.hetzner_api_token.label": "Hetzner API Token",
|
||||
"access.form.hetzner_api_token.placeholder": "请输入 Hetzner API Token",
|
||||
"access.form.hetzner_api_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.hetzner.com/cloud/api/getting-started/generating-api-token\" target=\"_blank\">https://docs.hetzner.com/cloud/api/getting-started/generating-api-token</a>",
|
||||
@ -424,12 +413,7 @@
|
||||
"access.form.sectigo_validation_type.option.dv.label": "DV(域名型)",
|
||||
"access.form.sectigo_validation_type.option.ov.label": "OV(企业型)",
|
||||
"access.form.sectigo_validation_type.option.ev.label": "EV(增强型)",
|
||||
"access.form.sectigo_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sectigo_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.sectigo_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.sectigo_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.sectigo_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.sectigo_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.sectigo_eab.guide": "点击下方链接了解如何获取 Sectigo EAB:<br><a href=\"https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme\" target=\"_blank\">https://www.sectigo.com/enterprise-solutions/certificate-manager/integrations-acme</a>",
|
||||
"access.form.slackbot_token.label": "Slack 机器人 Token",
|
||||
"access.form.slackbot_token.placeholder": "请输入 Slack 机器人 Token",
|
||||
"access.form.slackbot_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.slack.dev/authentication/tokens#bot\" target=\"_blank\">https://docs.slack.dev/authentication/tokens#bot</a>",
|
||||
@ -464,12 +448,7 @@
|
||||
"access.form.ssh_jump_servers.errmsg.invalid": "请配置有效的跳板机信息",
|
||||
"access.form.ssh_jump_servers.item.label": "跳板机",
|
||||
"access.form.ssh_jump_servers.add": "添加跳板机",
|
||||
"access.form.sslcom_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.sslcom_eab.guide": "点击下方链接了解如何获取 SSL.com EAB:<br><a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||
"access.form.telegrambot_token.label": "Telegram 机器人 API Token",
|
||||
"access.form.telegrambot_token.placeholder": "请输入 Telegram 机器人 API Token",
|
||||
"access.form.telegrambot_token.tooltip": "如何获取此参数?请参阅 <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||
@ -558,10 +537,5 @@
|
||||
"access.form.westcn_api_password.label": "西部数码 API 密码",
|
||||
"access.form.westcn_api_password.placeholder": "请输入西部数码 API 密码",
|
||||
"access.form.westcn_api_password.tooltip": "这是什么?请参阅 <a href=\"https://www.west.cn/CustomerCenter/doc/apiv2.html#12u3001u8eabu4efdu9a8cu8bc10a3ca20id3d12u3001u8eabu4efdu9a8cu8bc13e203ca3e\" target=\"_blank\">https://www.west.cn/CustomerCenter/doc/apiv2.html</a>",
|
||||
"access.form.zerossl_eab_kid.label": "ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.placeholder": "请输入 ACME EAB KID",
|
||||
"access.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>",
|
||||
"access.form.zerossl_eab_hmac_key.label": "ACME EAB HMAC Key",
|
||||
"access.form.zerossl_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
"access.form.zerossl_eab.guide": "点击下方链接了解如何获取 ZeroSSL EAB:<br><a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>"
|
||||
}
|
||||
|
||||
@ -21,11 +21,11 @@
|
||||
"workflow.action.enable.button": "启用",
|
||||
"workflow.action.enable.errmsg.unpublished": "请先完成流程编排并发布更改",
|
||||
"workflow.action.disable.button": "停用",
|
||||
"workflow.action.run.button": "运行",
|
||||
"workflow.action.run.menu": "运行工作流",
|
||||
"workflow.action.run.modal.title": "运行工作流",
|
||||
"workflow.action.run.modal.content": "你有尚未发布的更改。确定要以最近一次发布的版本继续运行吗?",
|
||||
"workflow.action.run.prompt": "运行中……请稍后查看运行历史",
|
||||
"workflow.action.execute.button": "运行",
|
||||
"workflow.action.execute.menu": "运行工作流",
|
||||
"workflow.action.execute.modal.title": "运行工作流",
|
||||
"workflow.action.execute.modal.content": "你有尚未发布的更改。确定要以最近一次发布的版本继续运行吗?",
|
||||
"workflow.action.execute.prompt": "运行中……请稍后查看运行历史",
|
||||
|
||||
"workflow.props.name": "名称",
|
||||
"workflow.props.description": "描述",
|
||||
@ -42,13 +42,14 @@
|
||||
|
||||
"workflow.new.title": "新建工作流",
|
||||
"workflow.new.subtitle": "使用工作流来监控域名、申请证书、部署上传和发送通知。",
|
||||
"workflow.new.templates.title": "选择工作流模板",
|
||||
"workflow.new.templates.template.standard.title": "标准模板",
|
||||
"workflow.new.templates.template.standard.description": "一个包含证书申请 + 证书部署 + 消息通知步骤的工作流程。",
|
||||
"workflow.new.templates.template.certtest.title": "监控模板",
|
||||
"workflow.new.templates.template.certtest.description": "一个包含证书监控 + 消息通知步骤的工作流程。",
|
||||
"workflow.new.templates.template.empty.title": "空白模板",
|
||||
"workflow.new.templates.template.empty.description": "从零开始自定义工作流的任务内容。",
|
||||
"workflow.new.button.create": "创建空白工作流",
|
||||
"workflow.new.button.import": "从文件导入……",
|
||||
"workflow.new.templates.title": "选择模板",
|
||||
"workflow.new.templates.subtitle": "使用这些模板快速初始化你的自动化证书管理工作流。",
|
||||
"workflow.new.templates.template.standard.title": "标准业务流程",
|
||||
"workflow.new.templates.template.standard.description": "一个包含证书申请 + 证书部署 + 消息通知步骤的工作流程,可适用于绝大多数业务场景。",
|
||||
"workflow.new.templates.template.certtest.title": "域名证书监控",
|
||||
"workflow.new.templates.template.certtest.description": "一个包含证书监控 + 消息通知步骤的工作流程,可在线上证书到期前或已过期时发出告警。",
|
||||
"workflow.new.templates.default_name": "未命名工作流",
|
||||
"workflow.new.templates.default_description": "创建于 {{date}}",
|
||||
|
||||
|
||||
@ -203,39 +203,35 @@ const InternalSharedForm = ({ children, provider }: { children?: React.ReactNode
|
||||
};
|
||||
|
||||
const InternalSharedFormEabFields = ({ i18nKey }: { i18nKey: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const hasGuide = i18n.exists(`access.form.${i18nKey}_eab.guide`);
|
||||
|
||||
const formSchema = z.object({
|
||||
endpoint: z.url(t("common.errmsg.url_invalid")),
|
||||
eabKid: z
|
||||
.string(t(`access.form.${i18nKey}_eab_kid.label`))
|
||||
.min(1, t(`access.form.${i18nKey}_eab_kid.label`))
|
||||
.string(t("access.form.shared_acme_eab_kid.label"))
|
||||
.min(1, t("access.form.shared_acme_eab_kid.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
eabHmacKey: z
|
||||
.string(t(`access.form.${i18nKey}_eab_hmac_key.label`))
|
||||
.min(1, t(`access.form.${i18nKey}_eab_hmac_key.label`))
|
||||
.string(t("access.form.shared_acme_eab_hmac_key.label"))
|
||||
.min(1, t("access.form.shared_acme_eab_hmac_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="eabKid"
|
||||
label={t(`access.form.${i18nKey}_eab_kid.label`)}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t(`access.form.${i18nKey}_eab_kid.tooltip`) }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t(`access.form.${i18nKey}_eab_kid.placeholder`)} />
|
||||
<Form.Item name="eabKid" label={t("access.form.shared_acme_eab_kid.label")} rules={[formRule]}>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t(`access.form.${i18nKey}_eab_hmac_key.label`)}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t(`access.form.${i18nKey}_eab_hmac_key.tooltip`) }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t(`access.form.${i18nKey}_eab_hmac_key.placeholder`)} />
|
||||
<Form.Item name="eabHmacKey" label={t("access.form.shared_acme_eab_hmac_key.label")} rules={[formRule]}>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.shared_acme_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item hidden={!hasGuide}>
|
||||
<Tips message={<span dangerouslySetInnerHTML={{ __html: t(`access.form.${i18nKey}_eab.guide`) }}></span>} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@ -356,20 +352,20 @@ const InternalSettingsFormProviderACMECA = () => {
|
||||
|
||||
<Form.Item
|
||||
name="eabKid"
|
||||
label={t("access.form.zerossl_eab_kid.label")}
|
||||
label={t("access.form.acmeca_eab_kid.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_kid.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmeca_eab_kid.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.zerossl_eab_kid.placeholder")} />
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.acmeca_eab_kid.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="eabHmacKey"
|
||||
label={t("access.form.zerossl_eab_hmac_key.label")}
|
||||
label={t("access.form.acmeca_eab_hmac_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.zerossl_eab_hmac_key.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmeca_eab_hmac_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.zerossl_eab_hmac_key.placeholder")} />
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.acmeca_eab_hmac_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</InternalSharedForm>
|
||||
);
|
||||
|
||||
@ -68,8 +68,8 @@ const WorkflowDetail = () => {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
if (workflow.hasDraft) {
|
||||
modal.confirm({
|
||||
title: t("workflow.action.run.modal.title"),
|
||||
content: t("workflow.action.run.modal.content"),
|
||||
title: t("workflow.action.execute.modal.title"),
|
||||
content: t("workflow.action.execute.modal.content"),
|
||||
onOk: () => resolve(void 0),
|
||||
});
|
||||
} else {
|
||||
@ -82,7 +82,7 @@ const WorkflowDetail = () => {
|
||||
|
||||
await startWorkflowRun(workflow.id);
|
||||
|
||||
message.info(t("workflow.action.run.prompt"));
|
||||
message.info(t("workflow.action.execute.prompt"));
|
||||
} catch (err) {
|
||||
setRunButtonLoading(false);
|
||||
|
||||
@ -137,7 +137,7 @@ const WorkflowDetail = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={handleActiveClick}>{workflow.enabled ? t("workflow.action.disable.button") : t("workflow.action.enable.button")}</Button>
|
||||
<Button disabled={runButtonDisabled} icon={<IconPlayerPlay size="1.25em" />} loading={runButtonLoading} type="primary" onClick={handleRunClick}>
|
||||
{t("workflow.action.run.button")}
|
||||
{t("workflow.action.execute.button")}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@ -157,8 +157,8 @@ const WorkflowList = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "run",
|
||||
label: t("workflow.action.run.menu"),
|
||||
key: "execute",
|
||||
label: t("workflow.action.execute.menu"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconPlayerPlay size="1em" />
|
||||
@ -166,7 +166,7 @@ const WorkflowList = () => {
|
||||
),
|
||||
disabled: !record.hasContent,
|
||||
onClick: () => {
|
||||
handleRecordRunClick(record);
|
||||
handleRecordExecuteClick(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -339,11 +339,11 @@ const WorkflowList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecordRunClick = async (workflow: WorkflowModel) => {
|
||||
const handleRecordExecuteClick = async (workflow: WorkflowModel) => {
|
||||
try {
|
||||
await startWorkflowRun(workflow.id);
|
||||
|
||||
message.info(t("workflow.action.run.prompt"));
|
||||
message.info(t("workflow.action.execute.prompt"));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { App, Card, Col, Row, Spin, Typography } from "antd";
|
||||
import { IconArrowRight, IconCode, IconSquarePlus2 } from "@tabler/icons-react";
|
||||
import { App, Button, Card, Spin, Typography } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import WorkflowGraphImportModal from "@/components/workflow/WorkflowGraphImportModal";
|
||||
import {
|
||||
WORKFLOW_NODE_TYPES,
|
||||
type WorkflowModel,
|
||||
@ -15,10 +17,10 @@ import {
|
||||
import { save as saveWorkflow } from "@/repository/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
const TEMPLATE_KEY_BLANK = "blank" as const;
|
||||
const TEMPLATE_KEY_STANDARD = "standard" as const;
|
||||
const TEMPLATE_KEY_CERTTEST = "certtest" as const;
|
||||
const TEMPLATE_KEY_EMPTY = "empty" as const;
|
||||
type TemplateKeys = typeof TEMPLATE_KEY_EMPTY | typeof TEMPLATE_KEY_CERTTEST | typeof TEMPLATE_KEY_STANDARD;
|
||||
type TemplateKeys = typeof TEMPLATE_KEY_BLANK | typeof TEMPLATE_KEY_CERTTEST | typeof TEMPLATE_KEY_STANDARD;
|
||||
|
||||
const WorkflowNew = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -27,21 +29,56 @@ const WorkflowNew = () => {
|
||||
|
||||
const { notification } = App.useApp();
|
||||
|
||||
const templateGridSpans = {
|
||||
xs: { flex: "100%" },
|
||||
md: { flex: "50%" },
|
||||
lg: { flex: "50%" },
|
||||
xl: { flex: "33.3333%" },
|
||||
xxl: { flex: "33.3333%" },
|
||||
};
|
||||
const templates = [
|
||||
{
|
||||
key: TEMPLATE_KEY_STANDARD,
|
||||
name: t("workflow.new.templates.template.standard.title"),
|
||||
description: t("workflow.new.templates.template.standard.description"),
|
||||
image: "/imgs/workflow/tpl-standard.png",
|
||||
},
|
||||
{
|
||||
key: TEMPLATE_KEY_CERTTEST,
|
||||
name: t("workflow.new.templates.template.certtest.title"),
|
||||
description: t("workflow.new.templates.template.certtest.description"),
|
||||
image: "/imgs/workflow/tpl-certtest.png",
|
||||
},
|
||||
];
|
||||
const [templateSelectKey, setTemplateSelectKey] = useState<TemplateKeys>();
|
||||
const [templatePending, setTemplatePending] = useState(false);
|
||||
|
||||
const [pending, setPending] = useState(false);
|
||||
const renderTemplateCard = ({ key, name, description, image }: { key: TemplateKeys; name: string; description: string; image: string }) => {
|
||||
return (
|
||||
<Card
|
||||
key={key}
|
||||
className="group/card size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src={image} />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(key)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between gap-4 overflow-hidden transition-colors group-hover/card:text-primary">
|
||||
<div className="flex-1 truncate">{name}</div>
|
||||
<IconArrowRight className="opacity-0 transition-opacity group-hover/card:opacity-100" size="1.25em" />
|
||||
</div>
|
||||
}
|
||||
description={description}
|
||||
/>
|
||||
{templatePending && <Spin spinning={templateSelectKey === key} />}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const { modalProps: workflowImportModalProps, ...workflowImportModal } = WorkflowGraphImportModal.useModal();
|
||||
|
||||
const handleTemplateClick = async (key: TemplateKeys) => {
|
||||
if (pending) return;
|
||||
if (templatePending) return;
|
||||
|
||||
setTemplateSelectKey(key);
|
||||
setTemplatePending(true);
|
||||
|
||||
try {
|
||||
let workflow = {} as WorkflowModel;
|
||||
@ -51,7 +88,7 @@ const WorkflowNew = () => {
|
||||
workflow.hasDraft = true;
|
||||
|
||||
switch (key) {
|
||||
case TEMPLATE_KEY_EMPTY:
|
||||
case TEMPLATE_KEY_BLANK:
|
||||
{
|
||||
const startNode = newNode(WORKFLOW_NODE_TYPES.START, { i18n: i18n });
|
||||
const endNode = newNode(WORKFLOW_NODE_TYPES.END, { i18n: i18n });
|
||||
@ -206,11 +243,35 @@ const WorkflowNew = () => {
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
setPending(false);
|
||||
setTemplatePending(false);
|
||||
setTemplateSelectKey(void 0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportClick = async () => {
|
||||
if (templatePending) return;
|
||||
|
||||
workflowImportModal.open().then(async (graph) => {
|
||||
setTemplatePending(true);
|
||||
|
||||
try {
|
||||
let workflow = {} as WorkflowModel;
|
||||
workflow.name = t("workflow.new.templates.default_name");
|
||||
workflow.description = t("workflow.new.templates.default_description", { date: dayjs().format("YYYY-MM-DD HH:mm") });
|
||||
workflow.graphDraft = graph;
|
||||
workflow.hasDraft = true;
|
||||
workflow = await saveWorkflow(workflow);
|
||||
navigate(`/workflows/${workflow.id}`, { replace: true });
|
||||
} catch (err) {
|
||||
notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
setTemplatePending(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-6 py-4">
|
||||
<div className="container">
|
||||
@ -219,65 +280,39 @@ const WorkflowNew = () => {
|
||||
</div>
|
||||
|
||||
<div className="container">
|
||||
<Typography.Text type="secondary">
|
||||
<div className="mb-4 text-xl">{t("workflow.new.templates.title")}</div>
|
||||
</Typography.Text>
|
||||
|
||||
<Row className="justify-stretch" gutter={[16, 16]}>
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-standard.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_STANDARD)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.standard.title")}
|
||||
description={t("workflow.new.templates.template.standard.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_STANDARD} />
|
||||
<div className="my-[6px]">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
|
||||
<Card className="size-full" styles={{ body: { padding: "1rem 1.5rem" } }} variant="borderless">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button
|
||||
className="border-none px-0 shadow-none"
|
||||
block
|
||||
icon={<IconSquarePlus2 size="1.25em" />}
|
||||
variant="solid"
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_BLANK)}
|
||||
>
|
||||
<div className="w-full text-left">{t("workflow.new.button.create")}</div>
|
||||
</Button>
|
||||
<Button className="border-none px-0 shadow-none" block icon={<IconCode size="1.25em" />} variant="solid" onClick={handleImportClick}>
|
||||
<div className="w-full text-left">{t("workflow.new.button.import")}</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-certtest.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_CERTTEST)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.certtest.title")}
|
||||
description={t("workflow.new.templates.template.certtest.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_CERTTEST} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<WorkflowGraphImportModal {...workflowImportModalProps} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-blank.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_EMPTY)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.empty.title")}
|
||||
description={t("workflow.new.templates.template.empty.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_EMPTY} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="mt-8">
|
||||
<h3>{t("workflow.new.templates.title")}</h3>
|
||||
<Typography.Text type="secondary">
|
||||
<div className="mb-4">{t("workflow.new.templates.subtitle")}</div>
|
||||
</Typography.Text>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
|
||||
{templates.map((template) => renderTemplateCard(template))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user