details improvement and unnecessary files deletion

This commit is contained in:
yoan
2024-11-24 13:36:17 +08:00
parent 37df882ed3
commit 9ff3e22c80
57 changed files with 978 additions and 5047 deletions

View File

@@ -1,109 +0,0 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { ClientResponseError } from "pocketbase";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { update } from "@/repository/access_group";
import { useConfigContext } from "@/providers/config";
type AccessGroupEditProps = {
className?: string;
trigger: React.ReactNode;
};
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { reloadAccessGroups } = useConfigContext();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const formSchema = z.object({
name: z
.string()
.min(1, "access.group.form.name.errmsg.empty")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
try {
await update({
name: data.name,
});
// 更新本地状态
reloadAccessGroups();
setOpen(false);
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
});
}
};
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild className={cn(className)}>
{trigger}
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t("access.group.add")}</DialogTitle>
</DialogHeader>
<div className="container py-3">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.group.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.group.form.name.errmsg.empty")} {...field} type="text" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
);
};
export default AccessGroupEdit;

View File

@@ -1,171 +0,0 @@
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Group } from "lucide-react";
import Show from "@/components/Show";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useToast } from "@/components/ui/use-toast";
import AccessGroupEdit from "./AccessGroupEdit";
import { accessProvidersMap } from "@/domain/access";
import { getErrMessage } from "@/lib/error";
import { useConfigContext } from "@/providers/config";
import { remove } from "@/repository/access_group";
const AccessGroupList = () => {
const {
config: { accessGroups },
reloadAccessGroups,
} = useConfigContext();
const { toast } = useToast();
const navigate = useNavigate();
const { t } = useTranslation();
const handleRemoveClick = async (id: string) => {
try {
await remove(id);
reloadAccessGroups();
} catch (e) {
toast({
title: t("common.delete.failed.message"),
description: getErrMessage(e),
variant: "destructive",
});
return;
}
};
const handleAddAccess = () => {
navigate("/access");
};
return (
<div className="mt-10">
<Show when={accessGroups.length == 0}>
<>
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Group size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">{t("access.group.domains.nodata")}</div>
<AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} className="mt-3" />
</div>
</>
</Show>
<ScrollArea className="h-[75vh] overflow-hidden">
<div className="flex gap-5 flex-wrap">
{accessGroups.map((accessGroup) => (
<Card className="w-full md:w-[350px]">
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{t("access.group.total", {
total: accessGroup.expand ? accessGroup.expand.access.length : 0,
})}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
{accessGroup.expand ? (
<>
{accessGroup.expand.access.slice(0, 3).map((access) => (
<div key={access.id} className="flex flex-col mb-3">
<div className="flex items-center">
<div className="">
<img src={accessProvidersMap.get(access.configType)!.icon} alt="provider" className="w-8 h-8"></img>
</div>
<div className="ml-3">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-200">{access.name}</div>
<div className="text-xs text-muted-foreground">{accessProvidersMap.get(access.configType)!.name}</div>
</div>
</div>
</div>
))}
</>
) : (
<>
<div className="flex text-gray-700 dark:text-gray-200 items-center">
<div>
<Group size={40} />
</div>
<div className="ml-2">{t("access.group.nodata")}</div>
</div>
</>
)}
</CardContent>
<CardFooter>
<div className="flex justify-end w-full">
<Show when={accessGroup.expand && accessGroup.expand.access.length > 0 ? true : false}>
<div>
<Button
size="sm"
variant={"link"}
onClick={() => {
navigate(`/access?accessGroupId=${accessGroup.id}&tab=access`, {
replace: true,
});
}}
>
{t("access.group.domains")}
</Button>
</div>
</Show>
<Show when={!accessGroup.expand || accessGroup.expand.access.length == 0 ? true : false}>
<div>
<Button size="sm" onClick={handleAddAccess}>
{t("access.authorization.add")}
</Button>
</div>
</Show>
<div className="ml-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
{t("common.delete")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">{t("access.group.delete")}</AlertDialogTitle>
<AlertDialogDescription>{t("access.group.delete.confirm")}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
handleRemoveClick(accessGroup.id ? accessGroup.id : "");
}}
>
{t("common.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</CardFooter>
</Card>
))}
</div>
</ScrollArea>
</div>
);
};
export default AccessGroupList;

View File

