Browse Source

完整证书申请页面

main
dark 4 months ago
parent
commit
e60020c9d8
  1. 4
      src/App.css
  2. 53
      src/components/copy/index.tsx
  3. 2
      src/locales/lang/en-US.ts
  4. 2
      src/locales/lang/zh-CN.ts
  5. 141
      src/pages/websites/cert/apply.tsx
  6. 57
      src/service/websites.ts
  7. 44
      src/store/websites/cert.ts

4
src/App.css

@ -50,6 +50,10 @@
color: green; color: green;
} }
.color-green1{
color: rgb(18 185 128 / 1 );
}
.color-red { .color-red {
color: #F56C6C; color: #F56C6C;
} }

53
src/components/copy/index.tsx

@ -0,0 +1,53 @@
import { Tooltip } from 'antd'
import { CopyConfig } from 'antd/es/typography/Base'
import { useCopyToClipboard, } from 'react-use'
import { useState } from 'react'
import { t } from 'i18next'
import { CheckOutlined } from '@ant-design/icons'
export interface ICopyProps extends CopyConfig {
}
const Copy = ({ tooltips = true, text, icon = <CheckOutlined/>, onCopy }: ICopyProps) => {
const [ , copyToClipboard ] = useCopyToClipboard()
const [ showIcon, setShow ] = useState(false)
const getText = (): any => {
if (typeof text === 'function') {
return text()
}
return text
}
const render = () => {
if (tooltips) {
return <Tooltip title={tooltips ?? t('actions.copy', '复制')}>
{getText()}
</Tooltip>
}
return getText()
}
return (
<>
<a style={{ display: 'inline-flex' }}
onClick={() => {
onCopy?.()
setShow(true)
copyToClipboard(getText())
setTimeout(() => {
setShow(false)
}, 2000)
}}>
{render()}
<span style={{ color: '#52c41a', paddingInlineStart: 5, width: 18, }}>
{showIcon && icon}
</span>
</a>
</>
)
}
export default Copy

2
src/locales/lang/en-US.ts

