|
@ -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"; |
|
|
|
|
|
|
|
@ -40,7 +40,7 @@ const BrandSelect = (props: any) => { |
|
|
vertical={true} |
|
|
vertical={true} |
|
|
onClick={() => onChange("Google")} |
|
|
onClick={() => onChange("Google")} |
|
|
className={cx("band-normal", { |
|
|
className={cx("band-normal", { |
|
|
"band-active": value === "Google", |
|
|
|
|
|
|
|
|
"band-active": value === "Google" || !props.value, |
|
|
})} |
|
|
})} |
|
|
> |
|
|
> |
|
|
<img src={google} style={{ height: "2rem" }} /> |
|
|
<img src={google} style={{ height: "2rem" }} /> |
|
@ -85,6 +85,14 @@ 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 [ |
|
|
{ |
|
|
{ |
|
@ -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); |
|
@ -238,6 +257,44 @@ const StatusTable = (props: { value: string }) => { |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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> |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const domainsAtom = atomWithStorage<string>("domains", ""); |
|
|
const domainsAtom = atomWithStorage<string>("domains", ""); |
|
|
|
|
|
|
|
|
const Apply = () => { |
|
|
const Apply = () => { |
|
@ -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(() => { |
|
@ -261,6 +320,23 @@ const Apply = () => { |
|
|
className={styles.applyContainer} |
|
|
className={styles.applyContainer} |
|
|
title={t(`${i18nPrefix}.apply.title`, "证书申请")} |
|
|
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={form} |
|
|
form={form} |
|
|
{...{ |
|
|
{...{ |
|
@ -296,23 +372,22 @@ const Apply = () => { |
|
|
label={t(`${i18nPrefix}.columns.domains`, "域名")} |
|
|
label={t(`${i18nPrefix}.columns.domains`, "域名")} |
|
|
rules={[{ required: true, message: t(`${i18nPrefix}.columns.domains.required`, "请输入域名") }]} |
|
|
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); |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
<DomainsInput |
|
|
|
|
|
currentDomainMod={currentDomainMod} |
|
|
|
|
|
setCurrentDomainMod={setCurrentDomainMod} |
|
|
|
|
|
setDomains={setDomains} |
|
|
|
|
|
currentStep={currentStep} |
|
|
/> |
|
|
/> |
|
|
</Form.Item> |
|
|
</Form.Item> |
|
|
|
|
|
{currentStep !== 0 && ( |
|
|
|
|
|
<> |
|
|
<Form.Item |
|
|
<Form.Item |
|
|
label={t(`${i18nPrefix}.columns.type`, "域名验证")} |
|
|
label={t(`${i18nPrefix}.columns.type`, "域名验证")} |
|
|
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]} |
|
|
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]} |
|
|
> |
|
|
> |
|
|
<StatusTable value={domains} /> |
|
|
<StatusTable value={domains} /> |
|
|
</Form.Item> |
|
|
</Form.Item> |
|
|
|
|
|
|
|
|
<Form.Item |
|
|
<Form.Item |
|
|
name={"brand"} |
|
|
name={"brand"} |
|
|
label={t(`${i18nPrefix}.columns.brand`, "证书品牌")} |
|
|
label={t(`${i18nPrefix}.columns.brand`, "证书品牌")} |
|
@ -323,7 +398,7 @@ const Apply = () => { |
|
|
}, |
|
|
}, |
|
|
]} |
|
|
]} |
|
|
> |
|
|
> |
|
|
<BrandSelect /> |
|
|
|
|
|
|
|
|
<BrandSelect value="Google" /> |
|
|
</Form.Item> |
|
|
</Form.Item> |
|
|
<Form.Item |
|
|
<Form.Item |
|
|
name={"algorithm"} |
|
|
name={"algorithm"} |
|
@ -340,14 +415,27 @@ const Apply = () => { |
|
|
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}> |
|
|
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}> |
|
|
<Input style={{ width: 400 }} /> |
|
|
<Input style={{ width: 400 }} /> |
|
|
</Form.Item> |
|
|
</Form.Item> |
|
|
|
|
|
</> |
|
|
|
|
|
)} |
|
|
<Form.Item label={" "} colon={false}> |
|
|
<Form.Item label={" "} colon={false}> |
|
|
<Space> |
|
|
<Space> |
|
|
<Button disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}> |
|
|
|
|
|
|
|
|
{currentStep !== 0 && ( |
|
|
|
|
|
<Button type="primary" onClick={() => setCurrentStep(currentStep - 1)} htmlType={"submit"}> |
|
|
{t(`${i18nPrefix}.apply.prev`, "上一步")} |
|
|
{t(`${i18nPrefix}.apply.prev`, "上一步")} |
|
|
</Button> |
|
|
</Button> |
|
|
<Button type={"primary"} disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}> |
|
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{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`, "提交申请")} |
|
|
{t(`${i18nPrefix}.apply.submit`, "提交申请")} |
|
|
</Button> |
|
|
</Button> |
|
|
|
|
|
)} |
|
|
</Space> |
|
|
</Space> |
|
|
</Form.Item> |
|
|
</Form.Item> |
|
|
</Form> |
|
|
</Form> |
|
|