@@ -3,16 +3,12 @@ import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Plus } from "lucide-react";
import { ClientResponseError } from "pocketbase";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import AccessGroupEdit from "./AccessGroupEdit";
import { readFileContent } from "@/lib/file";
import { cn } from "@/lib/utils";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type SSHConfig } from "@/domain/access";
import { save } from "@/repository/access";
@@ -26,12 +22,7 @@ type AccessSSHFormProps = {
};
const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
const {
addAccess,
updateAccess,
reloadAccessGroups,
config: { accessGroups },
} = useConfigContext();
const { addAccess, updateAccess, reloadAccessGroups } = useConfigContext();
const fileInputRef = useRef<HTMLInputElement | null>(null);
@@ -216,52 +207,6 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
)}
/>
<FormField
control={form.control}
name="group"
render={({ field }) => (
<FormItem>
<FormLabel className="w-full flex justify-between">
<div>{t("access.authorization.form.ssh_group.label")}</div>
<AccessGroupEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("common.add")}
</div>
}
/>
</FormLabel>
<FormControl>
<Select
{...field}
value={field.value}
defaultValue="emptyId"
onValueChange={(value) => {
form.setValue("group", value);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("access.authorization.form.access_group.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="emptyId">
<div className={cn("flex items-center space-x-2 rounded cursor-pointer")}>--</div>
</SelectItem>
{accessGroups.map((item) => (
<SelectItem value={item.id ? item.id : ""} key={item.id}>
<div className={cn("flex items-center space-x-2 rounded cursor-pointer")}>{item.name}</div>
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"

View File

@@ -1,17 +0,0 @@
import { createContext, useContext, type Context as ReactContext } from "react";
import { type DeployConfig } from "@/domain/domain";
export type DeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]> = {
config: Omit<DeployConfig, "config"> & { config: T };
setConfig: (config: Omit<DeployConfig, "config"> & { config: T }) => void;
errors: { [K in keyof T]?: string };
setErrors: (error: { [K in keyof T]?: string }) => void;
};
export const Context = createContext<DeployEditContext>({} as DeployEditContext);
export function useDeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]>() {
return useContext<DeployEditContext<T>>(Context as unknown as ReactContext<DeployEditContext<T>>);
}

View File

@@ -1,308 +0,0 @@
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ScrollArea } from "@/components/ui/scroll-area";
import AccessEditDialog from "./AccessEditDialog";
import { Context as DeployEditContext, type DeployEditContext as DeployEditContextType } from "./DeployEdit";
import DeployToAliyunOSS from "./DeployToAliyunOSS";
import DeployToAliyunCDN from "./DeployToAliyunCDN";
import DeployToAliyunCLB from "./DeployToAliyunCLB";
import DeployToAliyunALB from "./DeployToAliyunALB";
import DeployToAliyunNLB from "./DeployToAliyunNLB";
import DeployToTencentCDN from "./DeployToTencentCDN";
import DeployToTencentCLB from "./DeployToTencentCLB";
import DeployToTencentCOS from "./DeployToTencentCOS";
import DeployToTencentTEO from "./DeployToTencentTEO";
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN";
import DeployToQiniuCDN from "./DeployToQiniuCDN";
import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN";
import DeployToLocal from "./DeployToLocal";
import DeployToSSH from "./DeployToSSH";
import DeployToWebhook from "./DeployToWebhook";
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
import DeployToVolcengineLive from "./DeployToVolcengineLive";
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
import DeployToByteplusCDN from "./DeployToByteplusCDN";
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
import { accessProvidersMap } from "@/domain/access";
import { useConfigContext } from "@/providers/config";
type DeployEditDialogProps = {
trigger: React.ReactNode;
deployConfig?: DeployConfig;
onSave: (deploy: DeployConfig) => void;
};
const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogProps) => {
const { t } = useTranslation();
const {
config: { accesses },
} = useConfigContext();
const [deployType, setDeployType] = useState("");
const [locDeployConfig, setLocDeployConfig] = useState<DeployConfig>({
access: "",
type: "",
});
const [errors, setErrors] = useState<Record<string, string | undefined>>({});
const [open, setOpen] = useState(false);
useEffect(() => {
if (deployConfig) {
setLocDeployConfig({ ...deployConfig });
} else {
setLocDeployConfig({
access: "",
type: "",
});
}
}, [deployConfig]);
useEffect(() => {
setDeployType(locDeployConfig.type);
setErrors({});
}, [locDeployConfig.type]);
const setConfig = useCallback(
(deploy: DeployConfig) => {
if (deploy.type !== locDeployConfig.type) {
setLocDeployConfig({ ...deploy, access: "", config: {} });
} else {
setLocDeployConfig({ ...deploy });
}
},
[locDeployConfig.type]
);
const targetAccesses = accesses.filter((item) => {
if (item.usage == "apply") {
return false;
}
if (locDeployConfig.type == "") {
return true;
}
return item.configType === deployTargetsMap.get(locDeployConfig.type)?.provider;
});
const handleSaveClick = () => {
// 验证数据
const newError = { ...errors };
newError.type = locDeployConfig.type === "" ? t("domain.deployment.form.access.placeholder") : "";
newError.access = locDeployConfig.access === "" ? t("domain.deployment.form.access.placeholder") : "";
setErrors(newError);
if (Object.values(newError).some((e) => !!e)) return;
// 保存数据
onSave(locDeployConfig);
// 清理数据
setLocDeployConfig({
access: "",
type: "",
});
setErrors({});
// 关闭弹框
setOpen(false);
};
let childComponent = <></>;
switch (deployType) {
case "aliyun-oss":
childComponent = <DeployToAliyunOSS />;
break;
case "aliyun-cdn":
case "aliyun-dcdn":
childComponent = <DeployToAliyunCDN />;
break;
case "aliyun-clb":
childComponent = <DeployToAliyunCLB />;
break;
case "aliyun-alb":
childComponent = <DeployToAliyunALB />;
break;
case "aliyun-nlb":
childComponent = <DeployToAliyunNLB />;
break;
case "tencent-cdn":
case "tencent-ecdn":
childComponent = <DeployToTencentCDN />;
break;
case "tencent-clb":
childComponent = <DeployToTencentCLB />;
break;
case "tencent-cos":
childComponent = <DeployToTencentCOS />;
break;
case "tencent-teo":
childComponent = <DeployToTencentTEO />;
break;
case "huaweicloud-cdn":
childComponent = <DeployToHuaweiCloudCDN />;
break;
case "huaweicloud-elb":
childComponent = <DeployToHuaweiCloudELB />;
break;
case "baiducloud-cdn":
childComponent = <DeployToBaiduCloudCDN />;
break;
case "qiniu-cdn":
childComponent = <DeployToQiniuCDN />;
break;
case "dogecloud-cdn":
childComponent = <DeployToDogeCloudCDN />;
break;
case "local":
childComponent = <DeployToLocal />;
break;
case "ssh":
childComponent = <DeployToSSH />;
break;
case "webhook":
childComponent = <DeployToWebhook />;
break;
case "k8s-secret":
childComponent = <DeployToKubernetesSecret />;
break;
case "volcengine-live":
childComponent = <DeployToVolcengineLive />;
break;
case "volcengine-cdn":
childComponent = <DeployToVolcengineCDN />;
break;
case "byteplus-cdn":
childComponent = <DeployToByteplusCDN />;
break;
}
return (
<DeployEditContext.Provider
value={{
config: locDeployConfig as DeployEditContextType["config"],
setConfig: setConfig as DeployEditContextType["setConfig"],
errors: errors as DeployEditContextType["errors"],
setErrors: setErrors as DeployEditContextType["setErrors"],
}}
>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger>{trigger}</DialogTrigger>
<DialogContent
className="dark:text-stone-200"
onInteractOutside={(event) => {
event.preventDefault();
}}
>
<DialogHeader>
<DialogTitle>{t("domain.deployment.tab")}</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[80vh]">
<div className="container py-3">
{/* 部署方式 */}
<div>
<Label>{t("domain.deployment.form.type.label")}</Label>
<Select
value={locDeployConfig.type}
onValueChange={(val: string) => {
setConfig({ ...locDeployConfig, type: val });
}}
>
<SelectTrigger className="mt-2">
<SelectValue placeholder={t("domain.deployment.form.type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("domain.deployment.form.type.list")}</SelectLabel>
{Array.from(deployTargetsMap.entries()).map(([key, target]) => (
<SelectItem key={key} value={key}>
<div className="flex items-center space-x-2">
<img className="w-6" src={target.icon} />
<div>{t(target.name)}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-500 text-sm mt-1">{errors.type}</div>
</div>
{/* 授权配置 */}
<div className="mt-8">
<Label 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"
/>
</Label>
<Select
value={locDeployConfig.access}
onValueChange={(val: string) => {
setConfig({ ...locDeployConfig, access: val });
}}
>
<SelectTrigger className="mt-2">
<SelectValue placeholder={t("domain.deployment.form.access.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("domain.deployment.form.access.list")}</SelectLabel>
{targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}>
<div className="flex items-center space-x-2">
<img className="w-6" src={accessProvidersMap.get(item.configType)?.icon} />
<div>{item.name}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-500 text-sm mt-1">{errors.access}</div>
</div>
{/* 其他参数 */}
<div className="mt-8">{childComponent}</div>
</div>
</ScrollArea>
<DialogFooter>
<Button
onClick={(e) => {
e.stopPropagation();
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DeployEditContext.Provider>
);
};
export default DeployEditDialog;

View File

@@ -1,169 +0,0 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { nanoid } from "nanoid";
import { EditIcon, Trash2 } from "lucide-react";
import Show from "@/components/Show";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import DeployEditDialog from "./DeployEditDialog";
import { DeployConfig } from "@/domain/domain";
import { accessProvidersMap } from "@/domain/access";
import { deployTargetsMap } from "@/domain/domain";
import { useConfigContext } from "@/providers/config";
type DeployItemProps = {
item: DeployConfig;
onDelete: () => void;
onSave: (deploy: DeployConfig) => void;
};
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
const { t } = useTranslation();
const {
config: { accesses },
} = useConfigContext();
const access = accesses.find((access) => access.id === item.access);
const getTypeIcon = () => {
if (!access) {
return "";
}
return accessProvidersMap.get(access.configType)?.icon || "";
};
const getTypeName = () => {
return t(deployTargetsMap.get(item.type)?.name || "");
};
return (
<div className="flex justify-between text-sm p-3 items-center text-stone-700 dark:text-stone-200">
<div className="flex space-x-2 items-center">
<div>
<img src={getTypeIcon()} className="w-9"></img>
</div>
<div className="text-stone-600 flex-col flex space-y-0 dark:text-stone-200">
<div>{getTypeName()}</div>
<div>{access?.name}</div>
</div>
</div>
<div className="flex space-x-2">
<DeployEditDialog
trigger={<EditIcon size={16} className="cursor-pointer" />}
deployConfig={item}
onSave={(deploy: DeployConfig) => {
onSave(deploy);
}}
/>
<Trash2
size={16}
className="cursor-pointer"
onClick={() => {
onDelete();
}}
/>
</div>
</div>
);
};
type DeployListProps = {
deploys: DeployConfig[];
onChange: (deploys: DeployConfig[]) => void;
};
const DeployList = ({ deploys, onChange }: DeployListProps) => {
const [list, setList] = useState<DeployConfig[]>([]);
const { t } = useTranslation();
useEffect(() => {
setList(deploys);
}, [deploys]);
const handleAdd = (deploy: DeployConfig) => {
deploy.id = nanoid();
const newList = [...list, deploy];
setList(newList);
onChange(newList);
};
const handleDelete = (id: string) => {
const newList = list.filter((item) => item.id !== id);
setList(newList);
onChange(newList);
};
const handleSave = (deploy: DeployConfig) => {
const newList = list.map((item) => {
if (item.id === deploy.id) {
return { ...deploy };
}
return item;
});
setList(newList);
onChange(newList);
};
return (
<>
<Show
when={list.length > 0}
fallback={
<Alert className="w-full border dark:border-stone-400">
<AlertDescription className="flex flex-col items-center">
<div>{t("domain.deployment.nodata")}</div>
<div className="flex justify-end mt-2">
<DeployEditDialog
onSave={(config: DeployConfig) => {
handleAdd(config);
}}
trigger={<Button size={"sm"}>{t("common.add")}</Button>}
/>
</div>
</AlertDescription>
</Alert>
}
>
<div className="flex justify-end py-2 border-b dark:border-stone-400">
<DeployEditDialog
trigger={<Button size={"sm"}>{t("common.add")}</Button>}
onSave={(config: DeployConfig) => {
handleAdd(config);
}}
/>
</div>
<div className="w-full md:w-[35em] rounded mt-5 border dark:border-stone-400 dark:text-stone-200">
<div className="">
{list.map((item) => (
<DeployItem
key={item.id}
item={item}
onDelete={() => {
handleDelete(item.id ?? "");
}}
onSave={(deploy: DeployConfig) => {
handleSave(deploy);
}}
/>
))}
</div>
</div>
</Show>
</>
);
};
export default DeployList;

View File

@@ -1,55 +0,0 @@
import { useTranslation } from "react-i18next";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
type DeployProgressProps = {
phase?: "check" | "apply" | "deploy";
phaseSuccess?: boolean;
};
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
const { t } = useTranslation();
let step = 0;
if (phase === "check") {
step = 1;
} else if (phase === "apply") {
step = 2;
} else if (phase === "deploy") {
step = 3;
}
return (
<div className="flex items-center">
<div className={cn("text-xs text-nowrap", step === 1 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "", step > 1 ? "text-green-600" : "")}>
{t("history.props.stage.progress.check")}
</div>
<Separator className={cn("h-1 grow max-w-[60px]", step > 1 ? "bg-green-600" : "")} />
<div
className={cn(
"text-xs text-nowrap",
step < 2 ? "text-muted-foreground" : "",
step === 2 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 2 ? "text-green-600" : ""
)}
>
{t("history.props.stage.progress.apply")}
</div>
<Separator className={cn("h-1 grow max-w-[60px]", step > 2 ? "bg-green-600" : "")} />
<div
className={cn(
"text-xs text-nowrap",
step < 3 ? "text-muted-foreground" : "",
step === 3 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 3 ? "text-green-600" : ""
)}
>
{t("history.props.stage.progress.deploy")}
</div>
</div>
);
};
export default DeployProgress;

View File

@@ -1,43 +0,0 @@
import { CircleCheck, CircleX } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Deployment } from "@/domain/deployment";
type DeployStateProps = {
deployment: Deployment;
};
const DeployState = ({ deployment }: DeployStateProps) => {
// 获取指定阶段的错误信息
const error = (state: "check" | "apply" | "deploy") => {
if (!deployment.log[state]) {
return "";
}
return deployment.log[state][deployment.log[state].length - 1].error;
};
return (
<>
{(deployment.phase === "deploy" && deployment.phaseSuccess) || deployment.wholeSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<>
{error(deployment.phase).length ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild className="cursor-pointer">
<CircleX size={16} className="text-red-700" />
</TooltipTrigger>
<TooltipContent className="max-w-[35em]">{error(deployment.phase)}</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<CircleX size={16} className="text-red-700" />
)}
</>
)}
</>
);
};
export default DeployState;

View File

@@ -1,156 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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 { useDeployEditContext } from "./DeployEdit";
type DeployToAliyunALBConfigParams = {
region?: string;
resourceType?: string;
loadbalancerId?: string;
listenerId?: string;
};
const DeployToAliyunALB = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunALBConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "cn-hangzhou",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
region: z.string().min(1, t("domain.deployment.form.aliyun_alb_region.placeholder")),
resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], {
message: t("domain.deployment.form.aliyun_alb_resource_type.placeholder"),
}),
loadbalancerId: z.string().optional(),
listenerId: z.string().optional(),
})
.refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
message: t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder"),
path: ["loadbalancerId"],
})
.refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
message: t("domain.deployment.form.aliyun_alb_listener_id.placeholder"),
path: ["listenerId"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.aliyun_alb_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_alb_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.aliyun_alb_resource_type.label")}</Label>
<Select
value={config?.config?.resourceType}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.resourceType = value?.trim();
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.aliyun_alb_resource_type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_alb_resource_type.option.loadbalancer.label")}</SelectItem>
<SelectItem value="listener">{t("domain.deployment.form.aliyun_alb_resource_type.option.listener.label")}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
</div>
{config?.config?.resourceType === "loadbalancer" ? (
<div>
<Label>{t("domain.deployment.form.aliyun_alb_loadbalancer_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder")}
className="w-full mt-1"
value={config?.config?.loadbalancerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.loadbalancerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "listener" ? (
<div>
<Label>{t("domain.deployment.form.aliyun_alb_listener_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_alb_listener_id.placeholder")}
className="w-full mt-1"
value={config?.config?.listenerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.listenerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
</div>
) : (
<></>
)}
</div>
);
};
export default DeployToAliyunALB;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToAliyunCDNConfigParams = {
domain?: string;
};
const DeployToAliyunCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToAliyunCDN;

View File

@@ -1,153 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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 { useDeployEditContext } from "./DeployEdit";
type DeployToAliyunCLBConfigParams = {
region?: string;
resourceType?: string;
loadbalancerId?: string;
listenerPort?: string;
};
const DeployToAliyunCLB = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCLBConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "cn-hangzhou",
listenerPort: "443",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
region: z.string().min(1, t("domain.deployment.form.aliyun_clb_region.placeholder")),
resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], {
message: t("domain.deployment.form.aliyun_clb_resource_type.placeholder"),
}),
loadbalancerId: z.string().optional(),
listenerPort: z.string().optional(),
})
.refine((data) => (data.resourceType === "loadbalancer" || data.resourceType === "listener" ? !!data.loadbalancerId?.trim() : true), {
message: t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder"),
path: ["loadbalancerId"],
})
.refine((data) => (data.resourceType === "listener" ? +data.listenerPort! > 0 && +data.listenerPort! < 65535 : true), {
message: t("domain.deployment.form.aliyun_clb_listener_port.placeholder"),
path: ["listenerPort"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
listenerPort: res.error?.errors?.find((e) => e.path[0] === "listenerPort")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.aliyun_clb_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_clb_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.aliyun_clb_resource_type.label")}</Label>
<Select
value={config?.config?.resourceType}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.resourceType = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.aliyun_clb_resource_type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_clb_resource_type.option.loadbalancer.label")}</SelectItem>
<SelectItem value="listener">{t("domain.deployment.form.aliyun_clb_resource_type.option.listener.label")}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
</div>
<div>
<Label>{t("domain.deployment.form.aliyun_clb_loadbalancer_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder")}
className="w-full mt-1"
value={config?.config?.loadbalancerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.loadbalancerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
</div>
{config?.config?.resourceType === "listener" ? (
<div>
<Label>{t("domain.deployment.form.aliyun_clb_listener_port.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_clb_listener_port.placeholder")}
className="w-full mt-1"
value={config?.config?.listenerPort}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.listenerPort = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.listenerPort}</div>
</div>
) : (
<></>
)}
</div>
);
};
export default DeployToAliyunCLB;

View File

@@ -1,156 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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 { useDeployEditContext } from "./DeployEdit";
type DeployToAliyunNLBConfigParams = {
region?: string;
resourceType?: string;
loadbalancerId?: string;
listenerId?: string;
};
const DeployToAliyunNLB = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunNLBConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "cn-hangzhou",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
region: z.string().min(1, t("domain.deployment.form.aliyun_nlb_region.placeholder")),
resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], {
message: t("domain.deployment.form.aliyun_nlb_resource_type.placeholder"),
}),
loadbalancerId: z.string().optional(),
listenerId: z.string().optional(),
})
.refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
message: t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder"),
path: ["loadbalancerId"],
})
.refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
message: t("domain.deployment.form.aliyun_nlb_listener_id.placeholder"),
path: ["listenerId"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.aliyun_nlb_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_nlb_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.aliyun_nlb_resource_type.label")}</Label>
<Select
value={config?.config?.resourceType}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.resourceType = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.aliyun_nlb_resource_type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label")}</SelectItem>
<SelectItem value="listener">{t("domain.deployment.form.aliyun_nlb_resource_type.option.listener.label")}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
</div>
{config?.config?.resourceType === "loadbalancer" ? (
<div>
<Label>{t("domain.deployment.form.aliyun_nlb_loadbalancer_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder")}
className="w-full mt-1"
value={config?.config?.loadbalancerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.loadbalancerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "listener" ? (
<div>
<Label>{t("domain.deployment.form.aliyun_nlb_listener_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_nlb_listener_id.placeholder")}
className="w-full mt-1"
value={config?.config?.listenerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.listenerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
</div>
) : (
<></>
)}
</div>
);
};
export default DeployToAliyunNLB;

View File

@@ -1,114 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToAliyunOSSConfigParams = {
endpoint?: string;
bucket?: string;
domain?: string;
};
const DeployToAliyunOSS = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunOSSConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
endpoint: "oss.aliyuncs.com",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
endpoint: z.string().min(1, {
message: t("domain.deployment.form.aliyun_oss_endpoint.placeholder"),
}),
bucket: z.string().min(1, {
message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
}),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
endpoint: res.error?.errors?.find((e) => e.path[0] === "endpoint")?.message,
bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.aliyun_oss_endpoint.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_oss_endpoint.placeholder")}
className="w-full mt-1"
value={config?.config?.endpoint}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.endpoint = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.endpoint}</div>
</div>
<div>
<Label>{t("domain.deployment.form.aliyun_oss_bucket.label")}</Label>
<Input
placeholder={t("domain.deployment.form.aliyun_oss_bucket.placeholder")}
className="w-full mt-1"
value={config?.config?.bucket}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.bucket = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
</div>
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.label")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToAliyunOSS;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToBaiduCloudCDNConfigParams = {
domain?: string;
};
const DeployToBaiduCloudCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToBaiduCloudCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToBaiduCloudCDN;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToByteplusCDNConfigParams = {
domain?: string;
};
const DeployToByteplusCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToByteplusCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToByteplusCDN;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToDogeCloudCDNConfigParams = {
domain?: string;
};
const DeployToDogeCloudCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToDogeCloudCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToDogeCloudCDN;

