Merge pull request #61 from usual2970/feat/settings

Feat/settings
This commit is contained in:
usual2970 2024-09-21 06:38:04 +08:00 committed by GitHub
commit 2cca82eb95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 591 additions and 387 deletions

BIN
certimate

Binary file not shown.

303
ui/dist/assets/index-BLKGMHXS.js vendored Normal file

File diff suppressed because one or more lines are too long

1
ui/dist/assets/index-BmYeXvQX.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title> <title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-liy7dSav.js"></script> <script type="module" crossorigin src="/assets/index-BLKGMHXS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ChWRjRip.css"> <link rel="stylesheet" crossorigin href="/assets/index-BmYeXvQX.css">
</head> </head>
<body class="bg-background"> <body class="bg-background">
<div id="root"></div> <div id="root"></div>

View File

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

View File

@ -1 +1 @@
export const version = "Certimate v0.1.8"; export const version = "Certimate v0.1.9";

View File

@ -46,12 +46,12 @@ export default function Dashboard() {
}; };
const handleSettingClick = () => { const handleSettingClick = () => {
navigate("/setting/password"); navigate("/setting/account");
}; };
return ( return (
<> <>
<ConfigProvider> <ConfigProvider>
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]"> <div className="grid min-h-screen w-full md:grid-cols-[180px_1fr] lg:grid-cols-[200px_1fr] 2xl:md:grid-cols-[280px_1fr] ">
<div className="hidden border-r dark:border-stone-500 bg-muted/40 md:block"> <div className="hidden border-r dark:border-stone-500 bg-muted/40 md:block">
<div className="flex h-full max-h-screen flex-col gap-2"> <div className="flex h-full max-h-screen flex-col gap-2">
<div className="flex h-14 items-center border-b dark:border-stone-500 px-4 lg:h-[60px] lg:px-6"> <div className="flex h-14 items-center border-b dark:border-stone-500 px-4 lg:h-[60px] lg:px-6">

View File

@ -1,21 +1,58 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Toaster } from "@/components/ui/toaster"; import { Toaster } from "@/components/ui/toaster";
import { Outlet } from "react-router-dom"; import { KeyRound, UserRound } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
const SettingLayout = () => { const SettingLayout = () => {
const location = useLocation();
const [tabValue, setTabValue] = useState("account");
const navigate = useNavigate();
useEffect(() => {
const pathname = location.pathname;
const tabValue = pathname.split("/")[2];
setTabValue(tabValue);
}, [location]);
return ( return (
<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">
</div> </div>
<div className="w-full sm:w-[35em] mt-10 flex flex-col p-3 mx-auto"> <div className="w-full mt-5 p-3 flex justify-center">
{/* <div className="text-muted-foreground"> <Tabs defaultValue="account" className="" value={tabValue}>
<span className="transition-all text-sm bg-gray-400 px-3 py-1 rounded-sm text-white cursor-pointer"> <TabsList>
<TabsTrigger
</span> value="account"
</div> */} onClick={() => {
navigate("/setting/account");
}}
className="px-5"
>
<UserRound size={14} />
<div className="ml-1"></div>
</TabsTrigger>
<TabsTrigger
value="password"
onClick={() => {
navigate("/setting/password");
}}
className="px-5"
>
<KeyRound size={14} />
<div className="ml-1"></div>
</TabsTrigger>
</TabsList>
<TabsContent value={tabValue}>
<div className="mt-5 w-full md:w-[45em]">
<Outlet /> <Outlet />
</div> </div>
</TabsContent>
</Tabs>
</div>
</div> </div>
); );
}; };

View File

