certimate/ui/src/components/workflow/DeployToSSH.tsx

350 lines
12 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select";
import { Button } from "../ui/button";
import { DeployFormProps } from "./DeployForm";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useShallow } from "zustand/shallow";
import { usePanel } from "./PanelProvider";
import { useEffect, useState } from "react";
import i18n from "@/i18n";
import { WorkflowNode } from "@/domain/workflow";
import { Textarea } from "../ui/textarea";
import AccessSelect from "./AccessSelect";
import AccessEditDialog from "../certimate/AccessEditDialog";
import { Plus } from "lucide-react";
const selectState = (state: WorkflowState) => ({
updateNode: state.updateNode,
getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
});
const t = i18n.t;
const formSchema = z
.object({
providerType: z.string(),
access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
certificate: z.string().min(1),
format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], {
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(),
jksAlias: z.string().optional(),
jksKeypass: z.string().optional(),
jksStorepass: 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"],
})
.refine((data) => (data.format === "jks" ? !!data.jksAlias?.trim() : true), {
message: t("domain.deployment.form.file_jks_alias.placeholder"),
path: ["jksAlias"],
})
.refine((data) => (data.format === "jks" ? !!data.jksKeypass?.trim() : true), {
message: t("domain.deployment.form.file_jks_keypass.placeholder"),
path: ["jksKeypass"],
})
.refine((data) => (data.format === "jks" ? !!data.jksStorepass?.trim() : true), {
message: t("domain.deployment.form.file_jks_storepass.placeholder"),
path: ["jksStorepass"],
});
const DeployToSSH = ({ data }: DeployFormProps) => {
const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
const { hidePanel } = usePanel();
const { t } = useTranslation();
const [beforeOutput, setBeforeOutput] = useState<WorkflowNode[]>([]);
useEffect(() => {
const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
console.log(rs);
setBeforeOutput(rs);
}, [data]);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
providerType: "ssh",
access: data.config?.access as string,
certificate: data.config?.certificate as string,
format: (data.config?.format as "pem" | "pfx" | "jks") || "pem",
certPath: (data.config?.certPath as string) || "/etc/ssl/certs/cert.crt",
keyPath: (data.config?.keyPath as string) || "/etc/ssl/private/cert.key",
pfxPassword: (data.config?.pfxPassword as string) || "",
jksAlias: (data.config?.jksAlias as string) || "",
jksKeypass: (data.config?.jksKeypass as string) || "",
jksStorepass: (data.config?.jksStorepass as string) || "",
preCommand: (data.config?.preCommand as string) || "",
command: (data.config?.command as string) || "service nginx reload",
},
});
const format = form.watch("format");
const certPath = form.watch("certPath");
useEffect(() => {
if (format === "pem" && /(.pfx|.jks)$/.test(certPath)) {
form.setValue("certPath", certPath.replace(/(.pfx|.jks)$/, ".crt"));
} else if (format === "pfx" && /(.crt|.jks)$/.test(certPath)) {
form.setValue("certPath", certPath.replace(/(.crt|.jks)$/, ".pfx"));
} else if (format === "jks" && /(.crt|.pfx)$/.test(certPath)) {
form.setValue("certPath", certPath.replace(/(.crt|.pfx)$/, ".jks"));
}
}, [format]);
const onSubmit = async (config: z.infer<typeof formSchema>) => {
updateNode({ ...data, config, validated: true });
hidePanel();
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="access"
render={({ field }) => (
<FormItem>
<FormLabel className="flex justify-between">
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEditDialog
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.add")}
</div>
}
op="add"
outConfigType="ssh"
/>
</FormLabel>
<FormControl>
<AccessSelect
{...field}
value={field.value}
onValueChange={(value) => {
form.setValue("access", value);
}}
providerType="ssh"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="certificate"
render={({ field }) => (
<FormItem>
<FormLabel>{t("workflow.common.certificate.label")}</FormLabel>
<FormControl>
<Select
{...field}
value={field.value}
onValueChange={(value) => {
form.setValue("certificate", value);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("workflow.common.certificate.placeholder")} />
</SelectTrigger>
<SelectContent>
{beforeOutput.map((item) => (
<>
<SelectGroup key={item.id}>
<SelectLabel>{item.name}</SelectLabel>
{item.output?.map((output) => (
<SelectItem key={output.name} value={`${item.id}#${output.name}`}>
<div>
{item.name}-{output.label}
</div>
</SelectItem>
))}
</SelectGroup>
</>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="format"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.file_format.label")}</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="pem">PEM</SelectItem>
<SelectItem value="pfx">PFX</SelectItem>
<SelectItem value="jks">JKS</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="certPath"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.file_cert_path.label")}</FormLabel>
<FormControl>
<Input placeholder={t("domain.deployment.form.file_cert_path.label")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="keyPath"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="输入密钥路径" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{format === "pfx" && (
<FormField
control={form.control}
name="pfxPassword"
render={({ field }) => (
<FormItem>
<FormLabel>PFX </FormLabel>
<FormControl>
<Input type="password" placeholder="输入 PFX 密码" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{format === "jks" && (
<>
<FormField
control={form.control}
name="jksAlias"
render={({ field }) => (
<FormItem>
<FormLabel>JKS </FormLabel>
<FormControl>
<Input placeholder="输入 JKS 别名" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="jksKeypass"
render={({ field }) => (
<FormItem>
<FormLabel>JKS Keypass</FormLabel>
<FormControl>
<Input type="password" placeholder="输入 JKS Keypass" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="jksStorepass"
render={({ field }) => (
<FormItem>
<FormLabel>JKS Storepass</FormLabel>
<FormControl>
<Input type="password" placeholder="输入 JKS Storepass" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
<FormField
control={form.control}
name="preCommand"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.shell_pre_command.label")}</FormLabel>
<FormControl>
<Textarea placeholder={t("domain.deployment.form.shell_pre_command.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="command"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.shell_command.label")}</FormLabel>
<FormControl>
<Textarea placeholder={t("domain.deployment.form.shell_command.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
);
};
export default DeployToSSH;