View File

@@ -1,92 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToHuaweiCloudCDNConfigParams = {
region?: string;
domain?: string;
};
const DeployToHuaweiCloudCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "cn-north-1",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
region: z.string().min(1, {
message: t("domain.deployment.form.huaweicloud_cdn_region.placeholder"),
}),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.huaweicloud_cdn_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.huaweicloud_cdn_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToHuaweiCloudCDN;

View File

@@ -1,185 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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 { useDeployEditContext } from "./DeployEdit";
type DeployToHuaweiCloudELBConfigParams = {
region?: string;
resourceType?: string;
certificateId?: string;
loadbalancerId?: string;
listenerId?: string;
};
const DeployToHuaweiCloudELB = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudELBConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "cn-north-1",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")),
resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], {
message: t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder"),
}),
certificateId: z.string().optional(),
loadbalancerId: z.string().optional(),
listenerId: z.string().optional(),
})
.refine((data) => (data.resourceType === "certificate" ? !!data.certificateId?.trim() : true), {
message: t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder"),
path: ["certificateId"],
})
.refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
message: t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder"),
path: ["loadbalancerId"],
})
.refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
message: t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder"),
path: ["listenerId"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
certificateId: res.error?.errors?.find((e) => e.path[0] === "certificateId")?.message,
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.huaweicloud_elb_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.huaweicloud_elb_resource_type.label")}</Label>
<Select
value={config?.config?.resourceType}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.resourceType = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="certificate">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label")}</SelectItem>
<SelectItem value="loadbalancer">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label")}</SelectItem>
<SelectItem value="listener">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label")}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
</div>
{config?.config?.resourceType === "certificate" ? (
<div>
<Label>{t("domain.deployment.form.huaweicloud_elb_certificate_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder")}
className="w-full mt-1"
value={config?.config?.certificateId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.certificateId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.certificateId}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "loadbalancer" ? (
<div>
<Label>{t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder")}
className="w-full mt-1"
value={config?.config?.loadbalancerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.loadbalancerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "listener" ? (
<div>
<Label>{t("domain.deployment.form.huaweicloud_elb_listener_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder")}
className="w-full mt-1"
value={config?.config?.listenerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.listenerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
</div>
) : (
<></>
)}
</div>
);
};
export default DeployToHuaweiCloudELB;

View File

@@ -1,136 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToKubernetesSecretConfigParams = {
namespace?: string;
secretName?: string;
secretDataKeyForCrt?: string;
secretDataKeyForKey?: string;
};
const DeployToKubernetesSecret = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToKubernetesSecretConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
namespace: "default",
secretDataKeyForCrt: "tls.crt",
secretDataKeyForKey: "tls.key",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
namespace: z.string().min(1, {
message: t("domain.deployment.form.k8s_namespace.placeholder"),
}),
secretName: z.string().min(1, {
message: t("domain.deployment.form.k8s_secret_name.placeholder"),
}),
secretDataKeyForCrt: z.string().min(1, {
message: t("domain.deployment.form.k8s_secret_data_key_for_crt.placeholder"),
}),
secretDataKeyForKey: z.string().min(1, {
message: t("domain.deployment.form.k8s_secret_data_key_for_key.placeholder"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
namespace: res.error?.errors?.find((e) => e.path[0] === "namespace")?.message,
secretName: res.error?.errors?.find((e) => e.path[0] === "secretName")?.message,
secretDataKeyForCrt: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForCrt")?.message,
secretDataKeyForKey: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForKey")?.message,
});
}, [config]);
return (
<>
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.k8s_namespace.label")}</Label>
<Input
placeholder={t("domain.deployment.form.k8s_namespace.label")}
className="w-full mt-1"
value={config?.config?.namespace}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.namespace = e.target.value?.trim();
});
setConfig(nv);
}}
/>
</div>
<div>
<Label>{t("domain.deployment.form.k8s_secret_name.label")}</Label>
<Input
placeholder={t("domain.deployment.form.k8s_secret_name.label")}
className="w-full mt-1"
value={config?.config?.secretName}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.secretName = e.target.value?.trim();
});
setConfig(nv);
}}
/>
</div>
<div>
<Label>{t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}</Label>
<Input
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}
className="w-full mt-1"
value={config?.config?.secretDataKeyForCrt}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.secretDataKeyForCrt = e.target.value?.trim();
});
setConfig(nv);
}}
/>
</div>
<div>
<Label>{t("domain.deployment.form.k8s_secret_data_key_for_key.label")}</Label>
<Input
placeholder={t("domain.deployment.form.k8s_secret_data_key_for_key.label")}
className="w-full mt-1"
value={config?.config?.secretDataKeyForKey}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.secretDataKeyForKey = e.target.value?.trim();
});
setConfig(nv);
}}
/>
</div>
</div>
</>
);
};
export default DeployToKubernetesSecret;

