mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 09:21:56 +08:00
multiple domain support
This commit is contained in:
parent
f036eb1cf2
commit
71e2555391
@ -172,13 +172,7 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
domains := []string{option.Domain}
|
||||
|
||||
// 如果是通配置符域名,把根域名也加入
|
||||
if strings.HasPrefix(option.Domain, "*.") && len(strings.Split(option.Domain, ".")) == 3 {
|
||||
rootDomain := strings.TrimPrefix(option.Domain, "*.")
|
||||
domains = append(domains, rootDomain)
|
||||
}
|
||||
domains := strings.Split(option.Domain, ";")
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
|
332
ui/dist/assets/index-B6ZTaWIO.js
vendored
Normal file
332
ui/dist/assets/index-B6ZTaWIO.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-ClQTEWmX.css
vendored
Normal file
1
ui/dist/assets/index-ClQTEWmX.css
vendored
Normal file
File diff suppressed because one or more lines are too long
322
ui/dist/assets/index-Dn4jGLHB.js
vendored
322
ui/dist/assets/index-Dn4jGLHB.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-I--T0qY3.css
vendored
1
ui/dist/assets/index-I--T0qY3.css
vendored
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||
<script type="module" crossorigin src="/assets/index-Dn4jGLHB.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-I--T0qY3.css">
|
||||
<script type="module" crossorigin src="/assets/index-B6ZTaWIO.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-ClQTEWmX.css">
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<div id="root"></div>
|
||||
|
242
ui/src/components/certimate/StringList.tsx
Normal file
242
ui/src/components/certimate/StringList.tsx
Normal file
@ -0,0 +1,242 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import Show from "../Show";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { FormControl, FormLabel } from "../ui/form";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
import { Input } from "../ui/input";
|
||||
import { z } from "zod";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Edit, Plus, Trash2 } from "lucide-react";
|
||||
|
||||
type StringListProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
valueType?: "domain" | "ip";
|
||||
onValueChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const titles: Record<string, string> = {
|
||||
domain: "domain",
|
||||
ip: "IP",
|
||||
};
|
||||
|
||||
const StringList = ({
|
||||
value,
|
||||
className,
|
||||
onValueChange,
|
||||
valueType = "domain",
|
||||
}: StringListProps) => {
|
||||
const [list, setList] = useState<string[]>([]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useMemo(() => {
|
||||
if (value) {
|
||||
setList(value.split(";"));
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
const changeList = () => {
|
||||
onValueChange(list.join(";"));
|
||||
};
|
||||
changeList();
|
||||
}, [list]);
|
||||
|
||||
const addVal = (val: string) => {
|
||||
if (list.includes(val)) {
|
||||
return;
|
||||
}
|
||||
setList([...list, val]);
|
||||
};
|
||||
|
||||
const editVal = (index: number, val: string) => {
|
||||
const newList = [...list];
|
||||
newList[index] = val;
|
||||
setList(newList);
|
||||
};
|
||||
|
||||
const onRemoveClick = (index: number) => {
|
||||
const newList = [...list];
|
||||
newList.splice(index, 1);
|
||||
setList(newList);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn(className)}>
|
||||
<FormLabel className="flex justify-between items-center">
|
||||
<div>{t(titles[valueType])}</div>
|
||||
|
||||
<Show when={list.length > 0}>
|
||||
<StringEdit
|
||||
op="add"
|
||||
onValueChange={(val: string) => {
|
||||
addVal(val);
|
||||
}}
|
||||
valueType={valueType}
|
||||
value={""}
|
||||
trigger={
|
||||
<div className="flex items-center text-primary">
|
||||
<Plus size={16} className="cursor-pointer " />
|
||||
|
||||
<div className="text-sm ">{t("add")}</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Show
|
||||
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>
|
||||
|
||||
<StringEdit
|
||||
value={""}
|
||||
trigger={t("add")}
|
||||
onValueChange={addVal}
|
||||
valueType={valueType}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="border rounded-md p-3 text-sm mt-2 text-gray-700 space-y-2">
|
||||
{list.map((item, index) => (
|
||||
<div key={index} className="flex justify-between items-center">
|
||||
<div>{item}</div>
|
||||
<div className="flex space-x-2">
|
||||
<StringEdit
|
||||
op="edit"
|
||||
valueType={valueType}
|
||||
trigger={
|
||||
<Edit
|
||||
size={16}
|
||||
className="cursor-pointer text-gray-600"
|
||||
/>
|
||||
}
|
||||
value={item}
|
||||
onValueChange={(val: string) => {
|
||||
editVal(index, val);
|
||||
}}
|
||||
/>
|
||||
<Trash2
|
||||
size={16}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
onRemoveClick(index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Show>
|
||||
</FormControl>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StringList;
|
||||
|
||||
type ValueType = "domain" | "ip";
|
||||
|
||||
type StringEditProps = {
|
||||
value: string;
|
||||
trigger: React.ReactNode;
|
||||
onValueChange: (value: string) => void;
|
||||
valueType: ValueType;
|
||||
op?: "add" | "edit";
|
||||
};
|
||||
|
||||
const StringEdit = ({
|
||||
trigger,
|
||||
value,
|
||||
onValueChange,
|
||||
op = "add",
|
||||
valueType,
|
||||
}: StringEditProps) => {
|
||||
const [currentValue, setCurrentValue] = useState<string>("");
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(value);
|
||||
}, [value]);
|
||||
|
||||
const domainSchema = z
|
||||
.string()
|
||||
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: t("domain.not.empty.verify.message"),
|
||||
});
|
||||
|
||||
const ipSchema = z.string().ip({ message: t("ip.not.empty.verify.message") });
|
||||
|
||||
const schedules: Record<ValueType, z.ZodString> = {
|
||||
domain: domainSchema,
|
||||
ip: ipSchema,
|
||||
};
|
||||
|
||||
const onSaveClick = useCallback(() => {
|
||||
const schema = schedules[valueType];
|
||||
|
||||
const resp = schema.safeParse(currentValue);
|
||||
if (!resp.success) {
|
||||
setError(JSON.parse(resp.error.message)[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentValue("");
|
||||
setOpen(false);
|
||||
setError("");
|
||||
|
||||
onValueChange(currentValue);
|
||||
}, [currentValue]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger className="text-primary">{trigger}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t(titles[valueType])}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={currentValue}
|
||||
onChange={(e) => {
|
||||
setCurrentValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Show when={error.length > 0}>
|
||||
<div className="text-red-500 text-sm">{error}</div>
|
||||
</Show>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onSaveClick();
|
||||
}}
|
||||
>
|
||||
{op === "add" ? t("add") : t("confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -26,6 +26,22 @@ export type Domain = {
|
||||
expand?: {
|
||||
lastDeployment?: Deployment;
|
||||
};
|
||||
|
||||
applyConfig?: ApplyConfig;
|
||||
deployConfig?: DeployConfig[];
|
||||
};
|
||||
|
||||
export type DeployConfig = {
|
||||
access: string;
|
||||
type: string;
|
||||
config?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type ApplyConfig = {
|
||||
access: string;
|
||||
email: string;
|
||||
timeout?: number;
|
||||
nameservers?: string;
|
||||
};
|
||||
|
||||
export type Statistic = {
|
||||
|
@ -83,6 +83,7 @@
|
||||
"pagination.prev": "Previous",
|
||||
"domain": "Domain",
|
||||
"domain.add": "Add Domain",
|
||||
"domain.edit":"Edit Domain",
|
||||
"domain.delete": "Delete Domain",
|
||||
"domain.not.empty.verify.message": "Please enter domain",
|
||||
"domain.management.name": "Domain List",
|
||||
|
@ -83,6 +83,7 @@
|
||||
"pagination.prev": "上一页",
|
||||
"domain": "域名",
|
||||
"domain.add": "新增域名",
|
||||
"domain.edit": "编辑域名",
|
||||
"domain.delete": "删除域名",
|
||||
"domain.not.empty.verify.message": "请输入域名",
|
||||
"domain.management.name": "域名列表",
|
||||
|
@ -57,7 +57,7 @@ const Dashboard = () => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">{t('dashboard')}</div>
|
||||
<div className="text-muted-foreground">{t("dashboard")}</div>
|
||||
</div>
|
||||
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
|
||||
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
|
||||
@ -66,7 +66,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t('dashboard.all')}
|
||||
{t("dashboard.all")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@ -91,7 +91,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t('dashboard.near.expired')}
|
||||
{t("dashboard.near.expired")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@ -120,7 +120,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t('dashboard.enabled')}
|
||||
{t("dashboard.enabled")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@ -144,7 +144,9 @@ const Dashboard = () => {
|
||||
<Ban size={48} strokeWidth={1} className="text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">{t('dashboard.not.enabled')}</div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t("dashboard.not.enabled")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.disabled ? (
|
||||
@ -168,22 +170,19 @@ const Dashboard = () => {
|
||||
|
||||
<div>
|
||||
<div className="text-muted-foreground mt-5 text-sm">
|
||||
{t('deployment.log.name')}
|
||||
{t("deployment.log.name")}
|
||||
</div>
|
||||
|
||||
{deployments?.length == 0 ? (
|
||||
<>
|
||||
<Alert className="max-w-[40em] mt-10">
|
||||
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||
<AlertTitle>{t("no.data")}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<div className="flex items-center mt-5">
|
||||
<div>
|
||||
<Smile className="text-yellow-400" size={36} />
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
{" "}
|
||||
{t('deployment.log.empty')}
|
||||
</div>
|
||||
<div className="ml-2"> {t("deployment.log.empty")}</div>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
@ -191,7 +190,7 @@ const Dashboard = () => {
|
||||
navigate("/edit");
|
||||
}}
|
||||
>
|
||||
{t('domain.add')}
|
||||
{t("domain.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
@ -200,16 +199,18 @@ const Dashboard = () => {
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-48">{t('domain')}</div>
|
||||
<div className="w-48">{t("domain")}</div>
|
||||
|
||||
<div className="w-24">{t('deployment.log.status')}</div>
|
||||
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||
<div className="w-24">{t("deployment.log.status")}</div>
|
||||
<div className="w-56">{t("deployment.log.stage")}</div>
|
||||
<div className="w-56 sm:ml-2 text-center">
|
||||
{t("deployment.log.last.execution.time")}
|
||||
</div>
|
||||
|
||||
<div className="grow">{t('operation')}</div>
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t('deployment.log.name')}
|
||||
{t("deployment.log.name")}
|
||||
</div>
|
||||
|
||||
{deployments?.map((deployment) => (
|
||||
@ -218,7 +219,14 @@ const Dashboard = () => {
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{deployment.expand.domain?.domain}
|
||||
{deployment.expand.domain?.domain
|
||||
.split(";")
|
||||
.map((domain: string) => (
|
||||
<>
|
||||
{domain}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<DeployState deployment={deployment} />
|
||||
@ -236,14 +244,14 @@ const Dashboard = () => {
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t('deployment.log.detail.button.text')}
|
||||
{t("deployment.log.detail.button.text")}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:max-w-5xl">
|
||||
<SheetHeader>
|
||||
<SheetTitle>
|
||||
{deployment.expand.domain?.domain}-{deployment.id}
|
||||
{t('deployment.log.detail')}
|
||||
{t("deployment.log.detail")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@ -39,6 +38,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EmailsSetting } from "@/domain/settings";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import StringList from "@/components/certimate/StringList";
|
||||
|
||||
const Edit = () => {
|
||||
const {
|
||||
@ -70,16 +70,16 @@ const Edit = () => {
|
||||
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: 'domain.not.empty.verify.message',
|
||||
domain: z.string().min(1, {
|
||||
message: "domain.not.empty.verify.message",
|
||||
}),
|
||||
email: z.string().email('email.valid.message').optional(),
|
||||
email: z.string().email("email.valid.message").optional(),
|
||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||
message: 'domain.management.edit.dns.access.not.empty.message',
|
||||
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',
|
||||
message: "domain.management.edit.target.type.not.empty.message",
|
||||
}),
|
||||
variables: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
@ -140,11 +140,11 @@ const Edit = () => {
|
||||
if (group == "" && targetAccess == "") {
|
||||
form.setError("group", {
|
||||
type: "manual",
|
||||
message: 'domain.management.edit.target.access.verify.msg',
|
||||
message: "domain.management.edit.target.access.verify.msg",
|
||||
});
|
||||
form.setError("targetAccess", {
|
||||
type: "manual",
|
||||
message: 'domain.management.edit.target.access.verify.msg',
|
||||
message: "domain.management.edit.target.access.verify.msg",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -164,13 +164,13 @@ const Edit = () => {
|
||||
|
||||
try {
|
||||
await save(req);
|
||||
let description = t('domain.management.edit.succeed.tips');
|
||||
let description = t("domain.management.edit.succeed.tips");
|
||||
if (req.id == "") {
|
||||
description = t('domain.management.add.succeed.tips');
|
||||
description = t("domain.management.add.succeed.tips");
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t('succeed'),
|
||||
title: t("succeed"),
|
||||
description,
|
||||
});
|
||||
navigate("/domains");
|
||||
@ -195,7 +195,7 @@ const Edit = () => {
|
||||
<div className="">
|
||||
<Toaster />
|
||||
<div className=" h-5 text-muted-foreground">
|
||||
{domain?.id ? t('domain.edit') : t('domain.add')}
|
||||
{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">
|
||||
@ -208,7 +208,7 @@ const Edit = () => {
|
||||
setTab("base");
|
||||
}}
|
||||
>
|
||||
{t('basic.setting')}
|
||||
{t("basic.setting")}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
@ -219,7 +219,7 @@ const Edit = () => {
|
||||
setTab("advance");
|
||||
}}
|
||||
>
|
||||
{t('advanced.setting')}
|
||||
{t("advanced.setting")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -234,10 +234,15 @@ const Edit = () => {
|
||||
name="domain"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel>{t('domain')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('domain.not.empty.verify.message')} {...field} />
|
||||
</FormControl>
|
||||
<>
|
||||
<StringList
|
||||
value={field.value}
|
||||
valueType="domain"
|
||||
onValueChange={(domain: string) => {
|
||||
form.setValue("domain", domain);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -249,12 +254,15 @@ const Edit = () => {
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>{t('email') + t('domain.management.edit.email.description')}</div>
|
||||
<div>
|
||||
{t("email") +
|
||||
t("domain.management.edit.email.description")}
|
||||
</div>
|
||||
<EmailsEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t('add')}
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@ -268,11 +276,15 @@ const Edit = () => {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('domain.management.edit.email.not.empty.message')} />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.email.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('email.list')}</SelectLabel>
|
||||
<SelectLabel>{t("email.list")}</SelectLabel>
|
||||
{(emails.content as EmailsSetting).emails.map(
|
||||
(item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
@ -295,12 +307,14 @@ const Edit = () => {
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>{t('domain.management.edit.dns.access.label')}</div>
|
||||
<div>
|
||||
{t("domain.management.edit.dns.access.label")}
|
||||
</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t('add')}
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
@ -315,11 +329,17 @@ const Edit = () => {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('domain.management.edit.access.not.empty.message')} />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.access.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('domain.management.edit.access.label')}</SelectLabel>
|
||||
<SelectLabel>
|
||||
{t("domain.management.edit.access.label")}
|
||||
</SelectLabel>
|
||||
{accesses
|
||||
.filter((item) => item.usage != "deploy")
|
||||
.map((item) => (
|
||||
@ -351,7 +371,9 @@ const Edit = () => {
|
||||
name="targetType"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel>{t('domain.management.edit.target.type')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("domain.management.edit.target.type")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
@ -361,11 +383,17 @@ const Edit = () => {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('domain.management.edit.target.type.not.empty.message')} />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.target.type.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('domain.management.edit.target.type')}</SelectLabel>
|
||||
<SelectLabel>
|
||||
{t("domain.management.edit.target.type")}
|
||||
</SelectLabel>
|
||||
{targetTypeKeys.map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center space-x-2">
|
||||
@ -373,7 +401,9 @@ const Edit = () => {
|
||||
className="w-6"
|
||||
src={targetTypeMap.get(key)?.[1]}
|
||||
/>
|
||||
<div>{t(targetTypeMap.get(key)?.[0] || '')}</div>
|
||||
<div>
|
||||
{t(targetTypeMap.get(key)?.[0] || "")}
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
@ -392,12 +422,12 @@ const Edit = () => {
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>{t('domain.management.edit.target.access')}</div>
|
||||
<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')}
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
@ -411,12 +441,19 @@ const Edit = () => {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('domain.management.edit.target.access.not.empty.message')} />
|
||||
<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}
|
||||
{t(
|
||||
"domain.management.edit.target.access.content.label"
|
||||
)}{" "}
|
||||
{form.getValues().targetAccess}
|
||||
</SelectLabel>
|
||||
<SelectItem value="emptyId">
|
||||
<div className="flex items-center space-x-2">
|
||||
@ -452,9 +489,7 @@ const Edit = () => {
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>
|
||||
{t('domain.management.edit.group.label')}
|
||||
</div>
|
||||
<div>{t("domain.management.edit.group.label")}</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
@ -466,7 +501,11 @@ const Edit = () => {
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('domain.management.edit.group.not.empty.message')} />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.group.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="emptyId">
|
||||
@ -511,10 +550,12 @@ const Edit = () => {
|
||||
name="variables"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>{t('variables')}</FormLabel>
|
||||
<FormLabel>{t("variables")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('domain.management.edit.variables.placeholder')}
|
||||
placeholder={t(
|
||||
"domain.management.edit.variables.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
@ -530,10 +571,12 @@ const Edit = () => {
|
||||
name="nameservers"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>{t('dns')}</FormLabel>
|
||||
<FormLabel>{t("dns")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('domain.management.edit.dns.placeholder')}
|
||||
placeholder={t(
|
||||
"domain.management.edit.dns.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
@ -545,7 +588,7 @@ const Edit = () => {
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
@ -41,7 +41,7 @@ const Home = () => {
|
||||
const toast = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
const location = useLocation();
|
||||
const query = new URLSearchParams(location.search);
|
||||
@ -129,12 +129,12 @@ const Home = () => {
|
||||
await save(domain);
|
||||
|
||||
toast.toast({
|
||||
title: t('operation.succeed'),
|
||||
description: t('domain.management.start.deploy.succeed.tips'),
|
||||
title: t("operation.succeed"),
|
||||
description: t("domain.management.start.deploy.succeed.tips"),
|
||||
});
|
||||
} catch (e) {
|
||||
toast.toast({
|
||||
title: t('domain.management.execution.failed'),
|
||||
title: t("domain.management.execution.failed"),
|
||||
description: (
|
||||
// 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
|
||||
<Trans i18nKey="domain.management.execution.failed.tips">
|
||||
@ -142,7 +142,9 @@ const Home = () => {
|
||||
<Link
|
||||
to={`/history?domain=${domain.id}`}
|
||||
className="underline text-blue-500"
|
||||
>text2</Link>
|
||||
>
|
||||
text2
|
||||
</Link>
|
||||
text3
|
||||
</Trans>
|
||||
),
|
||||
@ -176,10 +178,10 @@ const Home = () => {
|
||||
<div className="">
|
||||
<Toaster />
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">{t('domain.management.name')}</div>
|
||||
<Button onClick={handleCreateClick}>
|
||||
{t('domain.add')}
|
||||
</Button>
|
||||
<div className="text-muted-foreground">
|
||||
{t("domain.management.name")}
|
||||
</div>
|
||||
<Button onClick={handleCreateClick}>{t("domain.add")}</Button>
|
||||
</div>
|
||||
|
||||
{!domains.length ? (
|
||||
@ -190,26 +192,32 @@ const Home = () => {
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
{t('domain.management.empty')}
|
||||
{t("domain.management.empty")}
|
||||
</div>
|
||||
<Button onClick={handleCreateClick} className="mt-3">
|
||||
{t('domain.add')}
|
||||
{t("domain.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-36">{t('domain')}</div>
|
||||
<div className="w-40">{t('domain.management.expiry.date')}</div>
|
||||
<div className="w-32">{t('domain.management.last.execution.status')}</div>
|
||||
<div className="w-64">{t('domain.management.last.execution.stage')}</div>
|
||||
<div className="w-40 sm:ml-2">{t('domain.management.last.execution.time')}</div>
|
||||
<div className="w-24">{t('domain.management.enable')}</div>
|
||||
<div className="grow">{t('operation')}</div>
|
||||
<div className="w-36">{t("domain")}</div>
|
||||
<div className="w-40">{t("domain.management.expiry.date")}</div>
|
||||
<div className="w-32">
|
||||
{t("domain.management.last.execution.status")}
|
||||
</div>
|
||||
<div className="w-64">
|
||||
{t("domain.management.last.execution.stage")}
|
||||
</div>
|
||||
<div className="w-40 sm:ml-2">
|
||||
{t("domain.management.last.execution.time")}
|
||||
</div>
|
||||
<div className="w-24">{t("domain.management.enable")}</div>
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t('domain')}
|
||||
{t("domain")}
|
||||
</div>
|
||||
|
||||
{domains.map((domain) => (
|
||||
@ -217,15 +225,26 @@ const Home = () => {
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
key={domain.id}
|
||||
>
|
||||
<div className="sm:w-36 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{domain.domain}
|
||||
<div className="sm:w-36 w-full pt-1 sm:pt-0 flex items-center truncate">
|
||||
{domain.domain.split(";").map((item) => (
|
||||
<>
|
||||
{item}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<div>
|
||||
{domain.expiredAt ? (
|
||||
<>
|
||||
<div>{t('domain.management.expiry.date1', { date: 90 })}</div>
|
||||
<div>{t('domain.management.expiry.date2', { date: getDate(domain.expiredAt) })}</div>
|
||||
<div>
|
||||
{t("domain.management.expiry.date1", { date: 90 })}
|
||||
</div>
|
||||
<div>
|
||||
{t("domain.management.expiry.date2", {
|
||||
date: getDate(domain.expiredAt),
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
"---"
|
||||
@ -269,7 +288,7 @@ const Home = () => {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
||||
{domain.enabled ? t('disable') : t('enable')}
|
||||
{domain.enabled ? t("disable") : t("enable")}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@ -281,7 +300,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleHistoryClick(domain.id)}
|
||||
>
|
||||
{t('deployment.log.name')}
|
||||
{t("deployment.log.name")}
|
||||
</Button>
|
||||
<Show when={domain.enabled ? true : false}>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
@ -290,7 +309,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleRightNowClick(domain)}
|
||||
>
|
||||
{t('domain.management.start.deploying')}
|
||||
{t("domain.management.start.deploying")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@ -307,7 +326,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleForceClick(domain)}
|
||||
>
|
||||
{t('domain.management.forced.deployment')}
|
||||
{t("domain.management.forced.deployment")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@ -318,7 +337,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleDownloadClick(domain)}
|
||||
>
|
||||
{t('download')}
|
||||
{t("download")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@ -328,24 +347,26 @@ const Home = () => {
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t('delete')}
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t('domain.delete')}</AlertDialogTitle>
|
||||
<AlertDialogTitle>
|
||||
{t("domain.delete")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('domain.management.delete.confirm')}
|
||||
{t("domain.management.delete.confirm")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
handleDeleteClick(domain.id);
|
||||
}}
|
||||
>
|
||||
{t('confirm')}
|
||||
{t("confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -357,7 +378,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleEditClick(domain.id)}
|
||||
>
|
||||
{t('edit')}
|
||||
{t("edit")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
@ -40,20 +40,17 @@ const History = () => {
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-[80vh] overflow-hidden">
|
||||
<div className="text-muted-foreground">{t('deployment.log.name')}</div>
|
||||
<div className="text-muted-foreground">{t("deployment.log.name")}</div>
|
||||
{!deployments?.length ? (
|
||||
<>
|
||||
<Alert className="max-w-[40em] mx-auto mt-20">
|
||||
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||
<AlertTitle>{t("no.data")}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<div className="flex items-center mt-5">
|
||||
<div>
|
||||
<Smile className="text-yellow-400" size={36} />
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
{" "}
|
||||
{t('deployment.log.empty')}
|
||||
</div>
|
||||
<div className="ml-2"> {t("deployment.log.empty")}</div>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
@ -61,7 +58,7 @@ const History = () => {
|
||||
navigate("/");
|
||||
}}
|
||||
>
|
||||
{t('domain.add')}
|
||||
{t("domain.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
@ -70,16 +67,18 @@ const History = () => {
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-48">{t('domain')}</div>
|
||||
<div className="w-48">{t("domain")}</div>
|
||||
|
||||
<div className="w-24">{t('deployment.log.status')}</div>
|
||||
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||
<div className="w-24">{t("deployment.log.status")}</div>
|
||||
<div className="w-56">{t("deployment.log.stage")}</div>
|
||||
<div className="w-56 sm:ml-2 text-center">
|
||||
{t("deployment.log.last.execution.time")}
|
||||
</div>
|
||||
|
||||
<div className="grow">{t('operation')}</div>
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t('deployment.log.name')}
|
||||
{t("deployment.log.name")}
|
||||
</div>
|
||||
|
||||
{deployments?.map((deployment) => (
|
||||
@ -88,7 +87,14 @@ const History = () => {
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{deployment.expand.domain?.domain}
|
||||
{deployment.expand.domain?.domain
|
||||
.split(";")
|
||||
.map((domain: string) => (
|
||||
<>
|
||||
{domain}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<DeployState deployment={deployment} />
|
||||
@ -106,14 +112,14 @@ const History = () => {
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t('deployment.log.detail.button.text')}
|
||||
{t("deployment.log.detail.button.text")}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:max-w-5xl">
|
||||
<SheetHeader>
|
||||
<SheetTitle>
|
||||
{deployment.expand.domain?.domain}-{deployment.id}
|
||||
{t('deployment.log.detail')}
|
||||
{t("deployment.log.detail")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
|
Loading…
x
Reference in New Issue
Block a user