mirror of https://github.com/usual2970/certimate
wip: i18n chinese
parent
85df8eb09d
commit
0abb030889
|
@ -1,4 +1,5 @@
|
||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +12,8 @@ import { useTheme } from "./ThemeProvider";
|
||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
@ -23,13 +26,13 @@ export function ThemeToggle() {
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
浅色
|
{t('theme.light')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
暗黑
|
{t('theme.dark')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
系统
|
{t('theme.system')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
type DeployProgressProps = {
|
type DeployProgressProps = {
|
||||||
|
@ -6,26 +8,40 @@ type DeployProgressProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let rs = <> </>;
|
let rs = <> </>;
|
||||||
if (phase === "check") {
|
if (phase === "check") {
|
||||||
if (phaseSuccess) {
|
if (phaseSuccess) {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">获取</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-red-600">检查 </div>
|
<div className="text-xs text-nowrap text-red-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">获取</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,21 +51,33 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
||||||
if (phaseSuccess) {
|
if (phaseSuccess) {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-red-600">获取</div>
|
<div className="text-xs text-nowrap text-red-600">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow" />
|
<Separator className="h-1 grow" />
|
||||||
<div className="text-xs text-nowrap text-muted-foreground">部署</div>
|
<div className="text-xs text-nowrap text-muted-foreground">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -59,21 +87,33 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
||||||
if (phaseSuccess) {
|
if (phaseSuccess) {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-green-600">部署</div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rs = (
|
rs = (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-xs text-nowrap text-green-600">检查 </div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.check')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-green-600">获取</div>
|
<div className="text-xs text-nowrap text-green-600">
|
||||||
|
{t('deploy.progress.apply')}
|
||||||
|
</div>
|
||||||
<Separator className="h-1 grow bg-green-600" />
|
<Separator className="h-1 grow bg-green-600" />
|
||||||
<div className="text-xs text-nowrap text-red-600">部署</div>
|
<div className="text-xs text-nowrap text-red-600">
|
||||||
|
{t('deploy.progress.deploy')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type DingTalkSetting = {
|
type DingTalkSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +18,7 @@ type DingTalkSetting = {
|
||||||
|
|
||||||
const DingTalk = () => {
|
const DingTalk = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
|
@ -70,15 +72,15 @@ const DingTalk = () => {
|
||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ const DingTalk = () => {
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
|
@ -136,7 +138,7 @@ const DingTalk = () => {
|
||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "@/domain/settings";
|
} from "@/domain/settings";
|
||||||
import { getSetting, update } from "@/repository/settings";
|
import { getSetting, update } from "@/repository/settings";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const NotifyTemplate = () => {
|
const NotifyTemplate = () => {
|
||||||
const [id, setId] = useState("");
|
const [id, setId] = useState("");
|
||||||
|
@ -17,6 +18,7 @@ const NotifyTemplate = () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const featchData = async () => {
|
const featchData = async () => {
|
||||||
|
@ -66,8 +68,8 @@ const NotifyTemplate = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "通知模板保存成功",
|
description: t('setting.notify.template.save.succeed'),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ const NotifyTemplate = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="text-muted-foreground text-sm mt-1">
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
可选的变量, COUNT:即将过期张数
|
{t('setting.notify.template.variables.tips.title')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
|
@ -92,10 +94,10 @@ const NotifyTemplate = () => {
|
||||||
}}
|
}}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
<div className="text-muted-foreground text-sm mt-1">
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
可选的变量, COUNT:即将过期张数,DOMAINS:域名列表
|
{t('setting.notify.template.variables.tips.content')}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<Button onClick={handleSaveClick}>保存</Button>
|
<Button onClick={handleSaveClick}>{t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
||||||
import { update } from "@/repository/settings";
|
import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type TelegramSetting = {
|
type TelegramSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +18,7 @@ type TelegramSetting = {
|
||||||
|
|
||||||
const Telegram = () => {
|
const Telegram = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [telegram, setTelegram] = useState<TelegramSetting>({
|
const [telegram, setTelegram] = useState<TelegramSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
|
@ -70,15 +72,15 @@ const Telegram = () => {
|
||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -128,7 +130,7 @@ const Telegram = () => {
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
|
@ -137,7 +139,7 @@ const Telegram = () => {
|
||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { update } from "@/repository/settings";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
import { isValidURL } from "@/lib/url";
|
import { isValidURL } from "@/lib/url";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type WebhookSetting = {
|
type WebhookSetting = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -18,6 +19,7 @@ type WebhookSetting = {
|
||||||
|
|
||||||
const Webhook = () => {
|
const Webhook = () => {
|
||||||
const { config, setChannels } = useNotify();
|
const { config, setChannels } = useNotify();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [webhook, setWebhook] = useState<WebhookSetting>({
|
const [webhook, setWebhook] = useState<WebhookSetting>({
|
||||||
id: config.id ?? "",
|
id: config.id ?? "",
|
||||||
|
@ -59,8 +61,8 @@ const Webhook = () => {
|
||||||
webhook.data.url = webhook.data.url.trim();
|
webhook.data.url = webhook.data.url.trim();
|
||||||
if (!isValidURL(webhook.data.url)) {
|
if (!isValidURL(webhook.data.url)) {
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "Url格式不正确",
|
description: t('setting.notify.config.save.failed.url.not.valid'),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -79,15 +81,15 @@ const Webhook = () => {
|
||||||
|
|
||||||
setChannels(resp);
|
setChannels(resp);
|
||||||
toast({
|
toast({
|
||||||
title: "保存成功",
|
title: t('save.succeed'),
|
||||||
description: "配置保存成功",
|
description: t('setting.notify.config.save.succeed'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = getErrMessage(e);
|
const msg = getErrMessage(e);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "保存失败",
|
title: t('save.failed'),
|
||||||
description: "配置保存失败:" + msg,
|
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -123,7 +125,7 @@ const Webhook = () => {
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
|
@ -132,7 +134,7 @@ const Webhook = () => {
|
||||||
handleSaveClick();
|
handleSaveClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存
|
{t('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
|
@ -145,7 +146,9 @@ const FormMessage = React.forwardRef<
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField()
|
||||||
const body = error ? String(error?.message) : children
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const body = error ? t(String(error?.message)) : children
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
<nav
|
<nav
|
||||||
|
@ -62,33 +63,41 @@ PaginationLink.displayName = "PaginationLink";
|
||||||
const PaginationPrevious = ({
|
const PaginationPrevious = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||||
<PaginationLink
|
const { t } = useTranslation()
|
||||||
aria-label="Go to previous page"
|
|
||||||
size="default"
|
return (
|
||||||
className={cn("gap-1 pl-2.5", className)}
|
<PaginationLink
|
||||||
{...props}
|
aria-label="Go to previous page"
|
||||||
>
|
size="default"
|
||||||
<ChevronLeft className="h-4 w-4" />
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
<span>上一页</span>
|
{...props}
|
||||||
</PaginationLink>
|
>
|
||||||
);
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>{t('pagination.prev')}</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
};
|
||||||
PaginationPrevious.displayName = "PaginationPrevious";
|
PaginationPrevious.displayName = "PaginationPrevious";
|
||||||
|
|
||||||
const PaginationNext = ({
|
const PaginationNext = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||||
<PaginationLink
|
const { t } = useTranslation()
|
||||||
aria-label="Go to next page"
|
|
||||||
size="default"
|
return (
|
||||||
className={cn("gap-1 pr-2.5", className)}
|
<PaginationLink
|
||||||
{...props}
|
aria-label="Go to next page"
|
||||||
>
|
size="default"
|
||||||
<span>下一页</span>
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
<ChevronRight className="h-4 w-4" />
|
{...props}
|
||||||
</PaginationLink>
|
>
|
||||||
);
|
<span>{t('pagination.next')}</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
};
|
||||||
PaginationNext.displayName = "PaginationNext";
|
PaginationNext.displayName = "PaginationNext";
|
||||||
|
|
||||||
const PaginationEllipsis = ({
|
const PaginationEllipsis = ({
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
useNavigate,
|
useNavigate,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
@ -22,12 +23,14 @@ import { cn } from "@/lib/utils";
|
||||||
import { ConfigProvider } from "@/providers/config";
|
import { ConfigProvider } from "@/providers/config";
|
||||||
import { getPb } from "@/repository/api";
|
import { getPb } from "@/repository/api";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||||
|
import LocaleToggle from "@/components/LocaleToggle";
|
||||||
|
|
||||||
import Version from "@/components/certimate/Version";
|
import Version from "@/components/certimate/Version";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
|
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
|
||||||
return <Navigate to="/login" />;
|
return <Navigate to="/login" />;
|
||||||
|
@ -70,7 +73,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Home className="h-4 w-4" />
|
<Home className="h-4 w-4" />
|
||||||
控制面板
|
{t('dashboard')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/domains"
|
to="/domains"
|
||||||
|
@ -80,7 +83,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Earth className="h-4 w-4" />
|
<Earth className="h-4 w-4" />
|
||||||
域名列表
|
{t('domain.management.name')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/access"
|
to="/access"
|
||||||
|
@ -90,7 +93,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Server className="h-4 w-4" />
|
<Server className="h-4 w-4" />
|
||||||
授权管理
|
{t('menu.auth.management')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
@ -101,7 +104,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<History className="h-4 w-4" />
|
<History className="h-4 w-4" />
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,7 +141,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Home className="h-5 w-5" />
|
<Home className="h-5 w-5" />
|
||||||
控制面板
|
{t('dashboard')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/domains"
|
to="/domains"
|
||||||
|
@ -148,7 +151,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Earth className="h-5 w-5" />
|
<Earth className="h-5 w-5" />
|
||||||
域名列表
|
{t('domain.management.name')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/access"
|
to="/access"
|
||||||
|
@ -158,7 +161,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Server className="h-5 w-5" />
|
<Server className="h-5 w-5" />
|
||||||
授权管理
|
{t('menu.auth.management')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
|
@ -169,13 +172,14 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<History className="h-5 w-5" />
|
<History className="h-5 w-5" />
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
<div className="w-full flex-1"></div>
|
<div className="w-full flex-1"></div>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
<LocaleToggle />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
@ -188,15 +192,15 @@ export default function Dashboard() {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>账户</DropdownMenuLabel>
|
<DropdownMenuLabel>{t('account')}</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItem onClick={handleSettingClick}>
|
<DropdownMenuItem onClick={handleSettingClick}>
|
||||||
偏好设置
|
{t('setting')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuItem onClick={handleLogoutClick}>
|
<DropdownMenuItem onClick={handleLogoutClick}>
|
||||||
退出
|
{t('logout')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
@ -4,11 +4,13 @@ import { KeyRound, Megaphone, UserRound } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const SettingLayout = () => {
|
const SettingLayout = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [tabValue, setTabValue] = useState("account");
|
const [tabValue, setTabValue] = useState("account");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pathname = location.pathname;
|
const pathname = location.pathname;
|
||||||
|
@ -20,7 +22,7 @@ const SettingLayout = () => {
|
||||||
<div>
|
<div>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
||||||
偏好设置
|
{t('setting')}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
|
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
|
||||||
<Tabs defaultValue="account" className="w-full" value={tabValue}>
|
<Tabs defaultValue="account" className="w-full" value={tabValue}>
|
||||||
|
@ -33,7 +35,7 @@ const SettingLayout = () => {
|
||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<UserRound size={14} />
|
<UserRound size={14} />
|
||||||
<div className="ml-1">账户</div>
|
<div className="ml-1">{t('account')}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="password"
|
value="password"
|
||||||
|
@ -43,7 +45,7 @@ const SettingLayout = () => {
|
||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<KeyRound size={14} />
|
<KeyRound size={14} />
|
||||||
<div className="ml-1">密码</div>
|
<div className="ml-1">{t('password')}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
|
@ -54,7 +56,7 @@ const SettingLayout = () => {
|
||||||
className="px-5"
|
className="px-5"
|
||||||
>
|
>
|
||||||
<Megaphone size={14} />
|
<Megaphone size={14} />
|
||||||
<div className="ml-1">消息推送</div>
|
<div className="ml-1">{t('setting.notify.menu')}</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value={tabValue}>
|
<TabsContent value={tabValue}>
|
||||||
|
|
|
@ -24,12 +24,14 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [statistic, setStatistic] = useState<Statistic>();
|
const [statistic, setStatistic] = useState<Statistic>();
|
||||||
const [deployments, setDeployments] = useState<Deployment[]>();
|
const [deployments, setDeployments] = useState<Deployment[]>();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchStatistic = async () => {
|
const fetchStatistic = async () => {
|
||||||
|
@ -55,7 +57,7 @@ const Dashboard = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">控制面板</div>
|
<div className="text-muted-foreground">{t('dashboard')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
|
<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">
|
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
|
||||||
|
@ -63,7 +65,9 @@ const Dashboard = () => {
|
||||||
<SquareSigma size={48} strokeWidth={1} className="text-blue-400" />
|
<SquareSigma size={48} strokeWidth={1} className="text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">所有</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.all')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.total ? (
|
{statistic?.total ? (
|
||||||
|
@ -74,7 +78,9 @@ const Dashboard = () => {
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,7 +90,9 @@ const Dashboard = () => {
|
||||||
<CalendarX2 size={48} strokeWidth={1} className="text-red-400" />
|
<CalendarX2 size={48} strokeWidth={1} className="text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">即将过期</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.near.expired')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.expired ? (
|
{statistic?.expired ? (
|
||||||
|
@ -95,7 +103,9 @@ const Dashboard = () => {
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,7 +119,9 @@ const Dashboard = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">启用中</div>
|
<div className="text-muted-foreground font-semibold">
|
||||||
|
{t('dashboard.enabled')}
|
||||||
|
</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.enabled ? (
|
{statistic?.enabled ? (
|
||||||
|
@ -120,7 +132,9 @@ const Dashboard = () => {
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -130,7 +144,7 @@ const Dashboard = () => {
|
||||||
<Ban size={48} strokeWidth={1} className="text-gray-400" />
|
<Ban size={48} strokeWidth={1} className="text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground font-semibold">未启用</div>
|
<div className="text-muted-foreground font-semibold">{t('dashboard.not.enabled')}</div>
|
||||||
<div className="flex items-baseline">
|
<div className="flex items-baseline">
|
||||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||||
{statistic?.disabled ? (
|
{statistic?.disabled ? (
|
||||||
|
@ -144,19 +158,23 @@ const Dashboard = () => {
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 text-stone-700 dark:text-stone-200">个</div>
|
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||||
|
{t("dashboard.unit")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground mt-5 text-sm">部署历史</div>
|
<div className="text-muted-foreground mt-5 text-sm">
|
||||||
|
{t('deployment.log.name')}
|
||||||
|
</div>
|
||||||
|
|
||||||
{deployments?.length == 0 ? (
|
{deployments?.length == 0 ? (
|
||||||
<>
|
<>
|
||||||
<Alert className="max-w-[40em] mt-10">
|
<Alert className="max-w-[40em] mt-10">
|
||||||
<AlertTitle>暂无数据</AlertTitle>
|
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<div className="flex items-center mt-5">
|
<div className="flex items-center mt-5">
|
||||||
<div>
|
<div>
|
||||||
|
@ -164,7 +182,7 @@ const Dashboard = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
{" "}
|
{" "}
|
||||||
你暂未创建任何部署,请先添加域名进行部署吧!
|
{t('deployment.log.empty')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex justify-end">
|
<div className="mt-2 flex justify-end">
|
||||||
|
@ -173,7 +191,7 @@ const Dashboard = () => {
|
||||||
navigate("/edit");
|
navigate("/edit");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
|
@ -182,16 +200,16 @@ 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="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">域名</div>
|
<div className="w-48">{t('domain')}</div>
|
||||||
|
|
||||||
<div className="w-24">状态</div>
|
<div className="w-24">{t('deployment.log.status')}</div>
|
||||||
<div className="w-56">阶段</div>
|
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||||
<div className="w-56 sm:ml-2 text-center">最近执行时间</div>
|
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||||
|
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deployments?.map((deployment) => (
|
{deployments?.map((deployment) => (
|
||||||
|
@ -218,14 +236,14 @@ const Dashboard = () => {
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
日志
|
{t('deployment.log.detail.button.text')}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="sm:max-w-5xl">
|
<SheetContent className="sm:max-w-5xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>
|
<SheetTitle>
|
||||||
{deployment.expand.domain?.domain}-{deployment.id}
|
{deployment.expand.domain?.domain}-{deployment.id}
|
||||||
部署详情
|
{t('deployment.log.detail')}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||||
|
|
|
@ -38,6 +38,7 @@ import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { EmailsSetting } from "@/domain/settings";
|
import { EmailsSetting } from "@/domain/settings";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -47,6 +48,7 @@ const Edit = () => {
|
||||||
const [domain, setDomain] = useState<Domain>();
|
const [domain, setDomain] = useState<Domain>();
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [tab, setTab] = useState<"base" | "advance">("base");
|
const [tab, setTab] = useState<"base" | "advance">("base");
|
||||||
|
|
||||||
|
@ -69,15 +71,15 @@ const Edit = () => {
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||||
message: "请输入正确的域名",
|
message: t('domain.management.edit.domain.verify.tips'),
|
||||||
}),
|
}),
|
||||||
email: z.string().email().optional(),
|
email: z.string().email().optional(),
|
||||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||||
message: "请选择DNS服务商授权配置",
|
message: t('domain.management.edit.dns.verify.tips'),
|
||||||
}),
|
}),
|
||||||
targetAccess: z.string().optional(),
|
targetAccess: z.string().optional(),
|
||||||
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
||||||
message: "请选择部署服务类型",
|
message: t('domain.management.edit.target.type.verify.tips'),
|
||||||
}),
|
}),
|
||||||
variables: z.string().optional(),
|
variables: z.string().optional(),
|
||||||
group: z.string().optional(),
|
group: z.string().optional(),
|
||||||
|
|
|
@ -35,11 +35,13 @@ import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
|
||||||
import { Earth } from "lucide-react";
|
import { Earth } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const query = new URLSearchParams(location.search);
|
const query = new URLSearchParams(location.search);
|
||||||
|
@ -127,23 +129,22 @@ const Home = () => {
|
||||||
await save(domain);
|
await save(domain);
|
||||||
|
|
||||||
toast.toast({
|
toast.toast({
|
||||||
title: "操作成功",
|
title: t('operation.succeed'),
|
||||||
description: "已发起部署,请稍后查看部署日志。",
|
description: t('domain.management.start.deploy.succeed.tips'),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.toast({
|
toast.toast({
|
||||||
title: "执行失败",
|
title: t('domain.management.execution.failed'),
|
||||||
description: (
|
description: (
|
||||||
<>
|
// 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
|
||||||
执行失败,请查看
|
<Trans i18nKey="domain.management.execution.failed.tips">
|
||||||
|
text1
|
||||||
<Link
|
<Link
|
||||||
to={`/history?domain=${domain.id}`}
|
to={`/history?domain=${domain.id}`}
|
||||||
className="underline text-blue-500"
|
className="underline text-blue-500"
|
||||||
>
|
>text2</Link>
|
||||||
部署日志
|
text3
|
||||||
</Link>
|
</Trans>
|
||||||
查看详情。
|
|
||||||
</>
|
|
||||||
),
|
),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
@ -175,8 +176,10 @@ const Home = () => {
|
||||||
<div className="">
|
<div className="">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">域名列表</div>
|
<div className="text-muted-foreground">{t('domain.management.name')}</div>
|
||||||
<Button onClick={handleCreateClick}>新增域名</Button>
|
<Button onClick={handleCreateClick}>
|
||||||
|
{t('domain.add')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!domains.length ? (
|
{!domains.length ? (
|
||||||
|
@ -187,26 +190,26 @@ const Home = () => {
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||||
请添加域名开始部署证书吧。
|
{t('domain.management.empty')}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleCreateClick} className="mt-3">
|
<Button onClick={handleCreateClick} className="mt-3">
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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="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">域名</div>
|
<div className="w-36">{t('domain')}</div>
|
||||||
<div className="w-40">有效期限</div>
|
<div className="w-40">{t('domain.management.expiry.date')}</div>
|
||||||
<div className="w-32">最近执行状态</div>
|
<div className="w-32">{t('domain.management.last.execution.status')}</div>
|
||||||
<div className="w-64">最近执行阶段</div>
|
<div className="w-64">{t('domain.management.last.execution.stage')}</div>
|
||||||
<div className="w-40 sm:ml-2">最近执行时间</div>
|
<div className="w-40 sm:ml-2">{t('domain.management.last.execution.time')}</div>
|
||||||
<div className="w-24">是否启用</div>
|
<div className="w-24">{t('domain.management.enable')}</div>
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
域名
|
{t('domain')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{domains.map((domain) => (
|
{domains.map((domain) => (
|
||||||
|
@ -221,8 +224,8 @@ const Home = () => {
|
||||||
<div>
|
<div>
|
||||||
{domain.expiredAt ? (
|
{domain.expiredAt ? (
|
||||||
<>
|
<>
|
||||||
<div>有效期90天</div>
|
<div>{t('domain.management.expiry.date1', { date: 90 })}</div>
|
||||||
<div>{getDate(domain.expiredAt)}到期</div>
|
<div>{t('domain.management.expiry.date2', { date: getDate(domain.expiredAt) })}</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"---"
|
"---"
|
||||||
|
@ -266,7 +269,7 @@ const Home = () => {
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
||||||
{domain.enabled ? "禁用" : "启用"}
|
{domain.enabled ? t('disable') : t('enable')}
|
||||||
</div>
|
</div>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -278,7 +281,7 @@ const Home = () => {
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleHistoryClick(domain.id)}
|
onClick={() => handleHistoryClick(domain.id)}
|
||||||
>
|
>
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</Button>
|
</Button>
|
||||||
<Show when={domain.enabled ? true : false}>
|
<Show when={domain.enabled ? true : false}>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||||
|
@ -287,7 +290,7 @@ const Home = () => {
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleRightNowClick(domain)}
|
onClick={() => handleRightNowClick(domain)}
|
||||||
>
|
>
|
||||||
立即部署
|
{t('domain.management.start.deploying')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -304,7 +307,7 @@ const Home = () => {
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleForceClick(domain)}
|
onClick={() => handleForceClick(domain)}
|
||||||
>
|
>
|
||||||
强行部署
|
{t('domain.management.forced.deployment')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -315,7 +318,7 @@ const Home = () => {
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleDownloadClick(domain)}
|
onClick={() => handleDownloadClick(domain)}
|
||||||
>
|
>
|
||||||
下载
|
{t('download')}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -325,24 +328,24 @@ const Home = () => {
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
删除
|
{t('delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>删除域名</AlertDialogTitle>
|
<AlertDialogTitle>{t('domain.delete')}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
确定要删除域名吗?
|
{t('domain.management.delete.confirm')}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteClick(domain.id);
|
handleDeleteClick(domain.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确认
|
{t('confirm')}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
@ -354,7 +357,7 @@ const Home = () => {
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => handleEditClick(domain.id)}
|
onClick={() => handleEditClick(domain.id)}
|
||||||
>
|
>
|
||||||
编辑
|
{t('edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -17,11 +17,13 @@ import { list } from "@/repository/deployment";
|
||||||
import { Smile } from "lucide-react";
|
import { Smile } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const History = () => {
|
const History = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [deployments, setDeployments] = useState<Deployment[]>();
|
const [deployments, setDeployments] = useState<Deployment[]>();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const { t } = useTranslation();
|
||||||
const domain = searchParams.get("domain");
|
const domain = searchParams.get("domain");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -38,11 +40,11 @@ const History = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-[80vh] overflow-hidden">
|
<ScrollArea className="h-[80vh] overflow-hidden">
|
||||||
<div className="text-muted-foreground">部署历史</div>
|
<div className="text-muted-foreground">{t('deployment.log.name')}</div>
|
||||||
{!deployments?.length ? (
|
{!deployments?.length ? (
|
||||||
<>
|
<>
|
||||||
<Alert className="max-w-[40em] mx-auto mt-20">
|
<Alert className="max-w-[40em] mx-auto mt-20">
|
||||||
<AlertTitle>暂无数据</AlertTitle>
|
<AlertTitle>{t('no.data')}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<div className="flex items-center mt-5">
|
<div className="flex items-center mt-5">
|
||||||
<div>
|
<div>
|
||||||
|
@ -50,7 +52,7 @@ const History = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
{" "}
|
{" "}
|
||||||
你暂未创建任何部署,请先添加域名进行部署吧!
|
{t('deployment.log.empty')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex justify-end">
|
<div className="mt-2 flex justify-end">
|
||||||
|
@ -59,7 +61,7 @@ const History = () => {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加域名
|
{t('domain.add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
|
@ -68,16 +70,16 @@ 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="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">域名</div>
|
<div className="w-48">{t('domain')}</div>
|
||||||
|
|
||||||
<div className="w-24">状态</div>
|
<div className="w-24">{t('deployment.log.status')}</div>
|
||||||
<div className="w-56">阶段</div>
|
<div className="w-56">{t('deployment.log.stage')}</div>
|
||||||
<div className="w-56 sm:ml-2 text-center">最近执行时间</div>
|
<div className="w-56 sm:ml-2 text-center">{t('deployment.log.last.execution.time')}</div>
|
||||||
|
|
||||||
<div className="grow">操作</div>
|
<div className="grow">{t('operation')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||||
部署历史
|
{t('deployment.log.name')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deployments?.map((deployment) => (
|
{deployments?.map((deployment) => (
|
||||||
|
@ -104,14 +106,14 @@ const History = () => {
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
日志
|
{t('deployment.log.detail.button.text')}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="sm:max-w-5xl">
|
<SheetContent className="sm:max-w-5xl">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>
|
<SheetTitle>
|
||||||
{deployment.expand.domain?.domain}-{deployment.id}
|
{deployment.expand.domain?.domain}-{deployment.id}
|
||||||
部署详情
|
{t('deployment.log.detail')}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
|
@ -11,20 +16,19 @@ import { Input } from "@/components/ui/input";
|
||||||
import { getErrMessage } from "@/lib/error";
|
import { getErrMessage } from "@/lib/error";
|
||||||
import { getPb } from "@/repository/api";
|
import { getPb } from "@/repository/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
username: z.string().email({
|
username: z.string().email({
|
||||||
message: "请输入正确的邮箱地址",
|
message: "login.username.no.empty.message",
|
||||||
}),
|
}),
|
||||||
password: z.string().min(10, {
|
password: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "login.password.length.message",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
@ -61,7 +65,7 @@ const Login = () => {
|
||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>用户名</FormLabel>
|
<FormLabel>{t('username')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="email" {...field} />
|
<Input placeholder="email" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -76,7 +80,7 @@ const Login = () => {
|
||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>密码</FormLabel>
|
<FormLabel>{t('password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="shadcn" {...field} type="password" />
|
<Input placeholder="shadcn" {...field} type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -86,7 +90,7 @@ const Login = () => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">登录</Button>
|
<Button type="submit">{t('login.submit')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -15,16 +15,18 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email("请输入正确的邮箱"),
|
email: z.string().email("setting.account.email.valid.message"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
|
|
||||||
|
@ -43,8 +45,8 @@ const Account = () => {
|
||||||
|
|
||||||
getPb().authStore.clear();
|
getPb().authStore.clear();
|
||||||
toast({
|
toast({
|
||||||
title: "修改账户邮箱功",
|
title: t("setting.account.email.change.succeed"),
|
||||||
description: "请重新登录",
|
description: t("setting.account.log.back.in"),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
|
@ -52,7 +54,7 @@ const Account = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrMessage(e);
|
const message = getErrMessage(e);
|
||||||
toast({
|
toast({
|
||||||
title: "修改账户邮箱失败",
|
title: t("setting.account.email.change.failed"),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
@ -72,10 +74,10 @@ const Account = () => {
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>邮箱</FormLabel>
|
<FormLabel>{t('email')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="请输入邮箱"
|
placeholder={t('setting.email.placeholder')}
|
||||||
{...field}
|
{...field}
|
||||||
type="email"
|
type="email"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -92,10 +94,10 @@ const Account = () => {
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{changed ? (
|
{changed ? (
|
||||||
<Button type="submit">确认修改</Button>
|
<Button type="submit">{t('setting.submit')}</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button type="submit" disabled variant={"secondary"}>
|
<Button type="submit" disabled variant={"secondary"}>
|
||||||
确认修改
|
{t('setting.submit')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,15 +9,18 @@ import {
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { NotifyProvider } from "@/providers/notify";
|
import { NotifyProvider } from "@/providers/notify";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const Notify = () => {
|
const Notify = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotifyProvider>
|
<NotifyProvider>
|
||||||
<div className="border rounded-sm p-5 shadow-lg">
|
<div className="border rounded-sm p-5 shadow-lg">
|
||||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>模板</AccordionTrigger>
|
<AccordionTrigger>{t('template')}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<NotifyTemplate />
|
<NotifyTemplate />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
|
|
|
@ -14,29 +14,31 @@ import { getPb } from "@/repository/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
oldPassword: z.string().min(10, {
|
oldPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
newPassword: z.string().min(10, {
|
newPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
confirmPassword: z.string().min(10, {
|
confirmPassword: z.string().min(10, {
|
||||||
message: "密码至少10个字符",
|
message: "setting.password.length.message",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.refine((data) => data.newPassword === data.confirmPassword, {
|
.refine((data) => data.newPassword === data.confirmPassword, {
|
||||||
message: "两次密码不一致",
|
message: "setting.password.not.match",
|
||||||
path: ["confirmPassword"],
|
path: ["confirmPassword"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const Password = () => {
|
const Password = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
|
@ -66,8 +68,8 @@ const Password = () => {
|
||||||
|
|
||||||
getPb().authStore.clear();
|
getPb().authStore.clear();
|
||||||
toast({
|
toast({
|
||||||
title: "修改密码成功",
|
title: t('setting.password.change.succeed'),
|
||||||
description: "请重新登录",
|
description: t("setting.account.log.back.in"),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
|
@ -75,7 +77,7 @@ const Password = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrMessage(e);
|
const message = getErrMessage(e);
|
||||||
toast({
|
toast({
|
||||||
title: "修改密码失败",
|
title: t('setting.password.change.failed'),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
@ -95,9 +97,9 @@ const Password = () => {
|
||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>当前密码</FormLabel>
|
<FormLabel>{t('setting.password.current.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="当前密码" {...field} type="password" />
|
<Input placeholder={t('setting.password.current.password')} {...field} type="password" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
@ -110,7 +112,7 @@ const Password = () => {
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>新密码</FormLabel>
|
<FormLabel>{t('setting.password.new.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="newPassword"
|
placeholder="newPassword"
|
||||||
|
@ -129,7 +131,7 @@ const Password = () => {
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>确认密码</FormLabel>
|
<FormLabel>{t('setting.password.confirm.password')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="confirmPassword"
|
placeholder="confirmPassword"
|
||||||
|
@ -143,7 +145,7 @@ const Password = () => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button type="submit">确认修改</Button>
|
<Button type="submit">{t('setting.submit')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
Loading…
Reference in New Issue