View File

@@ -1,481 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
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";
type DeployToLocalConfigParams = {
format?: string;
certPath?: string;
keyPath?: string;
pfxPassword?: string;
jksAlias?: string;
jksKeypass?: string;
jksStorepass?: string;
shell?: string;
preCommand?: string;
command?: string;
};
const DeployToLocal = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToLocalConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
format: "pem",
certPath: "/etc/nginx/ssl/nginx.crt",
keyPath: "/etc/nginx/ssl/nginx.key",
shell: "sh",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
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(),
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"],
})
.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"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
shell: res.error?.errors?.find((e) => e.path[0] === "shell")?.message,
preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
});
}, [config]);
useEffect(() => {
if (config.config?.format === "pem") {
if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
})
);
}
} else if (config.config?.format === "pfx") {
if (/(.crt|.jks)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
})
);
}
} else if (config.config?.format === "jks") {
if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
})
);
}
}
}, [config.config?.format]);
const getOptionCls = (val: string) => {
if (config.config?.shell === val) {
return "border-primary dark:border-primary";
}
return "";
};
const handleUsePresetScript = (key: string) => {
switch (key) {
case "reload_nginx":
{
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.shell = "sh";
draft.config.command = "sudo service nginx reload";
})
);
}
break;
case "binding_iis":
{
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.shell = "powershell";
draft.config.command = `
# 请将以下变量替换为实际值
$pfxPath = "<your-pfx-path>" # PFX 文件路径
$pfxPassword = "<your-pfx-password>" # PFX 密码
$siteName = "<your-site-name>" # IIS 网站名称
$domain = "<your-domain-name>" # 域名
$ipaddr = "<your-binding-ip>" # 绑定 IP“*”表示所有 IP 绑定
$port = "<your-binding-port>" # 绑定端口
# 导入证书到本地计算机的个人存储区
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
# 获取 Thumbprint
$thumbprint = $cert.Thumbprint
# 导入 WebAdministration 模块
Import-Module WebAdministration
# 检查是否已存在 HTTPS 绑定
$existingBinding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -HostHeader "$domain" -ErrorAction SilentlyContinue
if (!$existingBinding) {
# 添加新的 HTTPS 绑定
New-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
}
# 获取绑定对象
$binding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
# 绑定 SSL 证书
$binding.AddSslCertificate($thumbprint, "My")
# 删除目录下的证书文件
Remove-Item -Path "$pfxPath" -Force
`.trim();
})
);
}
break;
case "binding_netsh":
{
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.shell = "powershell";
draft.config.command = `
# 请将以下变量替换为实际值
$pfxPath = "<your-pfx-path>" # PFX 文件路径
$pfxPassword = "<your-pfx-password>" # PFX 密码
$ipaddr = "<your-binding-ip>" # 绑定 IP“0.0.0.0”表示所有 IP 绑定,可填入域名。
$port = "<your-binding-port>" # 绑定端口
$addr = $ipaddr + ":" + $port
# 导入证书到本地计算机的个人存储区
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
# 获取 Thumbprint
$thumbprint = $cert.Thumbprint
# 检测端口是否绑定证书,如绑定则删除绑定
$isExist = netsh http show sslcert ipport=$addr
if ($isExist -like "*$addr*"){ netsh http delete sslcert ipport=$addr }
# 绑定到端口
netsh http add sslcert ipport=$addr certhash=$thumbprint
# 删除目录下的证书文件
Remove-Item -Path "$pfxPath" -Force
`.trim();
})
);
}
break;
}
};
return (
<>
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.file_format.label")}</Label>
<Select
value={config?.config?.format}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.format = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="pem">PEM</SelectItem>
<SelectItem value="pfx">PFX</SelectItem>
<SelectItem value="jks">JKS</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.format}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_cert_path.label")}
className="w-full mt-1"
value={config?.config?.certPath}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
</div>
{config.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={config?.config?.keyPath}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.keyPath = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
</div>
) : (
<></>
)}
{config.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={config?.config?.pfxPassword}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.pfxPassword = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
</div>
) : (
<></>
)}
{config.config?.format === "jks" ? (
<>
<div>
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
className="w-full mt-1"
value={config?.config?.jksAlias}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksAlias = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_jks_keypass.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
className="w-full mt-1"
value={config?.config?.jksKeypass}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksKeypass = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_jks_storepass.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
className="w-full mt-1"
value={config?.config?.jksStorepass}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksStorepass = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
</div>
</>
) : (
<></>
)}
<div>
<Label>{t("domain.deployment.form.shell.label")}</Label>
<RadioGroup
className="flex mt-1"
value={config?.config?.shell}
onValueChange={(val) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.shell = val;
});
setConfig(nv);
}}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="sh" id="shellOptionSh" />
<Label htmlFor="shellOptionSh">
<div className={cn("flex items-center space-x-2 border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("sh"))}>
<div>POSIX Bash (Linux)</div>
</div>
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="cmd" id="shellOptionCmd" />
<Label htmlFor="shellOptionCmd">
<div className={cn("border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("cmd"))}>
<div>CMD (Windows)</div>
</div>
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="powershell" id="shellOptionPowerShell" />
<Label htmlFor="shellOptionPowerShell">
<div className={cn("border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("powershell"))}>
<div>PowerShell (Windows)</div>
</div>
</Label>
</div>
</RadioGroup>
<div className="text-red-600 text-sm mt-1">{errors?.shell}</div>
</div>
<div>
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
<Textarea
className="mt-1"
value={config?.config?.preCommand}
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.preCommand = e.target.value;
});
setConfig(nv);
}}
></Textarea>
<div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
</div>
<div>
<div className="flex items-center justify-between">
<Label>{t("domain.deployment.form.shell_command.label")}</Label>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<a className="text-xs text-blue-500 cursor-pointer">{t("domain.deployment.form.shell_preset_scripts.trigger")}</a>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => handleUsePresetScript("reload_nginx")}>
{t("domain.deployment.form.shell_preset_scripts.option.reload_nginx.label")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleUsePresetScript("binding_iis")}>
{t("domain.deployment.form.shell_preset_scripts.option.binding_iis.label")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleUsePresetScript("binding_netsh")}>
{t("domain.deployment.form.shell_preset_scripts.option.binding_netsh.label")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Textarea
className="mt-1"
value={config?.config?.command}
placeholder={t("domain.deployment.form.shell_command.placeholder")}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.command = e.target.value;
});
setConfig(nv);
}}
></Textarea>
<div className="text-red-600 text-sm mt-1">{errors?.command}</div>
</div>
</div>
</>
);
};
export default DeployToLocal;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToQiniuCDNConfigParams = {
domain?: string;
};
const DeployToQiniuCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToQiniuCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToQiniuCDN;

