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" />
<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-liy7dSav.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ChWRjRip.css">
<script type="module" crossorigin src="/assets/index-BLKGMHXS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BmYeXvQX.css">
</head>
<body class="bg-background">
<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 = () => {
navigate("/setting/password");
navigate("/setting/account");
};
return (
<>
<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="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">

View File

@ -1,20 +1,57 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
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 location = useLocation();
const [tabValue, setTabValue] = useState("account");
const navigate = useNavigate();
useEffect(() => {
const pathname = location.pathname;
const tabValue = pathname.split("/")[2];
setTabValue(tabValue);
}, [location]);
return (
<div>
<Toaster />
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
</div>
<div className="w-full sm:w-[35em] mt-10 flex flex-col p-3 mx-auto">
{/* <div className="text-muted-foreground">
<span className="transition-all text-sm bg-gray-400 px-3 py-1 rounded-sm text-white cursor-pointer">
</span>
</div> */}
<Outlet />
<div className="w-full mt-5 p-3 flex justify-center">
<Tabs defaultValue="account" className="" value={tabValue}>
<TabsList>
<TabsTrigger
value="account"
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 />
</div>
</TabsContent>
</Tabs>
</div>
</div>
);

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
@ -13,7 +14,7 @@ import {
import { Deployment, DeploymentListReq, Log } from "@/domain/deployment";
import { convertZulu2Beijing } from "@/lib/time";
import { list } from "@/repository/deployment";
import { CircleCheck, CircleX, Smile } from "lucide-react";
import { Smile } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
@ -88,11 +89,7 @@ const History = () => {
{deployment.expand.domain?.domain}
</div>
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
<DeployState deployment={deployment} />
</div>
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
<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,64 +84,70 @@ const Password = () => {
return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
<FormField
control={form.control}
name="oldPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="当前密码" {...field} type="password" />
</FormControl>
<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="oldPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="当前密码" {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="newPassword" {...field} type="password" />
</FormControl>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="newPassword"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="confirmPassword"
{...field}
type="password"
/>
</FormControl>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="confirmPassword"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>
</form>
</Form>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>
</form>
</Form>
</div>
</>
);
};

View File

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