@ -63,6 +63,8 @@ export default {
reset: 'Reset', reset: 'Reset',
clear: 'Clear', clear: 'Clear',
close: 'Close', close: 'Close',
copy: 'copy',
clickCopy: 'click copy',
}, },
message: { message: {
infoTitle: 'Hint', infoTitle: 'Hint',

2
src/locales/lang/zh-CN.ts

@ -75,6 +75,8 @@ export default {
reset: '重置', reset: '重置',
clear: '清空', clear: '清空',
close: '关闭', close: '关闭',
copy: '复制',
clickCopy: '点击复制',
}, },
message: { message: {
infoTitle: '提示', infoTitle: '提示',

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

@ -1,7 +1,12 @@
import { t } from '@/i18n.ts' import { t } from '@/i18n.ts'
import { useAtomValue } from 'jotai'
import { algorithmTypes, bandTypes, dnsConfigAtom, saveOrUpdateCertAtom, StatusText } from '@/store/websites/cert.ts'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import {
algorithmTypes,
dnsConfigAtom,
dnsVerifyAtom, dnsVerifyOKAtom,
saveOrUpdateCertAtom,
} from '@/store/websites/cert.ts'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { import {
Button, Button,
Flex, Flex,
@ -9,7 +14,7 @@ import {
Input, Input,
Select, Select,
Space, Space,
Table, Typography,
Table, Tooltip,
} from 'antd' } 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'
@ -17,6 +22,9 @@ import lets_encrypt from '@/pages/websites/cert/assets/lets_encrypt.png'
import { useStyle } from './style' import { useStyle } from './style'
import ListPageLayout from '@/layout/ListPageLayout.tsx' import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { ColumnsType } from 'antd/es/table' import { ColumnsType } from 'antd/es/table'
import { atomWithStorage } from 'jotai/utils'
import Copy from '@/components/copy'
import { InfoCircleOutlined, LoadingOutlined } from '@ant-design/icons'
const i18nPrefix = 'cert.apply' const i18nPrefix = 'cert.apply'
@ -68,46 +76,117 @@ const StatusTable = (props: { value: string }) => {
const { data, isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [ props.value ])) const { data, isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [ props.value ]))
const {
data: dnsVerifyStatus,
isFetching: isVerifyFetching,
refetch
} = useAtomValue(useMemo(() => dnsVerifyAtom(props.value, isFetching), [ props.value, isFetching ]))
const setDnsVerifyOK = useSetAtom(dnsVerifyOKAtom)
const timerRef = useRef<number>()
const columns = useMemo<ColumnsType>(() => { const columns = useMemo<ColumnsType>(() => {
return [ return [
{ {
title: t(`${i18nPrefix}.status.columns.status`, '状态'),
tooltip: t(`${i18nPrefix}.status.columns.statusTip`, '正确配置DNS解析后,域名验证会自动通过'),
title: <>{t(`${i18nPrefix}.status.columns.status`, '状态')}<Tooltip
title={t(`${i18nPrefix}.status.columns.statusTip`, '正确配置DNS解析后,域名验证会自动通过')}><InfoCircleOutlined
style={{ paddingInlineStart: 5 }}/></Tooltip> </>,
dataIndex: 'status', dataIndex: 'status',
width: 100,
render: (_, record) => {
if (isFetching) {
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, '等待')}</span>
}
if (isVerifyFetching) {
//0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功
return <span><LoadingOutlined
style={{ paddingInlineEnd: 5 }}/>{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, '检测中')}</span>
}
const dns = record.dns_name
const info = (dnsVerifyStatus as any)?.find((item) => item.dns_name === dns) as any
if (info) {
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.${info.status}`, `${info?.status_txt}`)}</span>
}
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, '等待')}</span>
},
}, },
{ {
//服务商 //服务商
title: t(`${i18nPrefix}.status.columns.name_servers`, '服务商'), title: t(`${i18nPrefix}.status.columns.name_servers`, '服务商'),
dataIndex: 'name_servers', dataIndex: 'name_servers',
width: 150,
render(text) {
if (text) {
return <span className={'color-green1'}>{text}</span>
}
return <span className={'color-yellow'}></span>
}
}, },
{ {
//域名 //域名
title: t(`${i18nPrefix}.status.columns.domain`, '域名'), title: t(`${i18nPrefix}.status.columns.domain`, '域名'),
dataIndex: 'domain',
dataIndex: 'dns_name',
width: 200,
}, },
{ {
//主机记录 //主机记录
title: t(`${i18nPrefix}.status.columns.record`, '主机记录'), title: t(`${i18nPrefix}.status.columns.record`, '主机记录'),
dataIndex: 'record',
dataIndex: 'host',
width: 200,
render: (text) => {
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />
}
}, },
{ {
//记录类型 //记录类型
title: t(`${i18nPrefix}.status.columns.record_type`, '记录类型'), title: t(`${i18nPrefix}.status.columns.record_type`, '记录类型'),
dataIndex: 'record_type',
dataIndex: 'type',
width: 100,
render: (text) => {
return <span className={'color-red'}>{text}</span>
}
}, },
{ {
//记录值 //记录值
title: t(`${i18nPrefix}.status.columns.record_value`, '记录值'), title: t(`${i18nPrefix}.status.columns.record_value`, '记录值'),
dataIndex: 'record_value', dataIndex: 'record_value',
render:(text)=>{
return <Typography.Text copyable={{ text: text }}>{text}</Typography.Text>
width: 200,
render: (text) => {
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />
} }
} }
] as ColumnsType ] as ColumnsType
}, [])
}, [ isFetching, isVerifyFetching, dnsVerifyStatus ])
useEffect(() => {
if ((dnsVerifyStatus as any)?.every((item) => item.status === 9)) {
setDnsVerifyOK(true)
return
}
timerRef.current = window.setInterval(() => {
if (isVerifyFetching) {
return
}
//dnsVerifyStatus 如果所有status 为 9 则说明域名验证通过
if ((dnsVerifyStatus as any)?.every((item) => item.status === 9)) {
setDnsVerifyOK(true)
window.clearInterval(timerRef.current)
} else {
refetch()
}
}, 2000)
return () => {
window.clearInterval(timerRef.current)
}
}, [ dnsVerifyStatus, isVerifyFetching ])
return <> return <>
<div style={{ paddingBlock: 5, color: '#5a5a5a' }}> <div style={{ paddingBlock: 5, color: '#5a5a5a' }}>
@ -124,11 +203,24 @@ const StatusTable = (props: { value: string }) => {
</> </>
} }
const Apply = (props: any) => {
const { styles, cx } = useStyle()
const domainsAtom = atomWithStorage<string>('domains', '')
const Apply = ( ) => {
const { styles } = useStyle()
const [ form ] = Form.useForm() const [ form ] = Form.useForm()
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateCertAtom)
const [ domains, setDomains ] = useState<string>('')
const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom)
const [ domains, setDomains ] = useAtom(domainsAtom)
const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom)
useEffect(() => {
if (domains) {
form.setFieldsValue({
domains
})
}
}, [ domains ])
return ( return (
<ListPageLayout <ListPageLayout
@ -144,12 +236,13 @@ const Apply = (props: any) => {
onValuesChange={(values) => { onValuesChange={(values) => {
// console.log('onValuesChange', values) // console.log('onValuesChange', values)
if (values.domains) { if (values.domains) {
setDomains(values.domains)
// setDomains(values.domains)
} }
}} }}
onFinish={async (values) => { onFinish={async (values) => {
console.log(values)
saveOrUpdate(values)
if (dnsVerifyOK) {
saveOrUpdate(values)
}
}} }}
> >
<Form.Item <Form.Item
@ -161,7 +254,11 @@ const Apply = (props: any) => {
placeholder={`请输入域名,每行一个,支持泛解析域名;如: placeholder={`请输入域名,每行一个,支持泛解析域名;如:
*.google.com *.google.com
*.a.baidu.com *.a.baidu.com
hello.alibaba.com`}/>
hello.alibaba.com`}
onBlur={(e) => {
setDomains(e.target.value)
}}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t(`${i18nPrefix}.columns.type`, '域名验证')} label={t(`${i18nPrefix}.columns.type`, '域名验证')}
@ -194,7 +291,9 @@ const Apply = (props: any) => {
<Input/> <Input/>
</Form.Item> </Form.Item>
<Form.Item label={' '} colon={false}> <Form.Item label={' '} colon={false}>
<Button type={'primary'} htmlType={'submit'}>{t(`${i18nPrefix}.apply.submit`, '提交申请')}</Button>
<Button type={'primary'}
disabled={!dnsVerifyOK || isSubmitting}
htmlType={'submit'}>{t(`${i18nPrefix}.apply.submit`, '提交申请')}</Button>
</Form.Item> </Form.Item>
</Form> </Form>
</ListPageLayout> </ListPageLayout>

57
src/service/websites.ts

@ -8,63 +8,6 @@ import { IWebsiteDnsAccount } from '@/types/website/dns_account'
const websitesServ = { const websitesServ = {
cert: { cert: {
...createCURD<any, ICertificate>('/website/cert'), ...createCURD<any, ICertificate>('/website/cert'),
list: async (params?: any) => {
console.log(params)
return Promise.resolve({
'code': 0,
'msg': 'success',
'data': {
'total': 3,
'rows': [
{
'id': 10266,
'brand': 'ZeroSSL',
'domains': '*.aa.com,*.abc.com,aa.com,abc.com',
'type': 4,
'level': 1,
'status': 3,
'createTime': '2024-06-29 14:36:02',
'remark': '',
'algorithm': 'ECC',
'fromType': 2
},
{
'id': 10265,
'brand': 'ZeroSSL',
'domains': '*.aa.com,*.abc.com,aa.com,abc.com',
'type': 4,
'level': 1,
'status': 3,
'createTime': '2024-06-29 14:35:58',
'remark': '',
'algorithm': 'ECC',
'fromType': 2
},
{
'id': 10261,
'brand': 'Google',
'domains': '*.3456.world,3456.world',
'type': 3,
'level': 1,
'status': 1,
'createTime': '2024-06-29 14:17:46',
'notAfter': '2024-09-27 13:18:31',
'notBefore': '2024-06-29 13:18:32',
'version': 3,
'sigAlgName': 'SHA256withRSA',
'remark': '3456.world',
'name': '*.3456.world',
'lifeTime': 90,
'remainingTime': 82,
'algorithm': 'RSA',
'bit': 2048,
'fromType': 2
}
]
},
})
// return request.post<any, any>('/website/cert/list', params)
},
//dns_config //dns_config
dnsConfig: async (params: any) => { dnsConfig: async (params: any) => {
return request.post<any, any>('/website/cert/dns_config', params) return request.post<any, any>('/website/cert/dns_config', params)

44
src/store/websites/cert.ts

@ -72,7 +72,7 @@ export const saveOrUpdateCertAtom = atomWithMutation<IApiResult, ICertificate>((
mutationKey: [ 'updateCert' ], mutationKey: [ 'updateCert' ],
mutationFn: async (data) => { mutationFn: async (data) => {
//data.status = data.status ? '1' : '0' //data.status = data.status ? '1' : '0'
if ( data.id) {
if (data.id) {
return await websitesServ.cert.update(data) return await websitesServ.cert.update(data)
} }
return await websitesServ.cert.add(data) return await websitesServ.cert.add(data)
@ -107,26 +107,54 @@ export const deleteCertAtom = atomWithMutation((get) => {
}) })
//dnsConfig //dnsConfig
export const dnsConfigAtom = (domains: string[] | string) => atomWithQuery<IApiResult, any>(() => {
if (typeof domains === 'string') {
domains = domains.split('\n').filter(Boolean)
}
export const dnsConfigAtom = (domains: string) => atomWithQuery<IApiResult, any>(() => {
return { return {
enabled: domains.length > 0,
enabled: domains.length > 0 && domains.includes('.'),
queryKey: [ 'dnsConfig', domains ], queryKey: [ 'dnsConfig', domains ],
queryFn: async ({ queryKey: [ , domains ] }) => { queryFn: async ({ queryKey: [ , domains ] }) => {
if ((domains as string[]).length === 0){
if ((domains as string).length === 0) {
return Promise.reject({ return Promise.reject({
data: [] data: []
}) })
} }
return await websitesServ.cert.dnsConfig({ dns_full_list: (domains as string[]).filter(Boolean), parse: true })
return await websitesServ.cert.dnsConfig({
dns_full_list: domains,
parse: (domains as string)?.includes('*')
})
}, },
select: res => { select: res => {
return res.data return res.data
} }
} }
}) })
export const dnsVerifyOKAtom = atom<boolean>(false)
//query dnsVerify
export const dnsVerifyAtom = (domains: string, block: boolean) => atomWithQuery<IApiResult, any>(() => {
return {
enabled: !block && domains.length > 0 && domains.includes('.'),
queryKey: [ 'dnsVerify', domains ],
queryFn: async ({ queryKey: [ , domains ] }) => {
if ((domains as string).length === 0) {
return Promise.reject({
data: []
})
}
return await websitesServ.cert.dnsVerify({
dns_list: domains,
})
},
select: res => {
return res.data?.dns_list
}
}
})
Loading…
Cancel
Save