View File

@@ -1,319 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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";
type DeployToSSHConfigParams = {
format?: string;
certPath?: string;
keyPath?: string;
pfxPassword?: string;
jksAlias?: string;
jksKeypass?: string;
jksStorepass?: string;
shell?: string;
preCommand?: string;
command?: string;
};
const DeployToSSH = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToSSHConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
format: "pem",
certPath: "/etc/nginx/ssl/nginx.crt",
keyPath: "/etc/nginx/ssl/nginx.key",
command: "sudo service nginx reload",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
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"],
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
});
}, [config]);
useEffect(() => {
if (config.config?.format === "pem") {
if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
})
);
}
} else if (config.config?.format === "pfx") {
if (/(.crt|.jks)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
})
);
}
} else if (config.config?.format === "jks") {
if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
setConfig(
produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
})
);
}
}
}, [config.config?.format]);
return (
<>
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.file_format.label")}</Label>
<Select
value={config?.config?.format}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.format = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="pem">PEM</SelectItem>
<SelectItem value="pfx">PFX</SelectItem>
<SelectItem value="jks">JKS</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.format}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_cert_path.label")}
className="w-full mt-1"
value={config?.config?.certPath}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.certPath = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
</div>
{config.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={config?.config?.keyPath}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.keyPath = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
</div>
) : (
<></>
)}
{config.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={config?.config?.pfxPassword}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.pfxPassword = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
</div>
) : (
<></>
)}
{config.config?.format === "jks" ? (
<>
<div>
<Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
className="w-full mt-1"
value={config?.config?.jksAlias}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksAlias = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_jks_keypass.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
className="w-full mt-1"
value={config?.config?.jksKeypass}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksKeypass = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
</div>
<div>
<Label>{t("domain.deployment.form.file_jks_storepass.label")}</Label>
<Input
placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
className="w-full mt-1"
value={config?.config?.jksStorepass}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.jksStorepass = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
</div>
</>
) : (
<></>
)}
<div>
<Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
<Textarea
className="mt-1"
value={config?.config?.preCommand}
placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.preCommand = e.target.value;
});
setConfig(nv);
}}
></Textarea>
<div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
</div>
<div>
<Label>{t("domain.deployment.form.shell_command.label")}</Label>
<Textarea
className="mt-1"
value={config?.config?.command}
placeholder={t("domain.deployment.form.shell_command.placeholder")}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.command = e.target.value;
});
setConfig(nv);
}}
></Textarea>
<div className="text-red-600 text-sm mt-1">{errors?.command}</div>
</div>
</div>
</>
);
};
export default DeployToSSH;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToTencentCDNParams = {
domain?: string;
};
const DeployToTencentCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCDNParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToTencentCDN;

