Browse Source

完善SSL

main
dark 5 months ago
parent
commit
9de027c971
  1. 22
      src/pages/websites/ssl/components/AcmeList.tsx
  2. 150
      src/pages/websites/ssl/components/CAList.tsx
  3. 43
      src/pages/websites/ssl/index.tsx
  4. 9
      src/service/websites.ts
  5. 24
      src/store/websites/acme.ts
  6. 50
      src/store/websites/ca.ts
  7. 87
      src/store/websites/dns.ts
  8. 9
      src/store/websites/ssl.ts

22
src/pages/websites/ssl/components/AcmeList.tsx

@ -2,10 +2,10 @@ import { useMemo, useState } from 'react'
import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components' import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
import { IAcmeAccount } from '@/types/website/acme' import { IAcmeAccount } from '@/types/website/acme'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { acmeListAtom, acmePageAtom, AcmeType, saveOrUpdateAcmeAtom } from '@/store/websites/acme.ts'
import { AcmeAccountTypes, acmeListAtom, acmePageAtom, AcmeType, saveOrUpdateAcmeAtom } from '@/store/websites/acme.ts'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { Alert, Button, Form } from 'antd' import { Alert, Button, Form } from 'antd'
import { KeyTypeEnum } from '@/store/websites/ssl.ts'
import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts'
const AcmeList = () => { const AcmeList = () => {
@ -41,12 +41,7 @@ const AcmeList = () => {
dataIndex: 'type', dataIndex: 'type',
valueType: 'select', valueType: 'select',
fieldProps: { fieldProps: {
options: [
{ label: 'Let\'s Encrypt', value: AcmeType.LetsEncrypt },
{ label: 'ZeroSSl', value: AcmeType.ZeroSSl },
{ label: 'Buypass', value: AcmeType.Buypass },
{ label: 'Google Cloud', value: AcmeType.Google },
]
options: AcmeAccountTypes
}, },
formItemProps: { formItemProps: {
rules: [ rules: [
@ -58,15 +53,8 @@ const AcmeList = () => {
title: t('website.ssl.acme.columns.keyType', '密钥算法'), title: t('website.ssl.acme.columns.keyType', '密钥算法'),
dataIndex: 'keyType', dataIndex: 'keyType',
valueType: 'select', valueType: 'select',
initialValue: KeyTypeEnum.EC256,
fieldProps: { fieldProps: {
options: [
{ label: t('website.ssl.keyTypeEnum.EC256', 'EC 256'), value: KeyTypeEnum.EC256 },
{ label: t('website.ssl.keyTypeEnum.EC384', 'EC 384'), value: KeyTypeEnum.EC384 },
{ label: t('website.ssl.keyTypeEnum.RSA2048', 'RSA 2048'), value: KeyTypeEnum.RSA2048 },
{ label: t('website.ssl.keyTypeEnum.RSA3072', 'RSA 3072'), value: KeyTypeEnum.RSA3072 },
{ label: t('website.ssl.keyTypeEnum.RSA4096', 'RSA 4096'), value: KeyTypeEnum.RSA4096 },
]
options: KeyTypes
}, },
formItemProps: { formItemProps: {
rules: [ rules: [
@ -85,8 +73,6 @@ const AcmeList = () => {
valueType: 'option', valueType: 'option',
render: (_, record) => { render: (_, record) => {
return [ return [
<a key="edit" onClick={() => {
}}>{t('actions.edit', '编辑')}</a>,
<a key="delete" onClick={() => { <a key="delete" onClick={() => {
}}>{t('actions.delete', '删除')}</a>, }}>{t('actions.delete', '删除')}</a>,
] ]

150
src/pages/websites/ssl/components/CAList.tsx

@ -0,0 +1,150 @@
import { useMemo, useState } from 'react'
import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
import { ICA, IcaAccount } from '@/types/website/ca'
import { useTranslation } from '@/i18n.ts'
import { caAccountTypes, caType } from '@/store/websites/ca.ts'
import { useAtom, useAtomValue } from 'jotai'
import { Alert, Button, Form } from 'antd'
import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts'
import { caListAtom, caPageAtom, saveOrUpdateCaAtom } from '@/store/websites/ca.ts'
const CAList = () => {
const { t } = useTranslation()
const [ form ] = Form.useForm()
const [ page, setPage ] = useAtom(caPageAtom)
const { data, isLoading, refetch } = useAtomValue(caListAtom)
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateCaAtom)
const [ open, setOpen ] = useState(false)
const columns = useMemo<ProColumns<ICA>[]>(() => {
return [
{
title: 'ID',
dataIndex: 'id',
hideInTable: true,
formItemProps: {
hidden: true,
}
},
{
title: t('website.ssl.ca.columns.name', '名称'),
dataIndex: 'name',
valueType: 'text',
formItemProps: {
label: t('website.ssl.ca.form.name', '机构名称'),
rules: [
{ required: true, message: t('message.required', '请输入') }
]
}
},
{
title: t('website.ssl.ca.columns.keyType', '密钥算法'),
dataIndex: 'keyType',
valueType: 'select',
fieldProps: {
options: KeyTypes
},
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请选择') }
]
},
},
{
title: t('website.ssl.ca.columns.url', 'URL'),
dataIndex: 'url',
valueType: 'text',
ellipsis: true, // 文本溢出省略
hideInForm: true,
}, {
title: '操作',
valueType: 'option',
render: (_, record) => {
return [
<a key="delete" onClick={() => {
}}>{t('actions.delete', '删除')}</a>,
]
}
}
]
}, [])
return (
<>
<Alert message={t('website.ssl.ca.tip', 'ca账户用于申请免费证书')}/>
<ProTable<IcaAccount>
cardProps={{
bodyStyle: {
padding: 0,
}
}}
rowKey="id"
headerTitle={
<Button
onClick={() => {
form.setFieldsValue({
id: 0,
type: caType.LetsEncrypt,
keyType: KeyTypeEnum.EC256,
})
setOpen(true)
}}
type={'primary'}>{t('website.ssl.ca.add', '添加ca帐户')}</Button>
}
loading={isLoading}
dataSource={data?.rows ?? []}
columns={columns}
search={false}
options={{
reload: () => {
refetch()
},
}}
pagination={{
total: data?.total,
pageSize: page.pageSize,
current: page.page,
onChange: (current, pageSize) => {
setPage(prev => {
return {
...prev,
page: current,
pageSize: pageSize,
}
})
},
}}
/>
<BetaSchemaForm<IcaAccount>
shouldUpdate={false}
width={600}
form={form}
layout={'horizontal'}
scrollToFirstError={true}
title={t(`website.ssl.ca.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '证书编辑' : '证书添加')}
// colProps={{ span: 24 }}
labelCol={{ span: 6 }}
wrapperCol={{ span: 14 }}
layoutType={'ModalForm'}
open={open}
modalProps={{
maskClosable: false,
}}
onOpenChange={(open) => {
setOpen(open)
}}
loading={isSubmitting}
onFinish={async (values) => {
// console.log('values', values)
saveOrUpdate(values)
return isSuccess
}}
columns={columns as ProFormColumnsType[]}/>
</>
)
}
export default CAList

43
src/pages/websites/ssl/index.tsx

@ -1,6 +1,6 @@
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
KeyTypeEnum,
KeyTypes,
ProviderTypeEnum, ProviderTypeEnum,
saveOrUpdateSslAtom, saveOrUpdateSslAtom,
sslListAtom, sslListAtom,
@ -16,6 +16,8 @@ import { PlusOutlined } from '@ant-design/icons'
import { ISSL } from '@/types/website/ssl' import { ISSL } from '@/types/website/ssl'
import DrawerPicker from '@/components/drawer-picker/DrawerPicker.tsx' import DrawerPicker from '@/components/drawer-picker/DrawerPicker.tsx'
import AcmeList from '@/pages/websites/ssl/components/AcmeList.tsx' import AcmeList from '@/pages/websites/ssl/components/AcmeList.tsx'
import { acmeListAtom, AcmeType, getAcmeAccountTypeName } from '@/store/websites/acme.ts'
import { dnsListAtom, getDNSTypeName } from '@/store/websites/dns.ts'
const SSL = () => { const SSL = () => {
@ -24,6 +26,8 @@ const SSL = () => {
const [ form ] = Form.useForm() const [ form ] = Form.useForm()
const [ page, setPage ] = useAtom(sslPageAtom) const [ page, setPage ] = useAtom(sslPageAtom)
const [ search, setSearch ] = useAtom(sslSearchAtom) const [ search, setSearch ] = useAtom(sslSearchAtom)
const { data: acmeData, isLoading: acmeLoading } = useAtomValue(acmeListAtom)
const { data: dnsData, isLoading: dnsLoading } = useAtomValue(dnsListAtom)
const { data, isLoading, isFetching, refetch } = useAtomValue(sslListAtom) const { data, isLoading, isFetching, refetch } = useAtomValue(sslListAtom)
const { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom) const { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom)
@ -55,21 +59,27 @@ const SSL = () => {
{ {
title: t('website.ssl.columns.acmeAccountId', 'Acme帐号'), title: t('website.ssl.columns.acmeAccountId', 'Acme帐号'),
dataIndex: 'acmeAccountId', dataIndex: 'acmeAccountId',
valueType: 'select',
fieldProps: {
loading: acmeLoading,
options: acmeData?.rows?.map(item => ({
label: `${item.email} [${getAcmeAccountTypeName(item.type as AcmeType)}]`,
value: item.id
}))
},
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请选择') }
]
}
}, },
{ {
title: t('website.ssl.columns.keyType', '密钥算法'), title: t('website.ssl.columns.keyType', '密钥算法'),
dataIndex: 'keyType', dataIndex: 'keyType',
hideInTable: true, hideInTable: true,
valueType: 'select', valueType: 'select',
initialValue: KeyTypeEnum.EC256,
fieldProps: { fieldProps: {
options: [
{ label: t('website.ssl.keyTypeEnum.EC256', 'EC 256'), value: KeyTypeEnum.EC256 },
{ label: t('website.ssl.keyTypeEnum.EC384', 'EC 384'), value: KeyTypeEnum.EC384 },
{ label: t('website.ssl.keyTypeEnum.RSA2048', 'RSA 2048'), value: KeyTypeEnum.RSA2048 },
{ label: t('website.ssl.keyTypeEnum.RSA3072', 'RSA 3072'), value: KeyTypeEnum.RSA3072 },
{ label: t('website.ssl.keyTypeEnum.RSA4096', 'RSA 4096'), value: KeyTypeEnum.RSA4096 },
]
options: KeyTypes
}, },
formItemProps: { formItemProps: {
rules: [ rules: [
@ -116,9 +126,18 @@ const SSL = () => {
return [ { return [ {
title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'), title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'),
dataIndex: 'dnsAccountId', dataIndex: 'dnsAccountId',
valueType: 'select',
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required', '请输入DNS帐号') } ] rules: [ { required: true, message: t('message.required', '请输入DNS帐号') } ]
}
},
fieldProps: {
loading: dnsLoading,
options: dnsData?.rows.map(item => ({
label: `${item.name} [${getDNSTypeName(item.type)}]`,
value: item.id
}))
},
} ] } ]
} }
@ -224,7 +243,7 @@ const SSL = () => {
<AcmeList/> <AcmeList/>
</DrawerPicker>, </DrawerPicker>,
<DrawerPicker target={<Button> <DrawerPicker target={<Button>
{t( 'website.ssl.actions.dns', 'DNS帐户')}
{t('website.ssl.actions.dns', 'DNS帐户')}
</Button>}> </Button>}>
</DrawerPicker>, </DrawerPicker>,
@ -240,7 +259,7 @@ const SSL = () => {
}} }}
type="primary" type="primary"
> >
{t('actions.add', '申请证书')}
{t('actions.sslApply', '申请证书')}
</Button>, </Button>,
] ]
}} }}

9
src/service/websites.ts

@ -1,6 +1,8 @@
import { createCURD } from '@/service/base.ts' import { createCURD } from '@/service/base.ts'
import { ISSL } from '@/types/website/ssl' import { ISSL } from '@/types/website/ssl'
import { IAcmeAccount } from '@/types/website/acme' import { IAcmeAccount } from '@/types/website/acme'
import { IDnsAccount } from '@/types/website/dns'
import { ICA } from '@/types/website/ca'
const websitesServ = { const websitesServ = {
ssl: { ssl: {
@ -8,7 +10,14 @@ const websitesServ = {
}, },
acme:{ acme:{
...createCURD<any, IAcmeAccount>('/website/acme') ...createCURD<any, IAcmeAccount>('/website/acme')
},
dns:{
...createCURD<any, IDnsAccount>('/website/dns')
},
ca:{
...createCURD<any, ICA>('/website/ca')
} }
} }
export default websitesServ export default websitesServ

24
src/store/websites/acme.ts

@ -3,7 +3,7 @@ import { IAcmeAccount } from '@/types/website/acme'
import websitesServ from '@/service/websites.ts' import websitesServ from '@/service/websites.ts'
import { message } from 'antd' import { message } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { IPage } from '@/global'
import { IApiResult, IPage } from '@/global'
import { atom } from 'jotai' import { atom } from 'jotai'
export enum AcmeType { export enum AcmeType {
@ -16,6 +16,17 @@ export enum AcmeType {
Google = 'google', Google = 'google',
} }
export const AcmeAccountTypes = [
{ label: 'Let\'s Encrypt', value: AcmeType.LetsEncrypt },
{ label: 'ZeroSSL', value: AcmeType.ZeroSSl },
{ label: 'Buypass', value: AcmeType.Buypass },
{ label: 'Google Cloud', value: AcmeType.Google },
]
export const getAcmeAccountTypeName = (type: AcmeType) => {
return AcmeAccountTypes.find(item => item.value === type)?.label
}
export const acmePageAtom = atom<IPage>({ export const acmePageAtom = atom<IPage>({
page: 1, pageSize: 10, page: 1, pageSize: 10,
}) })
@ -47,3 +58,14 @@ export const saveOrUpdateAcmeAtom = atomWithMutation<any, IAcmeAccount>(get => (
return res return res
} }
})) }))
export const deleteAcmeAtom = atomWithMutation<IApiResult, number>(get => ({
mutationKey: [ 'sslDelete' ],
mutationFn: async (id) => {
return await websitesServ.acme.delete(id)
},
onSuccess: () => {
message.success(t('message.deleteSuccess', '删除成功'))
get(acmeListAtom).refetch()
}
}))

50
src/store/websites/ca.ts

@ -0,0 +1,50 @@
import { atom } from 'jotai/index'
import { IApiResult, IPage } from '@/global'
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
import websitesServ from '@/service/websites.ts'
import { message } from 'antd'
import { t } from 'i18next'
import { ICA } from '@/types/website/ca'
export const caPageAtom = atom<IPage>({
page: 1, pageSize: 10,
})
//list
export const caListAtom = atomWithQuery(get => ({
queryKey: [ 'caList', get(caPageAtom) ],
queryFn: async ({ queryKey: [ , page ] }) => {
return await websitesServ.ca.list(page)
},
select: (data) => {
return data.data
}
}))
//saveOrUpdate
export const saveOrUpdateCaAtom = atomWithMutation<any, ICA>(get => ({
mutationKey: [ 'saveOrUpdateCA' ],
mutationFn: async (data: ICA) => {
if (data.id > 0) {
return await websitesServ.ca.update(data)
}
return await websitesServ.ca.add(data)
},
onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
get(caListAtom).refetch()
return res
}
}))
export const deleteCaAtom = atomWithMutation<IApiResult, number>(get => ({
mutationKey: [ 'sslDelete' ],
mutationFn: async (id) => {
return await websitesServ.ca.delete(id)
},
onSuccess: () => {
message.success(t('message.deleteSuccess', '删除成功'))
get(caListAtom).refetch()
}
}))

87
src/store/websites/dns.ts

@ -1,10 +1,79 @@
export interface IDnsAccount {
id: number;
createdAt: Date | null;
createdBy: number;
updatedAt: Date | null;
updatedBy: number;
name: string;
type: string;
authorization: string;
import { t } from 'i18next'
import { atom } from 'jotai/index'
import { IPage } from '@/global'
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
import websitesServ from '@/service/websites.ts'
import { message } from 'antd'
import { IDnsAccount } from '@/types/website/dns'
export const DNSTypes = [
{
label: t('website.dns.types.aliyun', '阿里云DNS'),
value: 'AliYun',
},
{
label: t('website.dns.types.tencentCloud', '腾讯云'),
value: 'TencentCloud',
},
{
label: t('website.dns.types.dnsPod', 'DNSPod'),
value: 'DnsPod',
},
{
label: 'CloudFlare',
value: 'CloudFlare',
},
{
label: 'NameSilo',
value: 'NameSilo',
},
{
label: 'NameCheap',
value: 'NameCheap',
},
{
label: 'Name.com',
value: 'NameCom',
},
{
label: 'GoDaddy',
value: 'Godaddy',
},
]
export const getDNSTypeName = (type: string) => {
return DNSTypes.find(item => item.value === type)?.label
} }
export const dnsPageAtom = atom<IPage>({
page: 1, pageSize: 10,
})
//list
export const dnsListAtom = atomWithQuery(get => ({
queryKey: [ 'dnsList', get(dnsPageAtom) ],
queryFn: async ({ queryKey: [ , page ] }) => {
return await websitesServ.dns.list(page)
},
select: (data) => {
return data.data
}
}))
//saveOrUpdate
export const saveOrUpdateDNSAtom = atomWithMutation<any, IDnsAccount>(get => ({
mutationKey: [ 'saveOrUpdatedDNS' ],
mutationFn: async (data: IDnsAccount) => {
if (data.id > 0) {
return await websitesServ.dns.update(data)
}
return await websitesServ.dns.add(data)
},
onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
get(dnsListAtom).refetch()
return res
}
}))

9
src/store/websites/ssl.ts

@ -20,6 +20,15 @@ export enum KeyTypeEnum {
RSA4096 = '4096', RSA4096 = '4096',
} }
export const KeyTypes = [
{ label: 'EC 256', value: 'P256' },
{ label: 'EC 384', value: 'P384' },
{ label: 'RSA 2048', value: '2048' },
{ label: 'RSA 3072', value: '3072' },
{ label: 'RSA 4096', value: '4096' },
];
export const sslPageAtom = atom<IPage>({ export const sslPageAtom = atom<IPage>({
page: 1, page: 1,
pageSize: 20, pageSize: 20,

Loading…
Cancel
Save