This commit is contained in:
yoan 2024-10-10 11:13:45 +08:00
parent 30ef5841d6
commit 9f1b00f04c
6 changed files with 301 additions and 420 deletions

View File

@ -20,13 +20,14 @@ import { Edit, Plus, Trash2 } from "lucide-react";
type StringListProps = {
className?: string;
value: string;
valueType?: "domain" | "ip";
valueType?: ValueType;
onValueChange: (value: string) => void;
};
const titles: Record<string, string> = {
domain: "domain",
ip: "IP",
dns: "dns",
};
const StringList = ({
@ -100,7 +101,9 @@ const StringList = ({
when={list.length > 0}
fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground"></div>
<div className="text-muted-foreground">
{t("not.added.yet." + valueType)}
</div>
<StringEdit
value={""}
@ -150,7 +153,7 @@ const StringList = ({
export default StringList;
type ValueType = "domain" | "ip";
type ValueType = "domain" | "dns" | "host";
type StringEditProps = {
value: string;
@ -186,7 +189,8 @@ const StringEdit = ({
const schedules: Record<ValueType, z.ZodString> = {
domain: domainSchema,
ip: ipSchema,
dns: ipSchema,
host: ipSchema,
};
const onSaveClick = useCallback(() => {

View File

@ -7,7 +7,7 @@ export type Domain = {
crontab: string;
access: string;
targetAccess?: string;
targetType: string;
targetType?: string;
expiredAt?: string;
phase?: Pahse;
phaseSuccess?: boolean;
@ -31,10 +31,19 @@ export type Domain = {
deployConfig?: DeployConfig[];
};
type KVType = {
key: string;
value: string;
};
export type DeployConfig = {
access: string;
type: string;
config?: Record<string, string>;
config?: {
[key: string]: string;
} & {
variables?: KVType[];
};
};
export type ApplyConfig = {

View File

@ -4,12 +4,14 @@
"username.not.empty": "Please enter username",
"password": "Password",
"password.not.empty": "Please enter password",
"ip.not.empty.verify.message": "Please enter Ip",
"email": "Email",
"logout": "Logout",
"setting": "Settings",
"account": "Account",
"template": "Template",
"save": "Save",
"next": "Next",
"no.data": "No data available",
"status": "Status",
"operation": "Operation",
@ -28,12 +30,15 @@
"variables": "Variables",
"dns": "Domain Name Server",
"name": "Name",
"timeout": "Time Out",
"not.added.yet.domain": "Domain not added yet.",
"not.added.yet.dns": "Nameserver not added yet.",
"create.time": "CreateTime",
"update.time": "UpdateTime",
"created.in": "Created in",
"updated.in": "Updated in",
"basic.setting": "Basic Settings",
"advanced.setting": "Advanced Settings",
"apply.setting": "Apply Settings",
"deploy.setting": "Deploy Settings",
"operation.succeed": "Operation Successful",
"save.succeed": "Save Successful",
"save.failed": "Save Failed",
@ -83,7 +88,7 @@
"pagination.prev": "Previous",
"domain": "Domain",
"domain.add": "Add Domain",
"domain.edit":"Edit Domain",
"domain.edit": "Edit Domain",
"domain.delete": "Delete Domain",
"domain.not.empty.verify.message": "Please enter domain",
"domain.management.name": "Domain List",
@ -120,6 +125,10 @@
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "Domain added successfully",
"domain.management.edit.timeout.placeholder": "Timeout (seconds)",
"domain.management.edit.deploy.error": "Please save applyment configuration first",
"domain.management.enabled.failed": "Enable failed",
"domain.management.enabled.without.deployments": "Failed to enable, no deployment configuration found",
"email.add": "Add Email",
"email.list": "Email List",
"email.valid.message": "Please enter a valid email address",

View File

@ -4,12 +4,14 @@
"username.not.empty": "请输入用户名",
"password": "密码",
"password.not.empty": "请输入密码",
"ip.not.empty.verify.message": "请输入 IP",
"email": "邮箱",
"logout": "退出登录",
"setting": "设置",
"account": "账户",
"template": "模版",
"save": "保存",
"next": "下一步",
"no.data": "暂无数据",
"status": "状态",
"operation": "操作",
@ -28,12 +30,15 @@
"variables": "变量",
"dns": "域名服务器",
"name": "名称",
"timeout": "超时时间",
"not.added.yet.domain": "域名未添加",
"not.added.yet.dns": "域名服务器暂未添加",
"create.time": "创建时间",
"update.time": "更新时间",
"created.in": "创建于",
"updated.in": "更新于",
"basic.setting": "基础设置",
"advanced.setting": "高级设置",
"apply.setting": "申请设置",
"deploy.setting": "部署设置",
"operation.succeed": "操作成功",
"save.succeed": "保存成功",
"save.failed": "保存失败",
@ -120,6 +125,10 @@
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "域名添加成功",
"domain.management.edit.timeout.placeholder": "超时时间(单位:秒)",
"domain.management.edit.deploy.error": "请先保存申请配置",
"domain.management.enabled.failed": "启用失败",
"domain.management.enabled.without.deployments": "启用失败,请先设置部署配置",
"email.add": "添加邮箱",
"email.list": "邮箱列表",
"email.valid.message": "请输入正确的邮箱地址",

View File

@ -30,7 +30,7 @@ import { PbErrorData } from "@/domain/base";
import { useToast } from "@/components/ui/use-toast";
import { Toaster } from "@/components/ui/toaster";
import { useLocation, useNavigate } from "react-router-dom";
import { Plus } from "lucide-react";
import { Plus, Trash2, Edit as EditIcon } from "lucide-react";
import { AccessEdit } from "@/components/certimate/AccessEdit";
import { accessTypeMap } from "@/domain/access";
import EmailsEdit from "@/components/certimate/EmailsEdit";
@ -39,6 +39,7 @@ import { cn } from "@/lib/utils";
import { EmailsSetting } from "@/domain/settings";
import { useTranslation } from "react-i18next";
import StringList from "@/components/certimate/StringList";
import { Input } from "@/components/ui/input";
const Edit = () => {
const {
@ -50,7 +51,7 @@ const Edit = () => {
const location = useLocation();
const { t } = useTranslation();
const [tab, setTab] = useState<"base" | "advance">("base");
const [tab, setTab] = useState<"apply" | "deploy">("apply");
const [targetType, setTargetType] = useState(domain ? domain.targetType : "");
@ -77,13 +78,8 @@ const Edit = () => {
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
message: "domain.management.edit.dns.access.not.empty.message",
}),
targetAccess: z.string().optional(),
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
message: "domain.management.edit.target.type.not.empty.message",
}),
variables: z.string().optional(),
group: z.string().optional(),
nameservers: z.string().optional(),
timeout: z.number().optional(),
});
const form = useForm<z.infer<typeof formSchema>>({
@ -93,11 +89,8 @@ const Edit = () => {
domain: "",
email: "",
access: "",
targetAccess: "",
targetType: "",
variables: "",
group: "",
nameservers: "",
timeout: 60,
},
});
@ -106,13 +99,11 @@ const Edit = () => {
form.reset({
id: domain.id,
domain: domain.domain,
email: domain.email,
access: domain.access,
targetAccess: domain.targetAccess,
targetType: domain.targetType,
variables: domain.variables,
group: domain.group,
nameservers: domain.nameservers,
email: domain.applyConfig?.email,
access: domain.applyConfig?.access,
nameservers: domain.applyConfig?.nameservers,
timeout: domain.applyConfig?.timeout,
});
}
}, [domain, form]);
@ -134,32 +125,19 @@ const Edit = () => {
const navigate = useNavigate();
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const group = data.group == "emptyId" ? "" : data.group;
const targetAccess =
data.targetAccess === "emptyId" ? "" : data.targetAccess;
if (group == "" && targetAccess == "") {
form.setError("group", {
type: "manual",
message: "domain.management.edit.target.access.verify.msg",
});
form.setError("targetAccess", {
type: "manual",
message: "domain.management.edit.target.access.verify.msg",
});
return;
}
console.log(data);
const req: Domain = {
id: data.id as string,
crontab: "0 0 * * *",
domain: data.domain,
email: data.email,
access: data.access,
group: group,
targetAccess: targetAccess,
targetType: data.targetType,
variables: data.variables,
applyConfig: {
email: data.email ?? "",
access: data.access,
nameservers: data.nameservers,
timeout: data.timeout,
},
};
try {
@ -173,7 +151,7 @@ const Edit = () => {
title: t("succeed"),
description,
});
navigate("/domains");
if (!domain?.id) setTab("deploy");
} catch (e) {
const err = e as ClientResponseError;
@ -198,42 +176,56 @@ const Edit = () => {
{domain?.id ? t("domain.edit") : t("domain.add")}
</div>
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex">
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex md:mt-5">
<div
className={cn(
"cursor-pointer text-right",
tab === "base" ? "text-primary" : ""
tab === "apply" ? "text-primary" : ""
)}
onClick={() => {
setTab("base");
setTab("apply");
}}
>
{t("basic.setting")}
{t("apply.setting")}
</div>
<div
className={cn(
"cursor-pointer text-right",
tab === "advance" ? "text-primary" : ""
tab === "deploy" ? "text-primary" : ""
)}
onClick={() => {
setTab("advance");
if (!domain?.id) {
toast({
title: t("domain.management.edit.deploy.error"),
description: t("domain.management.edit.deploy.error"),
variant: "destructive",
});
return;
}
setTab("deploy");
}}
>
{t("advanced.setting")}
{t("deploy.setting")}
</div>
</div>
<div className="w-full md:w-[35em] bg-gray-100 dark:bg-gray-900 p-5 rounded mt-3 md:mt-0">
<div className="flex flex-col">
<div
className={cn(
"w-full md:w-[35em] p-5 rounded mt-3 md:mt-0",
tab == "deploy" && "hidden"
)}
>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
{/* 域名 */}
<FormField
control={form.control}
name="domain"
render={({ field }) => (
<FormItem hidden={tab != "base"}>
<FormItem>
<>
<StringList
value={field.value}
@ -248,11 +240,12 @@ const Edit = () => {
</FormItem>
)}
/>
{/* 邮箱 */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem hidden={tab != "base"}>
<FormItem>
<FormLabel className="flex w-full justify-between">
<div>
{t("email") +
@ -301,11 +294,12 @@ const Edit = () => {
</FormItem>
)}
/>
{/* 授权 */}
<FormField
control={form.control}
name="access"
render={({ field }) => (
<FormItem hidden={tab != "base"}>
<FormItem>
<FormLabel className="flex w-full justify-between">
<div>
{t("domain.management.edit.dns.access.label")}
@ -366,198 +360,28 @@ const Edit = () => {
</FormItem>
)}
/>
{/* 超时时间 */}
<FormField
control={form.control}
name="targetType"
name="timeout"
render={({ field }) => (
<FormItem hidden={tab != "base"}>
<FormLabel>
{t("domain.management.edit.target.type")}
</FormLabel>
<FormItem>
<FormLabel>{t("timeout")}</FormLabel>
<FormControl>
<Select
{...field}
onValueChange={(value) => {
setTargetType(value);
form.setValue("targetType", value);
}}
>
<SelectTrigger>
<SelectValue
<Input
type="number"
placeholder={t(
"domain.management.edit.target.type.not.empty.message"
"domain.management.edit.timeout.placeholder"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.target.type")}
</SelectLabel>
{targetTypeKeys.map((key) => (
<SelectItem key={key} value={key}>
<div className="flex items-center space-x-2">
<img
className="w-6"
src={targetTypeMap.get(key)?.[1]}
/>
<div>
{t(targetTypeMap.get(key)?.[0] || "")}
</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="targetAccess"
render={({ field }) => (
<FormItem hidden={tab != "base"}>
<FormLabel className="w-full flex justify-between">
<div>{t("domain.management.edit.target.access")}</div>
<AccessEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("add")}
</div>
}
op="add"
/>
</FormLabel>
<FormControl>
<Select
{...field}
onValueChange={(value) => {
form.setValue("targetAccess", value);
}}
>
<SelectTrigger>
<SelectValue
placeholder={t(
"domain.management.edit.target.access.not.empty.message"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t(
"domain.management.edit.target.access.content.label"
)}{" "}
{form.getValues().targetAccess}
</SelectLabel>
<SelectItem value="emptyId">
<div className="flex items-center space-x-2">
--
</div>
</SelectItem>
{targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}>
<div className="flex items-center space-x-2">
<img
className="w-6"
src={
accessTypeMap.get(item.configType)?.[1]
}
/>
<div>{item.name}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="group"
render={({ field }) => (
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
<FormLabel className="w-full flex justify-between">
<div>{t("domain.management.edit.group.label")}</div>
</FormLabel>
<FormControl>
<Select
{...field}
value={field.value}
defaultValue="emptyId"
onValueChange={(value) => {
form.setValue("group", value);
}}
>
<SelectTrigger>
<SelectValue
placeholder={t(
"domain.management.edit.group.not.empty.message"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="emptyId">
<div
className={cn(
"flex items-center space-x-2 rounded cursor-pointer"
)}
>
--
</div>
</SelectItem>
{accessGroups
.filter((item) => {
return (
item.expand && item.expand?.access.length > 0
onChange={(e) => {
form.setValue(
"timeout",
parseInt(e.target.value)
);
})
.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="variables"
render={({ field }) => (
<FormItem hidden={tab != "advance"}>
<FormLabel>{t("variables")}</FormLabel>
<FormControl>
<Textarea
placeholder={t(
"domain.management.edit.variables.placeholder"
)}
{...field}
className="placeholder:whitespace-pre-wrap"
}}
/>
</FormControl>
@ -566,21 +390,19 @@ const Edit = () => {
)}
/>
{/* nameservers */}
<FormField
control={form.control}
name="nameservers"
render={({ field }) => (
<FormItem hidden={tab != "advance"}>
<FormLabel>{t("dns")}</FormLabel>
<FormControl>
<Textarea
placeholder={t(
"domain.management.edit.dns.placeholder"
)}
{...field}
className="placeholder:whitespace-pre-wrap"
/>
</FormControl>
<FormItem>
<StringList
value={field.value ?? ""}
onValueChange={(val: string) => {
form.setValue("nameservers", val);
}}
valueType="dns"
></StringList>
<FormMessage />
</FormItem>
@ -588,11 +410,38 @@ const Edit = () => {
/>
<div className="flex justify-end">
<Button type="submit">{t("save")}</Button>
<Button type="submit">
{domain?.id ? t("save") : t("next")}
</Button>
</div>
</form>
</Form>
</div>
<div
className={cn(
"flex flex-col space-y-5",
tab == "apply" && "hidden"
)}
>
<div className="flex justify-end py-2 border-b">
<Button size={"sm"} variant={"secondary"}>
</Button>
</div>
<div className="w-full md:w-[35em] rounded mt-5 border">
<div className="">
<div className="flex justify-between text-sm p-3 items-center text-stone-700">
<div>ALIYUN-CDN</div>
<div className="flex space-x-2">
<EditIcon size={16} />
<Trash2 size={16} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>

View File

@ -97,6 +97,7 @@ const Home = () => {
const checkedDomains = domains.filter((domain) => domain.id === id);
const isChecked = checkedDomains[0].enabled;
const data = checkedDomains[0];
data.enabled = !isChecked;