View File

@@ -1,220 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
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 { useDeployEditContext } from "./DeployEdit";
type DeployToTencentCLBParams = {
region?: string;
resourceType?: string;
loadbalancerId?: string;
listenerId?: string;
domain?: string;
};
const DeployToTencentCLB = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCLBParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "ap-guangzhou",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z
.object({
region: z.string().min(1, t("domain.deployment.form.tencent_clb_region.placeholder")),
resourceType: z.union([z.literal("ssl-deploy"), z.literal("loadbalancer"), z.literal("listener"), z.literal("ruledomain")], {
message: t("domain.deployment.form.tencent_clb_resource_type.placeholder"),
}),
loadbalancerId: z.string().min(1, t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")),
listenerId: z.string().optional(),
domain: z.string().optional(),
})
.refine(
(data) => {
switch (data.resourceType) {
case "ssl-deploy":
case "listener":
case "ruledomain":
return !!data.listenerId?.trim();
}
return true;
},
{
message: t("domain.deployment.form.tencent_clb_listener_id.placeholder"),
path: ["listenerId"],
}
)
.refine(
(data) => {
switch (data.resourceType) {
case "ssl-deploy":
case "ruledomain":
return !!data.domain?.trim() && /^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(data.domain);
}
return true;
},
{
message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
path: ["domain"],
}
);
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.tencent_clb_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_clb_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.tencent_clb_resource_type.label")}</Label>
<Select
value={config?.config?.resourceType}
onValueChange={(value) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.resourceType = value;
});
setConfig(nv);
}}
>
<SelectTrigger>
<SelectValue placeholder={t("domain.deployment.form.tencent_clb_resource_type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="ssl-deploy">{t("domain.deployment.form.tencent_clb_resource_type.option.ssl_deploy.label")}</SelectItem>
<SelectItem value="loadbalancer">{t("domain.deployment.form.tencent_clb_resource_type.option.loadbalancer.label")}</SelectItem>
<SelectItem value="listener">{t("domain.deployment.form.tencent_clb_resource_type.option.listener.label")}</SelectItem>
<SelectItem value="ruledomain">{t("domain.deployment.form.tencent_clb_resource_type.option.ruledomain.label")}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
</div>
<div>
<Label>{t("domain.deployment.form.tencent_clb_loadbalancer_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")}
className="w-full mt-1"
value={config?.config?.loadbalancerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.loadbalancerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
</div>
{config?.config?.resourceType === "ssl-deploy" || config?.config?.resourceType === "listener" || config?.config?.resourceType === "ruledomain" ? (
<div>
<Label>{t("domain.deployment.form.tencent_clb_listener_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_clb_listener_id.placeholder")}
className="w-full mt-1"
value={config?.config?.listenerId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.listenerId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "ssl-deploy" ? (
<div>
<Label>{t("domain.deployment.form.tencent_clb_domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_clb_domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
) : (
<></>
)}
{config?.config?.resourceType === "ruledomain" ? (
<div>
<Label>{t("domain.deployment.form.tencent_clb_ruledomain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_clb_ruledomain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
) : (
<></>
)}
</div>
);
};
export default DeployToTencentCLB;

View File

@@ -1,110 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToTencentCOSParams = {
region?: string;
bucket?: string;
domain?: string;
};
const DeployToTencentCOS = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCOSParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {
region: "ap-guangzhou",
},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
region: z.string().min(1, t("domain.deployment.form.tencent_cos_region.placeholder")),
bucket: z.string().min(1, t("domain.deployment.form.tencent_cos_bucket.placeholder")),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.tencent_cos_region.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_cos_region.placeholder")}
className="w-full mt-1"
value={config?.config?.region}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.region = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.region}</div>
</div>
<div>
<Label>{t("domain.deployment.form.tencent_cos_bucket.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_cos_bucket.placeholder")}
className="w-full mt-1"
value={config?.config?.bucket}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.bucket = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
</div>
<div>
<Label>{t("domain.deployment.form.domain.label")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToTencentCOS;

View File

@@ -1,89 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useDeployEditContext } from "./DeployEdit";
type DeployToTencentTEOParams = {
zoneId?: string;
domain?: string;
};
const DeployToTencentTEO = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentTEOParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
zoneId: z.string().min(1, t("domain.deployment.form.tencent_teo_zone_id.placeholder")),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
zoneId: res.error?.errors?.find((e) => e.path[0] === "zoneId")?.message,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.tencent_teo_zone_id.label")}</Label>
<Input
placeholder={t("domain.deployment.form.tencent_teo_zone_id.placeholder")}
className="w-full mt-1"
value={config?.config?.zoneId}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.zoneId = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.zoneId}</div>
</div>
<div>
<Label>{t("domain.deployment.form.tencent_teo_domain.label")}</Label>
<Textarea
placeholder={t("domain.deployment.form.tencent_teo_domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToTencentTEO;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToVolcengineCDNConfigParams = {
domain?: string;
};
const DeployToVolcengineCDN = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineCDNConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToVolcengineCDN;

View File

@@ -1,68 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { produce } from "immer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeployEditContext } from "./DeployEdit";
type DeployToVolcengineLiveConfigParams = {
domain?: string;
};
const DeployToVolcengineLive = () => {
const { t } = useTranslation();
const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineLiveConfigParams>();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
const formSchema = z.object({
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
useEffect(() => {
const res = formSchema.safeParse(config.config);
setErrors({
...errors,
domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
});
}, [config]);
return (
<div className="flex flex-col space-y-8">
<div>
<Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
<Input
placeholder={t("domain.deployment.form.domain.placeholder")}
className="w-full mt-1"
value={config?.config?.domain}
onChange={(e) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.domain = e.target.value?.trim();
});
setConfig(nv);
}}
/>
<div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
</div>
</div>
);
};
export default DeployToVolcengineLive;

View File

@@ -1,40 +0,0 @@
import { useEffect } from "react";
import { produce } from "immer";
import { useDeployEditContext } from "./DeployEdit";
import KVList from "./KVList";
import { type KVType } from "@/domain/domain";
const DeployToWebhook = () => {
const { config, setConfig, setErrors } = useDeployEditContext();
useEffect(() => {
if (!config.id) {
setConfig({
...config,
config: {},
});
}
}, []);
useEffect(() => {
setErrors({});
}, []);
return (
<>
<KVList
variables={config?.config?.variables}
onValueChange={(variables: KVType[]) => {
const nv = produce(config, (draft) => {
draft.config ??= {};
draft.config.variables = variables;
});
setConfig(nv);
}}
/>
</>
);
};
export default DeployToWebhook;

View File

@@ -18,6 +18,9 @@ import DeployToTencentCOS from "./DeployToTencentCOS";
import DeployToTencentTEO from "./DeployToTencentTEO";
import DeployToSSH from "./DeployToSSH";
import DeployToLocal from "./DeployToLocal";
import DeployToByteplusCDN from "./DeployToByteplusCDN";
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
import DeployToVolcengineLive from "./DeployToVolcengineLive";
export type DeployFormProps = {
data: WorkflowNode;
@@ -70,6 +73,12 @@ const getForm = (data: WorkflowNode, defaultProivder?: string) => {
return <DeployToSSH data={data} />;
case "local":
return <DeployToLocal data={data} />;
case "byteplus-cdn":
return <DeployToByteplusCDN data={data} />;
case "volcengine-cdn":
return <DeployToVolcengineCDN data={data} />;
case "volcengine-live":
return <DeployToVolcengineLive data={data} />;
default:
return <></>;
}

View File

@@ -32,7 +32,6 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
useEffect(() => {
const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
console.log(rs);
setBeforeOutput(rs);
}, [data]);

View File

@@ -0,0 +1,181 @@
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { Input } from "@/components/ui/input";
import { DeployFormProps } from "./DeployForm";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useShallow } from "zustand/shallow";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
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 DeployToByteplusCDN = ({ 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");
setBeforeOutput(rs);
}, [data]);
const formSchema = z.object({
providerType: z.string(),
access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
certificate: z.string().min(1),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
let config: WorkflowNodeConfig = {
certificate: "",
providerType: "byteplus-cdn",
access: "",
domain: "",
};
if (data) config = data.config ?? config;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
providerType: "byteplus-cdn",
access: config.access as string,
certificate: config.certificate as string,
domain: config.domain as string,
},
});
const onSubmit = async (config: z.infer<typeof formSchema>) => {
updateNode({ ...data, config: { ...config }, validated: true });
hidePanel();
};
return (
<>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
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="byteplus"
/>
</FormLabel>
<FormControl>
<AccessSelect
{...field}
value={field.value}
onValueChange={(value) => {
form.setValue("access", value);
}}
providerType="byteplus-cdn"
/>
</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="domain"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
<FormControl>
<Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default DeployToByteplusCDN;

View File

@@ -0,0 +1,181 @@
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { Input } from "@/components/ui/input";
import { DeployFormProps } from "./DeployForm";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useShallow } from "zustand/shallow";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
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 DeployToVolcengineCDN = ({ 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");
setBeforeOutput(rs);
}, [data]);
const formSchema = z.object({
providerType: z.string(),
access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
certificate: z.string().min(1),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
let config: WorkflowNodeConfig = {
certificate: "",
providerType: "volcengine-cdn",
access: "",
domain: "",
};
if (data) config = data.config ?? config;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
providerType: "volcengine-cdn",
access: config.access as string,
certificate: config.certificate as string,
domain: config.domain as string,
},
});
const onSubmit = async (config: z.infer<typeof formSchema>) => {
updateNode({ ...data, config: { ...config }, validated: true });
hidePanel();
};
return (
<>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
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="volcengine"
/>
</FormLabel>
<FormControl>
<AccessSelect
{...field}
value={field.value}
onValueChange={(value) => {
form.setValue("access", value);
}}
providerType="volcengine-cdn"
/>
</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="domain"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
<FormControl>
<Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default DeployToVolcengineCDN;

View File

@@ -0,0 +1,181 @@
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { Input } from "@/components/ui/input";
import { DeployFormProps } from "./DeployForm";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useShallow } from "zustand/shallow";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
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 DeployToVolcengineLive = ({ 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");
setBeforeOutput(rs);
}, [data]);
const formSchema = z.object({
providerType: z.string(),
access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
certificate: z.string().min(1),
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("common.errmsg.domain_invalid"),
}),
});
let config: WorkflowNodeConfig = {
certificate: "",
providerType: "volcengine-live",
access: "",
domain: "",
};
if (data) config = data.config ?? config;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
providerType: "volcengine-live",
access: config.access as string,
certificate: config.certificate as string,
domain: config.domain as string,
},
});
const onSubmit = async (config: z.infer<typeof formSchema>) => {
updateNode({ ...data, config: { ...config }, validated: true });
hidePanel();
};
return (
<>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
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="volcengine"
/>
</FormLabel>
<FormControl>
<AccessSelect
{...field}
value={field.value}
onValueChange={(value) => {
form.setValue("access", value);
}}
providerType="volcengine-live"
/>
</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="domain"
render={({ field }) => (
<FormItem>
<FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
<FormControl>
<Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default DeployToVolcengineLive;