From 9de027c971b99451b80ce36bdd13cdb88a7447d5 Mon Sep 17 00:00:00 2001 From: dark Date: Fri, 3 May 2024 16:07:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84SSL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/websites/ssl/components/AcmeList.tsx | 22 +--- src/pages/websites/ssl/components/CAList.tsx | 150 +++++++++++++++++++++++++ src/pages/websites/ssl/index.tsx | 43 +++++-- src/service/websites.ts | 9 ++ src/store/websites/acme.ts | 24 +++- src/store/websites/ca.ts | 50 +++++++++ src/store/websites/dns.ts | 89 +++++++++++++-- src/store/websites/ssl.ts | 9 ++ 8 files changed, 355 insertions(+), 41 deletions(-) create mode 100644 src/pages/websites/ssl/components/CAList.tsx diff --git a/src/pages/websites/ssl/components/AcmeList.tsx b/src/pages/websites/ssl/components/AcmeList.tsx index 9575329..fc99e81 100644 --- a/src/pages/websites/ssl/components/AcmeList.tsx +++ b/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 { IAcmeAccount } from '@/types/website/acme' 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 { Alert, Button, Form } from 'antd' -import { KeyTypeEnum } from '@/store/websites/ssl.ts' +import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts' const AcmeList = () => { @@ -41,12 +41,7 @@ const AcmeList = () => { dataIndex: 'type', valueType: 'select', 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: { rules: [ @@ -58,15 +53,8 @@ const AcmeList = () => { title: t('website.ssl.acme.columns.keyType', '密钥算法'), dataIndex: 'keyType', valueType: 'select', - initialValue: KeyTypeEnum.EC256, 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: { rules: [ @@ -85,8 +73,6 @@ const AcmeList = () => { valueType: 'option', render: (_, record) => { return [ - { - }}>{t('actions.edit', '编辑')}, { }}>{t('actions.delete', '删除')}, ] diff --git a/src/pages/websites/ssl/components/CAList.tsx b/src/pages/websites/ssl/components/CAList.tsx new file mode 100644 index 0000000..bf39c0e --- /dev/null +++ b/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[]>(() => { + 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 [ + { + }}>{t('actions.delete', '删除')}, + ] + } + } + ] + }, []) + + return ( + <> + + + cardProps={{ + bodyStyle: { + padding: 0, + } + }} + rowKey="id" + headerTitle={ + + } + 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, + } + }) + }, + + }} + /> + + 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 \ No newline at end of file diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx index 538492e..58622f2 100644 --- a/src/pages/websites/ssl/index.tsx +++ b/src/pages/websites/ssl/index.tsx @@ -1,6 +1,6 @@ import { useAtom, useAtomValue } from 'jotai' import { - KeyTypeEnum, + KeyTypes, ProviderTypeEnum, saveOrUpdateSslAtom, sslListAtom, @@ -16,6 +16,8 @@ import { PlusOutlined } from '@ant-design/icons' import { ISSL } from '@/types/website/ssl' import DrawerPicker from '@/components/drawer-picker/DrawerPicker.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 = () => { @@ -24,6 +26,8 @@ const SSL = () => { const [ form ] = Form.useForm() const [ page, setPage ] = useAtom(sslPageAtom) 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 { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom) @@ -55,21 +59,27 @@ const SSL = () => { { title: t('website.ssl.columns.acmeAccountId', 'Acme帐号'), 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', '密钥算法'), dataIndex: 'keyType', hideInTable: true, valueType: 'select', - initialValue: KeyTypeEnum.EC256, 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: { rules: [ @@ -116,9 +126,18 @@ const SSL = () => { return [ { title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'), dataIndex: 'dnsAccountId', + valueType: 'select', formItemProps: { 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 = () => { , - {t( 'website.ssl.actions.dns', 'DNS帐户')} + {t('website.ssl.actions.dns', 'DNS帐户')} }> , @@ -240,7 +259,7 @@ const SSL = () => { }} type="primary" > - {t('actions.add', '申请证书')} + {t('actions.sslApply', '申请证书')} , ] }} diff --git a/src/service/websites.ts b/src/service/websites.ts index 2e7546a..f3cf596 100644 --- a/src/service/websites.ts +++ b/src/service/websites.ts @@ -1,6 +1,8 @@ import { createCURD } from '@/service/base.ts' import { ISSL } from '@/types/website/ssl' import { IAcmeAccount } from '@/types/website/acme' +import { IDnsAccount } from '@/types/website/dns' +import { ICA } from '@/types/website/ca' const websitesServ = { ssl: { @@ -8,7 +10,14 @@ const websitesServ = { }, acme:{ ...createCURD('/website/acme') + }, + dns:{ + ...createCURD('/website/dns') + }, + ca:{ + ...createCURD('/website/ca') } + } export default websitesServ \ No newline at end of file diff --git a/src/store/websites/acme.ts b/src/store/websites/acme.ts index 88e4bb5..f163984 100644 --- a/src/store/websites/acme.ts +++ b/src/store/websites/acme.ts @@ -3,7 +3,7 @@ import { IAcmeAccount } from '@/types/website/acme' import websitesServ from '@/service/websites.ts' import { message } from 'antd' import { t } from 'i18next' -import { IPage } from '@/global' +import { IApiResult, IPage } from '@/global' import { atom } from 'jotai' export enum AcmeType { @@ -16,6 +16,17 @@ export enum AcmeType { 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({ page: 1, pageSize: 10, }) @@ -47,3 +58,14 @@ export const saveOrUpdateAcmeAtom = atomWithMutation(get => ( return res } })) + +export const deleteAcmeAtom = atomWithMutation(get => ({ + mutationKey: [ 'sslDelete' ], + mutationFn: async (id) => { + return await websitesServ.acme.delete(id) + }, + onSuccess: () => { + message.success(t('message.deleteSuccess', '删除成功')) + get(acmeListAtom).refetch() + } +})) \ No newline at end of file diff --git a/src/store/websites/ca.ts b/src/store/websites/ca.ts index e69de29..0b8c1be 100644 --- a/src/store/websites/ca.ts +++ b/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({ + 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(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(get => ({ + mutationKey: [ 'sslDelete' ], + mutationFn: async (id) => { + return await websitesServ.ca.delete(id) + }, + onSuccess: () => { + message.success(t('message.deleteSuccess', '删除成功')) + get(caListAtom).refetch() + } +})) \ No newline at end of file diff --git a/src/store/websites/dns.ts b/src/store/websites/dns.ts index 36a8c9d..e75d322 100644 --- a/src/store/websites/dns.ts +++ b/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; -} \ No newline at end of file +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({ + 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(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 + } +})) diff --git a/src/store/websites/ssl.ts b/src/store/websites/ssl.ts index e4c7804..c3b1297 100644 --- a/src/store/websites/ssl.ts +++ b/src/store/websites/ssl.ts @@ -20,6 +20,15 @@ export enum KeyTypeEnum { 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({ page: 1, pageSize: 20,