Browse Source

完善SSL模块

main
dark 7 months ago
parent
commit
c5c0b02f97
  1. 20
      src/components/drawer-picker/DrawerPicker.tsx
  2. 78
      src/pages/websites/ssl/components/CAList.tsx
  3. 1
      src/pages/websites/ssl/components/DNSList.tsx
  4. 34
      src/pages/websites/ssl/index.tsx
  5. 9
      src/request.ts
  6. 11
      src/store/websites/ca.ts
  7. 16
      src/types/website/acme.d.ts
  8. 18
      src/types/website/ca.d.ts
  9. 8
      src/types/website/dns.d.ts
  10. 30
      src/types/website/ssl.d.ts

20
src/components/drawer-picker/DrawerPicker.tsx

@ -7,9 +7,10 @@ export interface DrawerPickerProps extends DrawerProps {
target?: React.ReactNode target?: React.ReactNode
children?: React.ReactNode children?: React.ReactNode
key?: string key?: string
foreRender?: boolean
} }
const DrawerPicker = ({ children, target, ...props }: DrawerPickerProps) => {
const DrawerPicker = ({ children, target, foreRender, ...props }: DrawerPickerProps) => {
const { styles } = useStyle() const { styles } = useStyle()
@ -22,15 +23,18 @@ const DrawerPicker = ({ children, target, ...props }: DrawerPickerProps) => {
return ( return (
<div className={styles.container} key={props.key ?? generateUUID()}> <div className={styles.container} key={props.key ?? generateUUID()}>
<span className={styles.target} onClick={() => {
<div className={styles.target} onClick={() => {
setOpen(true) setOpen(true)
}}> }}>
{getTarget()}
</span>
<Drawer {...props}
open={open}
onClose={() => setOpen(false)}
>{children}</Drawer>
{getTarget()}
</div>
{
(foreRender || open) && <Drawer {...props}
open={open}
onClose={() => setOpen(false)}
>{children}</Drawer>
}
</div> </div>
) )
} }

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

@ -4,7 +4,7 @@ import { ICA, } from '@/types/website/ca'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { deleteCaAtom } from '@/store/websites/ca.ts' import { deleteCaAtom } from '@/store/websites/ca.ts'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { Alert, Button, Form, Popconfirm } from 'antd'
import { Button, Form, Popconfirm } from 'antd'
import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts' import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts'
import { caListAtom, caPageAtom, saveOrUpdateCaAtom } from '@/store/websites/ca.ts' import { caListAtom, caPageAtom, saveOrUpdateCaAtom } from '@/store/websites/ca.ts'
@ -39,6 +39,68 @@ const CAList = () => {
] ]
} }
}, },
{
title: t('website.ssl.ca.form.common_name', '证书主体名称(CN)'),
dataIndex: 'common_name',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请输入') }
]
}
},
{
title: t('website.ssl.ca.form.organization', '公司/组织'),
dataIndex: 'organization',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请输入') }
]
}
},
{
title: t('website.ssl.ca.form.organization_uint', '部门'),
dataIndex: 'organization_uint',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {}
},
{
title: t('website.ssl.ca.form.country', '国家代号'),
dataIndex: 'country',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请输入') }
]
}
},
{
title: t('website.ssl.ca.form.province', '省份'),
dataIndex: 'province',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {}
},
{
title: t('website.ssl.ca.form.city', '城市'),
dataIndex: 'city',
hideInTable: true,
hideInSetting: true,
valueType: 'text',
formItemProps: {}
},
{ {
title: t('website.ssl.ca.columns.keyType', '密钥算法'), title: t('website.ssl.ca.columns.keyType', '密钥算法'),
@ -54,12 +116,12 @@ const CAList = () => {
}, },
}, },
{ {
title: t('website.ssl.ca.columns.url', 'URL'),
dataIndex: 'url',
valueType: 'text',
ellipsis: true, // 文本溢出省略
title: t('website.ssl.ca.columns.createAt', '时间'),
dataIndex: 'create_at',
valueType: 'dateTime',
hideInForm: true, hideInForm: true,
}, {
},
{
title: '操作', title: '操作',
valueType: 'option', valueType: 'option',
render: (_, record) => { render: (_, record) => {
@ -83,7 +145,6 @@ const CAList = () => {
return ( return (
<> <>
<Alert message={t('website.ssl.ca.tip', 'ca账户用于申请免费证书')}/>
<ProTable<ICA> <ProTable<ICA>
cardProps={{ cardProps={{
bodyStyle: { bodyStyle: {
@ -96,11 +157,12 @@ const CAList = () => {
onClick={() => { onClick={() => {
form.setFieldsValue({ form.setFieldsValue({
id: 0, id: 0,
country: 'CN',
keyType: KeyTypeEnum.EC256, keyType: KeyTypeEnum.EC256,
}) })
setOpen(true) setOpen(true)
}} }}
type={'primary'}>{t('website.ssl.ca.add', '添加ca帐户')}</Button>
type={'primary'}>{t('website.ssl.ca.add', '创建机构')}</Button>
} }
loading={isLoading} loading={isLoading}
dataSource={data?.rows ?? []} dataSource={data?.rows ?? []}

1
src/pages/websites/ssl/components/DNSList.tsx

@ -192,6 +192,7 @@ const DNSList = () => {
{ {
name: [ 'type' ], name: [ 'type' ],
valueType: 'dependency', valueType: 'dependency',
hideInSetting: true,
columns: ({ type }) => { columns: ({ type }) => {
return getKeyColumn(type, t) return getKeyColumn(type, t)
} }

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

@ -21,6 +21,7 @@ import AcmeList from './components/AcmeList.tsx'
import { acmeListAtom, AcmeType, getAcmeAccountTypeName } from '@/store/websites/acme.ts' import { acmeListAtom, AcmeType, getAcmeAccountTypeName } from '@/store/websites/acme.ts'
import { dnsListAtom, getDNSTypeName } from '@/store/websites/dns.ts' import { dnsListAtom, getDNSTypeName } from '@/store/websites/dns.ts'
import DNSList from './components/DNSList.tsx' import DNSList from './components/DNSList.tsx'
import CAList from '@/pages/websites/ssl/components/CAList.tsx'
const SSL = () => { const SSL = () => {
@ -33,7 +34,7 @@ const SSL = () => {
const { data: dnsData, isLoading: dnsLoading } = useAtomValue(dnsListAtom) 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)
const { mutate: delateSSL, isPending: isDeleteing } = useAtomValue(deleteSslAtom)
const { mutate: deleteSSL, isPending: isDeleting } = useAtomValue(deleteSslAtom)
const [ open, setOpen ] = useState(false) const [ open, setOpen ] = useState(false)
@ -125,6 +126,7 @@ const SSL = () => {
{ {
name: [ 'provider' ], name: [ 'provider' ],
valueType: 'dependency', valueType: 'dependency',
hideInSetting: true,
columns: ({ provider }) => { columns: ({ provider }) => {
if (provider === ProviderTypeEnum.DnsAccount) { if (provider === ProviderTypeEnum.DnsAccount) {
return [ { return [ {
@ -141,8 +143,6 @@ const SSL = () => {
value: item.id value: item.id
})) }))
}, },
} ] } ]
} }
return [] return []
@ -166,6 +166,7 @@ const SSL = () => {
{ {
name: [ 'pushDir' ], name: [ 'pushDir' ],
valueType: 'dependency', valueType: 'dependency',
hideInSetting: true,
columns: ({ pushDir }) => { columns: ({ pushDir }) => {
if (pushDir) { if (pushDir) {
return [ { return [ {
@ -198,9 +199,9 @@ const SSL = () => {
</a>, </a>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleteing}
disabled={isDeleting}
onConfirm={() => { onConfirm={() => {
delateSSL(record.id)
deleteSSL(record.id)
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del"> <a key="del">
@ -222,6 +223,11 @@ const SSL = () => {
rowKey={'id'} rowKey={'id'}
dataSource={data?.rows ?? []} dataSource={data?.rows ?? []}
columns={columns} columns={columns}
columnsState={{
defaultValue: {
option: { fixed: 'right', disable: true },
},
}}
options={{ options={{
reload: () => { reload: () => {
refetch() refetch()
@ -238,9 +244,19 @@ const SSL = () => {
actions: [ actions: [
<DrawerPicker <DrawerPicker
maskClosable={false} maskClosable={false}
title={t('website.ssl.actions.acme', 'Acme帐户')}
title={t('website.ssl.ca.title', '证书颁发机构')}
width={1000}
target={<Button type={'primary'} ghost={true}>
{t('website.ssl.actions.selfSigned', '自签证书')}
</Button>}
>
<CAList/>
</DrawerPicker>,
<DrawerPicker
maskClosable={false}
title={t('website.ssl.acme.title', 'Acme帐户')}
width={1000} width={1000}
target={<Button>
target={<Button type={'primary'} ghost={true}>
{t('website.ssl.actions.acme', 'Acme帐户')} {t('website.ssl.actions.acme', 'Acme帐户')}
</Button>} </Button>}
> >
@ -248,9 +264,9 @@ const SSL = () => {
</DrawerPicker>, </DrawerPicker>,
<DrawerPicker <DrawerPicker
maskClosable={false} maskClosable={false}
title={t('website.ssl.actions.dns', 'DNS帐户')}
title={t('website.ssl.dns.title', 'DNS帐户')}
width={1000} width={1000}
target={<Button>
target={<Button type={'primary'} ghost={true}>
{t('website.ssl.actions.dns', 'DNS帐户')} {t('website.ssl.actions.dns', 'DNS帐户')}
</Button>}> </Button>}>
<DNSList/> <DNSList/>

9
src/request.ts

@ -46,10 +46,9 @@ axiosInstance.interceptors.response.use(
message.destroy() message.destroy()
const result = response.data as IApiResult const result = response.data as IApiResult
switch (result.code) { switch (result.code) {
case 200: case 200:
//login
//login
if (response.config.url?.includes('/sys/login')) { if (response.config.url?.includes('/sys/login')) {
setToken(result.data.token) setToken(result.data.token)
const search = new URLSearchParams(window.location.search) const search = new URLSearchParams(window.location.search)
@ -105,6 +104,12 @@ axiosInstance.interceptors.response.use(
} }
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}` window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return return
case 403:
message.error('没有权限')
break
case 404:
message.error('请求的资源不存在')
break
default: default:
message.error(response.data.message ?? response.data ?? error.message ?? '请求失败') message.error(response.data.message ?? response.data ?? error.message ?? '请求失败')
return Promise.reject(response) return Promise.reject(response)

11
src/store/websites/ca.ts

@ -2,7 +2,7 @@ import { atom } from 'jotai/index'
import { IApiResult, IPage } from '@/global' import { IApiResult, IPage } from '@/global'
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query' import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
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 { ICA } from '@/types/website/ca' import { ICA } from '@/types/website/ca'
@ -22,7 +22,7 @@ export const caListAtom = atomWithQuery(get => ({
})) }))
//saveOrUpdate //saveOrUpdate
export const saveOrUpdateCaAtom = atomWithMutation<any, ICA>(get => ({
export const saveOrUpdateCaAtom = atomWithMutation<any, ICA, IApiResult>(get => ({
mutationKey: [ 'saveOrUpdateCA' ], mutationKey: [ 'saveOrUpdateCA' ],
mutationFn: async (data: ICA) => { mutationFn: async (data: ICA) => {
if (data.id > 0) { if (data.id > 0) {
@ -35,7 +35,12 @@ export const saveOrUpdateCaAtom = atomWithMutation<any, ICA>(get => ({
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
get(caListAtom).refetch() get(caListAtom).refetch()
return res return res
}
},
// onError: (err) => {
// const msg = err.data?.message || err.message || t('message.saveFail', '提交失败')
// message.error(msg)
// return err
// },
})) }))
export const deleteCaAtom = atomWithMutation<IApiResult, number>(get => ({ export const deleteCaAtom = atomWithMutation<IApiResult, number>(get => ({

16
src/types/website/acme.d.ts

@ -1,14 +1,14 @@
export interface IAcmeAccount { export interface IAcmeAccount {
id: number; id: number;
createdAt?: string;
createdBy: number;
updatedAt?: string;
updatedBy: number;
created_at: string | null;
created_by: number;
updated_at: string | null;
updated_by: number;
email: string; email: string;
url: string; url: string;
privateKey: string;
private_key: string;
type: string; type: string;
eabKid: string;
eabHmacKey: string;
keyType: string;
eab_kid: string;
eab_hmac_key: string;
key_type: string;
} }

18
src/types/website/ca.d.ts

@ -1,11 +1,17 @@
export interface ICA { export interface ICA {
id: number; id: number;
createdAt: string | null;
createdBy: number;
updatedAt: string | null;
updatedBy: number;
created_at: string | null;
created_by: number;
updated_at: string | null;
updated_by: number;
csr: string; csr: string;
name: string; name: string;
privateKey: string;
keyType: string;
common_name: string;
organization: string;
organization_unit: string;
country: string;
province: string;
city: string;
private_key: string;
key_type: string;
} }

8
src/types/website/dns.d.ts

@ -1,9 +1,9 @@
export interface IDnsAccount { export interface IDnsAccount {
id: number; id: number;
createdAt: string | null;
createdBy: number;
updatedAt: string | null;
updatedBy: number;
created_at: string | null;
created_by: number;
updated_at: string | null;
updated_by: number;
name: string; name: string;
type: string; type: string;
authorization: string; authorization: string;

30
src/types/website/ssl.d.ts

@ -1,27 +1,27 @@
export interface ISSL { export interface ISSL {
id: number; id: number;
createdAt: Date | null;
createdBy: number;
updatedAt: Date | null;
updatedBy: number;
primaryDomain: string;
privateKey: string;
created_at: string | null;
created_by: number;
updated_at: string | null;
updated_by: number;
primary_domain: string;
private_key: string;
pem: string; pem: string;
domains: string; domains: string;
certUrl: string;
cert_url: string;
type: string; type: string;
provider: string; provider: string;
organization: string; organization: string;
dnsAccountId: number;
acmeAccountId: number;
caId: number;
autoRenew: boolean;
expireDate: string | null;
startDate: string | null;
dns_account_id: number;
acme_account_id: number;
ca_id: number;
auto_renew: boolean;
expire_date: string | null;
start_date: string | null;
status: string; status: string;
message: string; message: string;
keyType: string;
pushDir: boolean;
key_type: string;
push_dir: boolean;
dir: string; dir: string;
description: string; description: string;
} }

Loading…
Cancel
Save