mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-15 08:59:51 +00:00
feat: support specified format on deployment to local/ssh
This commit is contained in:
parent
e7870e2b05
commit
adad5d86ba
5
go.mod
5
go.mod
@ -24,8 +24,10 @@ require (
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
|
||||
golang.org/x/crypto v0.28.0
|
||||
k8s.io/api v0.31.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
k8s.io/client-go v0.31.1
|
||||
software.sslmate.com/src/go-pkcs12 v0.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -58,7 +60,6 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.31.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
@ -151,7 +152,7 @@ require (
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -763,3 +763,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M=
|
||||
software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
|
@ -2,12 +2,15 @@ package deployer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"software.sslmate.com/src/go-pkcs12"
|
||||
|
||||
"github.com/usual2970/certimate/internal/applicant"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
@ -180,3 +183,32 @@ func getDeployVariables(conf domain.DeployConfig) map[string]string {
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
func convertPemToPfx(certificate string, privateKey string, password string) ([]byte, error) {
|
||||
// TODO: refactor
|
||||
|
||||
certBlock, _ := pem.Decode([]byte(certificate))
|
||||
if certBlock == nil {
|
||||
return nil, fmt.Errorf("failed to decode pem")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse pem: %w", err)
|
||||
}
|
||||
|
||||
privkeyBlock, _ := pem.Decode([]byte(privateKey))
|
||||
if privkeyBlock == nil {
|
||||
return nil, fmt.Errorf("failed to decode pem")
|
||||
}
|
||||
privkey, err := x509.ParsePKCS1PrivateKey(privkeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse pem: %w", err)
|
||||
}
|
||||
|
||||
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode as pfx %w", err)
|
||||
}
|
||||
|
||||
return pfxData, nil
|
||||
}
|
||||
|
@ -51,10 +51,6 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||
|
||||
// 写入证书和私钥文件
|
||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", "pem") {
|
||||
case "pfx":
|
||||
// TODO: pfx
|
||||
return fmt.Errorf("not implemented")
|
||||
|
||||
case "pem":
|
||||
if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
||||
@ -67,6 +63,18 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("保存私钥成功", nil))
|
||||
|
||||
case "pfx":
|
||||
pfxData, err := convertPemToPfx(d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("pfxPassword"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
||||
}
|
||||
|
||||
if err := fs.WriteFile(d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||
return fmt.Errorf("failed to save certificate file: %w", err)
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("保存证书成功", nil))
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@ -87,15 +95,15 @@ func (d *LocalDeployer) execCommand(command string) (string, string, error) {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch d.option.DeployConfig.GetConfigAsString("shell") {
|
||||
case "sh":
|
||||
cmd = exec.Command("sh", "-c", command)
|
||||
|
||||
case "cmd":
|
||||
cmd = exec.Command("cmd", "/C", command)
|
||||
|
||||
case "powershell":
|
||||
cmd = exec.Command("powershell", "-Command", command)
|
||||
|
||||
case "sh":
|
||||
cmd = exec.Command("sh", "-c", command)
|
||||
|
||||
case "":
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/C", command)
|
||||
|
@ -60,20 +60,34 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
|
||||
d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
if err := d.uploadFile(client, d.option.Certificate.Certificate, d.option.DeployConfig.GetConfigAsString("certPath")); err != nil {
|
||||
return fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
// 上传证书和私钥文件
|
||||
switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", "pem") {
|
||||
case "pem":
|
||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
|
||||
return fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||
|
||||
if err := d.writeSftpFileString(client, d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
|
||||
return fmt.Errorf("failed to upload private key file: %w", err)
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
||||
|
||||
case "pfx":
|
||||
pfxData, err := convertPemToPfx(d.option.Certificate.Certificate, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("pfxPassword"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert pem to pfx %w", err)
|
||||
}
|
||||
|
||||
if err := d.writeSftpFile(client, d.option.DeployConfig.GetConfigAsString("certPath"), pfxData); err != nil {
|
||||
return fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
|
||||
|
||||
// 上传私钥
|
||||
if err := d.uploadFile(client, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("keyPath")); err != nil {
|
||||
return fmt.Errorf("failed to upload private key file: %w", err)
|
||||
}
|
||||
|
||||
d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
|
||||
|
||||
// 执行命令
|
||||
command := d.option.DeployConfig.GetConfigAsString("command")
|
||||
if command != "" {
|
||||
@ -133,7 +147,11 @@ func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string
|
||||
return stdoutBuf.String(), stderrBuf.String(), err
|
||||
}
|
||||
|
||||
func (d *SSHDeployer) uploadFile(client *ssh.Client, path string, content string) error {
|
||||
func (d *SSHDeployer) writeSftpFileString(client *ssh.Client, path string, content string) error {
|
||||
return d.writeSftpFile(client, path, []byte(content))
|
||||
}
|
||||
|
||||
func (d *SSHDeployer) writeSftpFile(client *ssh.Client, path string, data []byte) error {
|
||||
sftpCli, err := sftp.NewClient(client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sftp client: %w", err)
|
||||
@ -150,7 +168,7 @@ func (d *SSHDeployer) uploadFile(client *ssh.Client, path string, content string
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write([]byte(content))
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to remote file: %w", err)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 与 `WriteFile` 类似,但写入的是字符串内容。
|
||||
// 与 [WriteFile] 类似,但写入的是字符串内容。
|
||||
//
|
||||
// 入参:
|
||||
// - path: 文件路径。
|
||||
|
@ -102,7 +102,7 @@ const DeployToHuaweiCloudCDN = () => {
|
||||
onValueChange={(value) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.resourceType = value?.trim();
|
||||
draft.config.resourceType = value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
|
@ -6,6 +6,7 @@ import { produce } from "immer";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useDeployEditContext } from "./DeployEdit";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -20,8 +21,10 @@ const DeployToLocal = () => {
|
||||
setDeploy({
|
||||
...data,
|
||||
config: {
|
||||
format: "pem",
|
||||
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||
pfxPassword: "",
|
||||
shell: "sh",
|
||||
preCommand: "",
|
||||
command: "sudo service nginx reload",
|
||||
@ -34,15 +37,34 @@ const DeployToLocal = () => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
const formSchema = z.object({
|
||||
certPath: z.string().min(1, t("domain.deployment.form.file_cert_path.placeholder")),
|
||||
keyPath: z.string().min(1, t("domain.deployment.form.file_key_path.placeholder")),
|
||||
shell: z.union([z.literal("sh"), z.literal("cmd"), z.literal("powershell")], {
|
||||
message: t("domain.deployment.form.shell.placeholder"),
|
||||
}),
|
||||
preCommand: z.string().optional(),
|
||||
command: z.string().optional(),
|
||||
});
|
||||
const formSchema = z
|
||||
.object({
|
||||
format: z.union([z.literal("pem"), z.literal("pfx")], {
|
||||
message: t("domain.deployment.form.file_format.placeholder"),
|
||||
}),
|
||||
certPath: z
|
||||
.string()
|
||||
.min(1, t("domain.deployment.form.file_cert_path.placeholder"))
|
||||
.max(255, t("common.errmsg.string_max", { max: 255 })),
|
||||
keyPath: z
|
||||
.string()
|
||||
.min(0, t("domain.deployment.form.file_key_path.placeholder"))
|
||||
.max(255, t("common.errmsg.string_max", { max: 255 })),
|
||||
pfxPassword: z.string().optional(),
|
||||
shell: z.union([z.literal("sh"), z.literal("cmd"), z.literal("powershell")], {
|
||||
message: t("domain.deployment.form.shell.placeholder"),
|
||||
}),
|
||||
preCommand: z.string().optional(),
|
||||
command: z.string().optional(),
|
||||
})
|
||||
.refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), {
|
||||
message: t("domain.deployment.form.file_key_path.placeholder"),
|
||||
path: ["keyPath"],
|
||||
})
|
||||
.refine((data) => (data.format === "pfx" ? !!data.pfxPassword?.trim() : true), {
|
||||
message: t("domain.deployment.form.file_pfx_password.placeholder"),
|
||||
path: ["pfxPassword"],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const res = formSchema.safeParse(data.config);
|
||||
@ -67,6 +89,26 @@ const DeployToLocal = () => {
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.config?.format === "pem") {
|
||||
if (data.config.certPath && data.config.certPath.endsWith(".pfx")) {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.certPath = data.config!.certPath.replace(/.pfx$/, ".crt");
|
||||
});
|
||||
setDeploy(newData);
|
||||
}
|
||||
} else if (data.config?.format === "pfx") {
|
||||
if (data.config.certPath && data.config.certPath.endsWith(".crt")) {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.certPath = data.config!.certPath.replace(/.crt$/, ".pfx");
|
||||
});
|
||||
setDeploy(newData);
|
||||
}
|
||||
}
|
||||
}, [data.config?.format]);
|
||||
|
||||
const getOptionCls = (val: string) => {
|
||||
if (data.config?.shell === val) {
|
||||
return "border-primary dark:border-primary";
|
||||
@ -78,6 +120,31 @@ const DeployToLocal = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col space-y-8">
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
||||
<Select
|
||||
value={data?.config?.format}
|
||||
onValueChange={(value) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.format = value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="pem">PEM</SelectItem>
|
||||
<SelectItem value="pfx">PFX</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.format}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
|
||||
<Input
|
||||
@ -95,22 +162,47 @@ const DeployToLocal = () => {
|
||||
<div className="text-red-600 text-sm mt-1">{error?.certPath}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.keyPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.keyPath = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
||||
</div>
|
||||
{data.config?.format === "pem" ? (
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.keyPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.keyPath = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{data.config?.format === "pfx" ? (
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.pfxPassword}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.pfxPassword = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.pfxPassword}</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.shell.label")}</Label>
|
||||
|
@ -5,6 +5,7 @@ import { produce } from "immer";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useDeployEditContext } from "./DeployEdit";
|
||||
|
||||
@ -18,8 +19,10 @@ const DeployToSSH = () => {
|
||||
setDeploy({
|
||||
...data,
|
||||
config: {
|
||||
format: "pem",
|
||||
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||
pfxPassword: "",
|
||||
preCommand: "",
|
||||
command: "sudo service nginx reload",
|
||||
},
|
||||
@ -31,12 +34,31 @@ const DeployToSSH = () => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
const formSchema = z.object({
|
||||
certPath: z.string().min(1, t("domain.deployment.form.file_cert_path.placeholder")),
|
||||
keyPath: z.string().min(1, t("domain.deployment.form.file_key_path.placeholder")),
|
||||
preCommand: z.string().optional(),
|
||||
command: z.string().optional(),
|
||||
});
|
||||
const formSchema = z
|
||||
.object({
|
||||
format: z.union([z.literal("pem"), z.literal("pfx")], {
|
||||
message: t("domain.deployment.form.file_format.placeholder"),
|
||||
}),
|
||||
certPath: z
|
||||
.string()
|
||||
.min(1, t("domain.deployment.form.file_cert_path.placeholder"))
|
||||
.max(255, t("common.errmsg.string_max", { max: 255 })),
|
||||
keyPath: z
|
||||
.string()
|
||||
.min(0, t("domain.deployment.form.file_key_path.placeholder"))
|
||||
.max(255, t("common.errmsg.string_max", { max: 255 })),
|
||||
pfxPassword: z.string().optional(),
|
||||
preCommand: z.string().optional(),
|
||||
command: z.string().optional(),
|
||||
})
|
||||
.refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), {
|
||||
message: t("domain.deployment.form.file_key_path.placeholder"),
|
||||
path: ["keyPath"],
|
||||
})
|
||||
.refine((data) => (data.format === "pfx" ? !!data.pfxPassword?.trim() : true), {
|
||||
message: t("domain.deployment.form.file_pfx_password.placeholder"),
|
||||
path: ["pfxPassword"],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const res = formSchema.safeParse(data.config);
|
||||
@ -59,9 +81,54 @@ const DeployToSSH = () => {
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.config?.format === "pem") {
|
||||
if (data.config.certPath && data.config.certPath.endsWith(".pfx")) {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.certPath = data.config!.certPath.replace(/.pfx$/, ".crt");
|
||||
});
|
||||
setDeploy(newData);
|
||||
}
|
||||
} else if (data.config?.format === "pfx") {
|
||||
if (data.config.certPath && data.config.certPath.endsWith(".crt")) {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.certPath = data.config!.certPath.replace(/.crt$/, ".pfx");
|
||||
});
|
||||
setDeploy(newData);
|
||||
}
|
||||
}
|
||||
}, [data.config?.format]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col space-y-8">
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_format.label")}</Label>
|
||||
<Select
|
||||
value={data?.config?.format}
|
||||
onValueChange={(value) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.format = value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="pem">PEM</SelectItem>
|
||||
<SelectItem value="pfx">PFX</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.format}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
|
||||
<Input
|
||||
@ -79,22 +146,47 @@ const DeployToSSH = () => {
|
||||
<div className="text-red-600 text-sm mt-1">{error?.certPath}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.keyPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.keyPath = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
||||
</div>
|
||||
{data.config?.format === "pem" ? (
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_key_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_key_path.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.keyPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.keyPath = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.keyPath}</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{data.config?.format === "pfx" ? (
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.pfxPassword}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
draft.config ??= {};
|
||||
draft.config.pfxPassword = e.target.value?.trim();
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.pfxPassword}</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
|
||||
|
@ -86,10 +86,14 @@
|
||||
"domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder": "Please enter ELB loadbalancer ID",
|
||||
"domain.deployment.form.huaweicloud_elb_listener_id.label": "Listener ID",
|
||||
"domain.deployment.form.huaweicloud_elb_listener_id.placeholder": "Please enter ELB listener ID",
|
||||
"domain.deployment.form.file_format.label": "Certificate Format",
|
||||
"domain.deployment.form.file_format.placeholder": "Please select certificate format",
|
||||
"domain.deployment.form.file_cert_path.label": "Certificate Save Path",
|
||||
"domain.deployment.form.file_cert_path.placeholder": "Please enter certificate save path",
|
||||
"domain.deployment.form.file_key_path.label": "Private Key Save Path",
|
||||
"domain.deployment.form.file_key_path.placeholder": "Please enter private key save path",
|
||||
"domain.deployment.form.file_pfx_password.label": "PFX Output Password",
|
||||
"domain.deployment.form.file_pfx_password.placeholder": "Please enter PFX output password",
|
||||
"domain.deployment.form.shell.label": "Shell",
|
||||
"domain.deployment.form.shell.placeholder": "Please select shell environment",
|
||||
"domain.deployment.form.shell.option.sh.label": "POSIX Bash (Linux)",
|
||||
|
@ -86,10 +86,14 @@
|
||||
"domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder": "请输入负载均衡器 ID(可从华为云控制面板获取)",
|
||||
"domain.deployment.form.huaweicloud_elb_listener_id.label": "监听器 ID",
|
||||
"domain.deployment.form.huaweicloud_elb_listener_id.placeholder": "请输入监听器 ID(可从华为云控制面板获取)",
|
||||
"domain.deployment.form.file_key_path.label": "私钥保存路径",
|
||||
"domain.deployment.form.file_key_path.placeholder": "请输入私钥保存路径",
|
||||
"domain.deployment.form.file_format.label": "证书格式",
|
||||
"domain.deployment.form.file_format.placeholder": "请选择证书格式",
|
||||
"domain.deployment.form.file_cert_path.label": "证书保存路径",
|
||||
"domain.deployment.form.file_cert_path.placeholder": "请输入证书保存路径",
|
||||
"domain.deployment.form.file_key_path.label": "私钥保存路径",
|
||||
"domain.deployment.form.file_key_path.placeholder": "请输入私钥保存路径",
|
||||
"domain.deployment.form.file_pfx_password.label": "PFX 导出密码",
|
||||
"domain.deployment.form.file_pfx_password.placeholder": "请输入 PFX 导出密码",
|
||||
"domain.deployment.form.shell.label": "Shell",
|
||||
"domain.deployment.form.shell.placeholder": "请选择命令执行环境",
|
||||
"domain.deployment.form.shell_pre_command.label": "前置命令",
|
||||
|
@ -209,7 +209,7 @@ const Dashboard = () => {
|
||||
{t("history.log")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh] overflow-y-auto">
|
||||
{deployment.log.check && (
|
||||
<>
|
||||
{deployment.log.check.map((item: Log) => {
|
||||
|
@ -104,7 +104,7 @@ const History = () => {
|
||||
{t("history.log")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh] overflow-y-auto">
|
||||
{deployment.log.check && (
|
||||
<>
|
||||
{deployment.log.check.map((item: Log) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user