@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress"; import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -17,8 +18,6 @@ import { statistics } from "@/repository/domains";
import { import {
Ban, Ban,
CalendarX2, CalendarX2,
CircleCheck,
CircleX,
LoaderPinwheel, LoaderPinwheel,
Smile, Smile,
SquareSigma, SquareSigma,
@ -204,11 +203,7 @@ const Dashboard = () => {
{deployment.expand.domain?.domain} {deployment.expand.domain?.domain}
</div> </div>
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
{deployment.phase === "deploy" && deployment.phaseSuccess ? ( <DeployState deployment={deployment} />
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
</div> </div>
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
<DeployProgress <DeployProgress

View File

@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress"; import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import XPagination from "@/components/certimate/XPagination"; import XPagination from "@/components/certimate/XPagination";
import Show from "@/components/Show"; import Show from "@/components/Show";
import { import {
@ -31,7 +32,7 @@ import {
} from "@/repository/domains"; } from "@/repository/domains";
import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip"; import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
import { CircleCheck, CircleX, 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";
@ -196,12 +197,12 @@ const Home = () => {
) : ( ) : (
<> <>
<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-40"></div> <div className="w-36"></div>
<div className="w-48"></div> <div className="w-40"></div>
<div className="w-32"></div> <div className="w-32"></div>
<div className="w-64"></div> <div className="w-64"></div>
<div className="w-40 sm:ml-2"></div> <div className="w-40 sm:ml-2"></div>
<div className="w-32"></div> <div className="w-24"></div>
<div className="grow"></div> <div className="grow"></div>
</div> </div>
<div className="sm:hidden flex text-sm text-muted-foreground"> <div className="sm:hidden flex text-sm text-muted-foreground">
@ -213,10 +214,10 @@ 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" 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} key={domain.id}
> >
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-36 w-full pt-1 sm:pt-0 flex items-center">
{domain.domain} {domain.domain}
</div> </div>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
<div> <div>
{domain.expiredAt ? ( {domain.expiredAt ? (
<> <>
@ -231,12 +232,7 @@ const Home = () => {
<div className="sm:w-32 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-32 w-full pt-1 sm:pt-0 flex items-center">
{domain.lastDeployedAt && domain.expand?.lastDeployment ? ( {domain.lastDeployedAt && domain.expand?.lastDeployment ? (
<> <>
{domain.expand.lastDeployment?.phase === "deploy" && <DeployState deployment={domain.expand.lastDeployment} />
domain.expand.lastDeployment?.phaseSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
</> </>
) : ( ) : (
"---" "---"
@ -257,7 +253,7 @@ const Home = () => {
? convertZulu2Beijing(domain.lastDeployedAt) ? convertZulu2Beijing(domain.lastDeployedAt)
: "---"} : "---"}
</div> </div>
<div className="sm:w-32 flex items-center"> <div className="sm:w-24 flex items-center">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>

View File

@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress"; import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
@ -13,7 +14,7 @@ import {
import { Deployment, DeploymentListReq, Log } from "@/domain/deployment"; import { Deployment, DeploymentListReq, Log } from "@/domain/deployment";
import { convertZulu2Beijing } from "@/lib/time"; import { convertZulu2Beijing } from "@/lib/time";
import { list } from "@/repository/deployment"; import { list } from "@/repository/deployment";
import { CircleCheck, CircleX, 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";
@ -88,11 +89,7 @@ const History = () => {
{deployment.expand.domain?.domain} {deployment.expand.domain?.domain}
</div> </div>
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
{deployment.phase === "deploy" && deployment.phaseSuccess ? ( <DeployState deployment={deployment} />
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
</div> </div>
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
<DeployProgress <DeployProgress

View File

@ -0,0 +1,109 @@
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getPb } from "@/repository/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { z } from "zod";
const formSchema = z.object({
email: z.string().email("请输入正确的邮箱"),
});
const Account = () => {
const { toast } = useToast();
const navigate = useNavigate();
const [changed, setChanged] = useState(false);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: getPb().authStore.model?.email,
},
});
const onSubmit = async (values: z.infer<typeof formSchema>) => {
try {
await getPb().admins.update(getPb().authStore.model?.id, {
email: values.email,
});
getPb().authStore.clear();
toast({
title: "修改账户邮箱功",
description: "请重新登录",
});
setTimeout(() => {
navigate("/login");
}, 500);
} catch (e) {
const message = getErrMessage(e);
toast({
title: "修改账户邮箱失败",
description: message,
variant: "destructive",
});
}
};
return (
<>
<div className="w-full md:max-w-[35em]">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="请输入邮箱"
{...field}
type="email"
onChange={(e) => {
setChanged(true);
form.setValue("email", e.target.value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
{changed ? (
<Button type="submit"></Button>
) : (
<Button type="submit" disabled variant={"secondary"}>
</Button>
)}
</div>
</form>
</Form>
</div>
</>
);
};
export default Account;

View File

@ -84,6 +84,7 @@ const Password = () => {
return ( return (
<> <>
<div className="w-full md:max-w-[35em]">
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
@ -111,7 +112,11 @@ const Password = () => {
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel></FormLabel>
<FormControl> <FormControl>
<Input placeholder="newPassword" {...field} type="password" /> <Input
placeholder="newPassword"
{...field}
type="password"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -142,6 +147,7 @@ const Password = () => {
</div> </div>
</form> </form>
</Form> </Form>
</div>
</> </>
); );
}; };

View File

@ -10,6 +10,7 @@ import LoginLayout from "./pages/LoginLayout";
import Password from "./pages/setting/Password"; import Password from "./pages/setting/Password";
import SettingLayout from "./pages/SettingLayout"; import SettingLayout from "./pages/SettingLayout";
import Dashboard from "./pages/dashboard/Dashboard"; import Dashboard from "./pages/dashboard/Dashboard";
import Account from "./pages/setting/Account";
export const router = createHashRouter([ export const router = createHashRouter([
{ {
@ -44,6 +45,10 @@ export const router = createHashRouter([
path: "/setting/password", path: "/setting/password",
element: <Password />, element: <Password />,
}, },
{
path: "/setting/account",
element: <Account />,
},
], ],
}, },
], ],