Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cca82eb95 | ||
|
|
30beee6027 | ||
|
|
b649348162 | ||
|
|
b912c5e688 | ||
|
|
5981200df2 | ||
|
|
f9e7bfd606 | ||
|
|
7f6549bdf3 | ||
|
|
2af26dbfe0 | ||
|
|
b7f382e16f | ||
|
|
7762955989 | ||
|
|
1ab603b506 | ||
|
|
b432cbfd3f | ||
|
|
e4d76113f8 |
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 加入频道讨论
|
||||
url: https://t.me/+ZXphsppxUg41YmVl
|
||||
about: 加入到电报频道寻求更多帮助
|
||||
31
.github/workflows/push_image.yml
vendored
31
.github/workflows/push_image.yml
vendored
@@ -4,6 +4,12 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag version to be used for Docker image"
|
||||
required: true
|
||||
default: "v0.1.9"
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
@@ -19,7 +25,22 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
usual2970/certimate
|
||||
registry.cn-shanghai.aliyuncs.com/usual2970/certimate
|
||||
|
||||
- name: Log in to DOCKERHUB
|
||||
uses: docker/login-action@v3
|
||||
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Log in to ALIYUNCS
|
||||
uses: docker/login-action@v3
|
||||
|
||||
with:
|
||||
@@ -27,10 +48,6 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract Git tag
|
||||
id: get_tag
|
||||
run: echo "tag=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -38,6 +55,4 @@ jobs:
|
||||
file: ./Dockerfile_build
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
registry.cn-shanghai.aliyuncs.com/usual2970/certimate:${{ env.tag }}
|
||||
registry.cn-shanghai.aliyuncs.com/usual2970/certimate:latest
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
@@ -35,6 +35,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
|
||||
- [3. 部署服务商授权信息](#3-部署服务商授权信息)
|
||||
- [六、常见问题](#六常见问题)
|
||||
- [七、贡献](#七贡献)
|
||||
- [八、加入社区](#八加入社区)
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +115,7 @@ go run main.go serve
|
||||
Certimate 的工作流程如下:
|
||||
|
||||
* 用户通过 Certimate 管理页面填写申请证书的信息,包括域名、dns 服务商的授权信息、以及要部署到的服务商的授权信息。
|
||||
* Certimate 向证书场商的 API 发起申请请求,获取 SSL 证书。
|
||||
* Certimate 向证书厂商的 API 发起申请请求,获取 SSL 证书。
|
||||
* Certimate 存储证书信息,包括证书内容、私钥、证书有效期等,并在证书即将过期时自动续期。
|
||||
* Certimate 向服务商的 API 发起部署请求,将证书部署到服务商的服务器上。
|
||||
|
||||
@@ -178,4 +179,10 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
|
||||
|
||||
支持更多服务商、UI 的优化改进、BUG 修复、文档完善等,欢迎大家提交 PR。
|
||||
|
||||
## 八、加入社区
|
||||
|
||||
* [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
|
||||
|
||||
* 微信群聊
|
||||
|
||||
<img src="https://i.imgur.com/lJUfTeD.png" width="400"/>
|
||||
|
||||
303
ui/dist/assets/index-BLKGMHXS.js
vendored
Normal file
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
1
ui/dist/assets/index-BmYeXvQX.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-ChWRjRip.css
vendored
1
ui/dist/assets/index-ChWRjRip.css
vendored
File diff suppressed because one or more lines are too long
293
ui/dist/assets/index-liy7dSav.js
vendored
293
ui/dist/assets/index-liy7dSav.js
vendored
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||
<script type="module" crossorigin src="/assets/index-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>
|
||||
|
||||
49
ui/src/components/certimate/DeployState.tsx
Normal file
49
ui/src/components/certimate/DeployState.tsx
Normal 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;
|
||||
@@ -1 +1 @@
|
||||
export const version = "Certimate v0.1.8";
|
||||
export const version = "Certimate v0.1.9";
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
109
ui/src/pages/setting/Account.tsx
Normal file
109
ui/src/pages/setting/Account.tsx
Normal 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;
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user