Browse Source

证书相关UI完成

main
lk 3 months ago
parent
commit
41a1f17d6a
  1. 422
      src/pages/websites/cert/apply.tsx

422
src/pages/websites/cert/apply.tsx

@ -7,8 +7,8 @@ import {
dnsVerifyOKAtom, dnsVerifyOKAtom,
saveOrUpdateCertAtom, saveOrUpdateCertAtom,
} from "@/store/websites/cert.ts"; } from "@/store/websites/cert.ts";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button, Flex, Form, Input, Progress, Select, Space, Table, Tooltip } from "antd";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {Button, Flex, Form, Input, message, Progress, Radio, Select, Space, Steps, Table, Tooltip} from "antd";
import google from "@/pages/websites/cert/assets/google.png"; import google from "@/pages/websites/cert/assets/google.png";
import zerossl from "@/pages/websites/cert/assets/zerossl.png"; import zerossl from "@/pages/websites/cert/assets/zerossl.png";
import lets_encrypt from "@/pages/websites/cert/assets/lets_encrypt.png"; import lets_encrypt from "@/pages/websites/cert/assets/lets_encrypt.png";
@ -17,7 +17,7 @@ import ListPageLayout from "@/layout/ListPageLayout.tsx";
import { ColumnsType } from "antd/es/table"; import { ColumnsType } from "antd/es/table";
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
import Copy from "@/components/copy"; import Copy from "@/components/copy";
import { InfoCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import { CopyOutlined, InfoCircleOutlined, LoadingOutlined } from "@ant-design/icons";
const i18nPrefix = "cert.apply"; const i18nPrefix = "cert.apply";
@ -34,45 +34,45 @@ const BrandSelect = (props: any) => {
}, []); }, []);
return ( return (
<>
<Space className={styles.bandSelect}>
<Flex
vertical={true}
onClick={() => onChange("Google")}
className={cx("band-normal", {
"band-active": value === "Google",
})}
>
<img src={google} style={{ height: "2rem" }} />
<span>Google Trust Services</span>
</Flex>
<Flex
vertical={true}
onClick={() => onChange("ZeroSSL")}
className={cx("band-normal", {
"band-active": value === "ZeroSSL",
})}
>
<img src={zerossl} style={{ height: "2rem" }} />
<span>ZeroSSL</span>
</Flex>
<Flex
vertical={true}
onClick={() => onChange("Let's Encrypt")}
className={cx("band-normal", {
"band-active": value === "Let's Encrypt",
})}
>
<img src={lets_encrypt} style={{ height: "2rem" }} />
<span>Let's Encrypt</span>
</Flex>
</Space>
</>
<>
<Space className={styles.bandSelect}>
<Flex
vertical={true}
onClick={() => onChange("Google")}
className={cx("band-normal", {
"band-active": value === "Google" || !props.value,
})}
>
<img src={google} style={{ height: "2rem" }} />
<span>Google Trust Services</span>
</Flex>
<Flex
vertical={true}
onClick={() => onChange("ZeroSSL")}
className={cx("band-normal", {
"band-active": value === "ZeroSSL",
})}
>
<img src={zerossl} style={{ height: "2rem" }} />
<span>ZeroSSL</span>
</Flex>
<Flex
vertical={true}
onClick={() => onChange("Let's Encrypt")}
className={cx("band-normal", {
"band-active": value === "Let's Encrypt",
})}
>
<img src={lets_encrypt} style={{ height: "2rem" }} />
<span>Let's Encrypt</span>
</Flex>
</Space>
</>
); );
}; };
const StatusTable = (props: { value: string }) => { const StatusTable = (props: { value: string }) => {
const [data, setData] = useState<any>(null); // 临时状态来存储模拟数据
const [data, setData] = useState<any>(null); // 临时状态来存储模拟数据
const { isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [props.value])); const { isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [props.value]));
const { const {
@ -85,16 +85,24 @@ const StatusTable = (props: { value: string }) => {
const timerRef = useRef<number>(); const timerRef = useRef<number>();
const handleCopy = (str:string) => {
navigator.clipboard.writeText(str).then(() => {
message.info('复制成功!');
}).catch(err => {
message.info(err);
});
};
const columns = useMemo<ColumnsType>(() => { const columns = useMemo<ColumnsType>(() => {
return [ return [
{ {
title: ( title: (
<>
{t(`${i18nPrefix}.status.columns.status`, "状态")}
<Tooltip title={t(`${i18nPrefix}.status.columns.statusTip`, "正确配置DNS解析后,域名验证会自动通过")}>
<InfoCircleOutlined style={{ paddingInlineStart: 5 }} />
</Tooltip>{" "}
</>
<>
{t(`${i18nPrefix}.status.columns.status`, "状态")}
<Tooltip title={t(`${i18nPrefix}.status.columns.statusTip`, "正确配置DNS解析后,域名验证会自动通过")}>
<InfoCircleOutlined style={{ paddingInlineStart: 5 }} />
</Tooltip>{" "}
</>
), ),
dataIndex: "status", dataIndex: "status",
width: 100, width: 100,
@ -106,9 +114,9 @@ const StatusTable = (props: { value: string }) => {
if (isVerifyFetching) { if (isVerifyFetching) {
//0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功 //0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功
return ( return (
<span>
<span>
<LoadingOutlined style={{ paddingInlineEnd: 5 }} /> <LoadingOutlined style={{ paddingInlineEnd: 5 }} />
{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, "检测中")}
{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, "检测中")}
</span> </span>
); );
} }
@ -127,9 +135,9 @@ const StatusTable = (props: { value: string }) => {
width: 150, width: 150,
render(text) { render(text) {
if (text) { if (text) {
return <span className={"color-green1"}>{text}</span>;
return <span className={"color-yellow"}>{text}</span>;
} }
return <span className={"color-yellow"}></span>;
return <span className={"color-red"}></span>;
}, },
}, },
{ {
@ -137,6 +145,13 @@ const StatusTable = (props: { value: string }) => {
title: t(`${i18nPrefix}.status.columns.domain`, "域名"), title: t(`${i18nPrefix}.status.columns.domain`, "域名"),
dataIndex: "dns_name", dataIndex: "dns_name",
width: 200, width: 200,
render(text) {
return (
<Button size="small" type="text" className={"color-green1"} icon={<CopyOutlined />} iconPosition={"end"} onClick={() => handleCopy(text)}>
{text}
</Button>
);
},
}, },
{ {
//主机记录 //主机记录
@ -162,7 +177,11 @@ const StatusTable = (props: { value: string }) => {
dataIndex: "record_value", dataIndex: "record_value",
width: 200, width: 200,
render: (text) => { render: (text) => {
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />;
return (
<Button size="small" type="text" className={"color-green1"} icon={<CopyOutlined />} iconPosition={"end"} onClick={() => handleCopy(text)}>
{t(`actions.clickCopy`)}
</Button>
);
}, },
}, },
] as ColumnsType; ] as ColumnsType;
@ -178,7 +197,7 @@ const StatusTable = (props: { value: string }) => {
dns_name: "example.com", dns_name: "example.com",
host: "example", host: "example",
type: "A", type: "A",
record_value: "192.168.1.1"
record_value: "192.168.1.1",
}, },
{ {
status: 3, status: 3,
@ -186,9 +205,9 @@ const StatusTable = (props: { value: string }) => {
dns_name: "another.com", dns_name: "another.com",
host: "another", host: "another",
type: "CNAME", type: "CNAME",
record_value: "cname.another.com"
}
]
record_value: "cname.another.com",
},
],
}; };
setData(mockData); setData(mockData);
@ -217,24 +236,62 @@ const StatusTable = (props: { value: string }) => {
}, [dnsVerifyStatus, isVerifyFetching]); }, [dnsVerifyStatus, isVerifyFetching]);
return ( return (
<>
<div style={{ paddingBlock: 5, color: "#5a5a5a" }}>
<Flex vertical={false} align={"center"}>
<div>DNS解析记录</div>
<Button type="link"></Button>
</Flex>
<div> 1. </div>
<div> 2. 1~2</div>
</div>
<Table
columns={columns}
dataSource={(data as any)?.dns_list}
loading={isFetching}
size={"small"}
pagination={false}
bordered={true}
/>
</>
<>
<div style={{ paddingBlock: 5, color: "#5a5a5a" }}>
<Flex vertical={false} align={"center"}>
<div>DNS解析记录</div>
<Button type="link"></Button>
</Flex>
<div> 1. </div>
<div> 2. 1~2</div>
</div>
<Table
columns={columns}
dataSource={(data as any)?.dns_list}
loading={isFetching}
size={"small"}
pagination={false}
bordered={true}
/>
</>
);
};
const DomainsInput = (props: { setDomains; currentDomainMod; setCurrentDomainMod; currentStep }) => {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Radio.Group
style={{ marginBottom: 8 }}
value={props.currentDomainMod}
onChange={(e) => props.setCurrentDomainMod(e.target.value)}
>
<Radio.Button value="single" disabled={props.currentStep !== 0}>
</Radio.Button>
<Radio.Button value="multiple" disabled={props.currentStep !== 0}>
</Radio.Button>
</Radio.Group>
{props.currentDomainMod === "single" && (
<Input
disabled={props.currentStep !== 0}
placeholder="请输入域名,支持泛解析域名。如果为多个域名注册到一个证书,域名之间用<逗号(英文输入)>分割。如:a.com,*.b.com"
/>
)}
{props.currentDomainMod === "multiple" && (
<Input.TextArea
disabled={props.currentStep !== 0}
rows={6}
placeholder={`多个域名多个证书申请,用回车分隔。以下示例为6个域名申请3个证书,每行对应一个证书,支持泛解析域名;如:
*.a.baidu.com, *.baidu.com
hello.alibaba.com,b.com,*.c.com
sss.ddd.com`}
onBlur={(e) => {
props.setDomains(e.target.value);
}}
/>
)}
</Space>
); );
}; };
@ -245,6 +302,8 @@ const Apply = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom); const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom);
const [domains, setDomains] = useAtom(domainsAtom); const [domains, setDomains] = useAtom(domainsAtom);
const [currentStep, setCurrentStep] = useState(0);
const [currentDomainMod, setCurrentDomainMod] = useState<"single" | "multiple">("single");
const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom); const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom);
useEffect(() => { useEffect(() => {
@ -256,103 +315,132 @@ const Apply = () => {
}, [domains]); }, [domains]);
return ( return (
<ListPageLayout
childrenClassName={styles.applyContent}
className={styles.applyContainer}
title={t(`${i18nPrefix}.apply.title`, "证书申请")}
>
<Form
form={form}
{...{
labelCol: { span: 3 },
wrapperCol: { span: 16 },
}}
onValuesChange={(values) => {
// console.log('onValuesChange', values)
if (values.domains) {
// setDomains(values.domains)
}
}}
onFinish={async (values) => {
if (dnsVerifyOK) {
saveOrUpdate(values);
}
}}
<ListPageLayout
childrenClassName={styles.applyContent}
className={styles.applyContainer}
title={t(`${i18nPrefix}.apply.title`, "证书申请")}
>
<div style={{ padding: "50px 50px 0 50px" }}>
<Steps
size="small"
current={currentStep}
items={[
{
title: "填写域名",
},
{
title: "等待检测",
},
{
title: "提交申请",
},
]}
/>
</div>
<Form
form={form}
{...{
labelCol: { span: 3 },
wrapperCol: { span: 16 },
}}
onValuesChange={(values) => {
// console.log('onValuesChange', values)
if (values.domains) {
// setDomains(values.domains)
}
}}
onFinish={async (values) => {
if (dnsVerifyOK) {
saveOrUpdate(values);
}
}}
>
{/* <Form.Item*/}
{/* name={"domains"}*/}
{/* label={t(`${i18nPrefix}.columns.quota`, "证书额度")}*/}
{/* >*/}
{/* <Flex vertical gap="small" style={{ width: 300 }}>*/}
{/*<span>*/}
{/* <span style={{ color: "#00762B" }}>{t(`${i18nPrefix}.apply.remaining`, "剩余5张")}</span>*/}
{/* <span style={{ color: "#000000" }}>/ {t(`${i18nPrefix}.apply.total`, "共5张")}</span>*/}
{/*</span>*/}
{/* <Progress percent={30} strokeColor="#00762B" />*/}
{/* </Flex>*/}
{/* </Form.Item>*/}
<Form.Item
name={"domains"}
label={t(`${i18nPrefix}.columns.domains`, "域名")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.domains.required`, "请输入域名") }]}
>
<DomainsInput
currentDomainMod={currentDomainMod}
setCurrentDomainMod={setCurrentDomainMod}
setDomains={setDomains}
currentStep={currentStep}
/>
</Form.Item>
{currentStep !== 0 && (
<>
<Form.Item
label={t(`${i18nPrefix}.columns.type`, "域名验证")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]}
> >
{/* <Form.Item*/}
{/* name={"domains"}*/}
{/* label={t(`${i18nPrefix}.columns.quota`, "证书额度")}*/}
{/* >*/}
{/* <Flex vertical gap="small" style={{ width: 300 }}>*/}
{/*<span>*/}
{/* <span style={{ color: "#00762B" }}>{t(`${i18nPrefix}.apply.remaining`, "剩余5张")}</span>*/}
{/* <span style={{ color: "#000000" }}>/ {t(`${i18nPrefix}.apply.total`, "共5张")}</span>*/}
{/*</span>*/}
{/* <Progress percent={30} strokeColor="#00762B" />*/}
{/* </Flex>*/}
{/* </Form.Item>*/}
<Form.Item
name={"domains"}
label={t(`${i18nPrefix}.columns.domains`, "域名")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.domains.required`, "请输入域名") }]}
>
<Input.TextArea
rows={5}
placeholder={`请输入域名,每行一个,支持泛解析域名;如:
*.google.com
*.a.baidu.com
hello.alibaba.com`}
onBlur={(e) => {
setDomains(e.target.value);
}}
/>
</Form.Item>
<Form.Item
label={t(`${i18nPrefix}.columns.type`, "域名验证")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]}
>
<StatusTable value={domains} />
</Form.Item>
<Form.Item
name={"brand"}
label={t(`${i18nPrefix}.columns.brand`, "证书品牌")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.brand.required`, "请选择证书品牌"),
},
]}
>
<BrandSelect />
</Form.Item>
<Form.Item
name={"algorithm"}
label={t(`${i18nPrefix}.columns.algorithm`, "加密方式")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.algorithm.required`, "请选择加密方式"),
},
]}
>
<Select style={{ width: 120 }} options={algorithmTypes} />
</Form.Item>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}>
<Input style={{ width: 400 }} />
</Form.Item>
<Form.Item label={" "} colon={false}>
<Space>
<Button disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.prev`, "上一步")}
</Button>
<Button type={"primary"} disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.submit`, "提交申请")}
</Button>
</Space>
</Form.Item>
</Form>
</ListPageLayout>
<StatusTable value={domains} />
</Form.Item>
<Form.Item
name={"brand"}
label={t(`${i18nPrefix}.columns.brand`, "证书品牌")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.brand.required`, "请选择证书品牌"),
},
]}
>
<BrandSelect value="Google" />
</Form.Item>
<Form.Item
name={"algorithm"}
label={t(`${i18nPrefix}.columns.algorithm`, "加密方式")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.algorithm.required`, "请选择加密方式"),
},
]}
>
<Select style={{ width: 120 }} options={algorithmTypes} />
</Form.Item>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}>
<Input style={{ width: 400 }} />
</Form.Item>
</>
)}
<Form.Item label={" "} colon={false}>
<Space>
{currentStep !== 0 && (
<Button type="primary" onClick={() => setCurrentStep(currentStep - 1)} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.prev`, "上一步")}
</Button>
)}
{currentStep === 0 && (
<Button type="primary" onClick={() => setCurrentStep(currentStep + 1)} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.next`, "下一步")}
</Button>
)}
{currentStep !== 0 && (
<Button type="primary" htmlType={"submit"} disabled={currentStep === 1}>
{t(`${i18nPrefix}.apply.submit`, "提交申请")}
</Button>
)}
</Space>
</Form.Item>
</Form>
</ListPageLayout>
); );
}; };
export default Apply;
export default Apply;
Loading…
Cancel
Save