diff --git a/src/pages/websites/ssl/components/AcmeList.tsx b/src/pages/websites/ssl/acme/AcmeList.tsx similarity index 100% rename from src/pages/websites/ssl/components/AcmeList.tsx rename to src/pages/websites/ssl/acme/AcmeList.tsx diff --git a/src/pages/websites/ssl/components/CAList.tsx b/src/pages/websites/ssl/ca/CAList.tsx similarity index 86% rename from src/pages/websites/ssl/components/CAList.tsx rename to src/pages/websites/ssl/ca/CAList.tsx index e0ded30..c503334 100644 --- a/src/pages/websites/ssl/components/CAList.tsx +++ b/src/pages/websites/ssl/ca/CAList.tsx @@ -2,11 +2,14 @@ import { useMemo, useState } from 'react' import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components' import { useTranslation } from '@/i18n.ts' import { deleteCaAtom } from '@/store/websites/ca.ts' -import { useAtom, useAtomValue } from 'jotai' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { Button, Form, Popconfirm } from 'antd' import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts' import { caListAtom, caPageAtom, saveOrUpdateCaAtom } from '@/store/websites/ca.ts' import { WebSite } from '@/types' +import Detail from './Detail.tsx' +import { detailAtom, selfSignAtom } from './store.ts' +import SelfSign from './SelfSign.tsx' const CAList = () => { @@ -17,7 +20,8 @@ const CAList = () => { const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateCaAtom) const { mutate: deleteCA, isPending: isDeleting } = useAtomValue(deleteCaAtom) const [ open, setOpen ] = useState(false) - + const updateUI = useSetAtom(detailAtom) + const selfSignUI = useSetAtom(selfSignAtom) const columns = useMemo[]>(() => { return [ { @@ -104,7 +108,7 @@ const CAList = () => { { title: t('website.ssl.ca.columns.keyType', '密钥算法'), - dataIndex: 'keyType', + dataIndex: 'key_type', valueType: 'select', fieldProps: { options: KeyTypes @@ -117,7 +121,7 @@ const CAList = () => { }, { title: t('website.ssl.ca.columns.createAt', '时间'), - dataIndex: 'create_at', + dataIndex: 'created_at', valueType: 'dateTime', hideInForm: true, }, @@ -126,6 +130,24 @@ const CAList = () => { valueType: 'option', render: (_, record) => { return [ + { + selfSignUI({ + open: true, + record: { + id: record.id, + key_type: KeyTypeEnum.EC256, + unit: 'year', + auto_renew: true, + time: 10, + } + }) + }}>{t('website.actions.selfSign', '签发证书')}, + { + updateUI({ + open: true, record, + }) + }}>{t('website.actions.detail', '详情')}, + { return isSuccess }} columns={columns as ProFormColumnsType[]}/> + + ) } diff --git a/src/pages/websites/ssl/ca/Detail.tsx b/src/pages/websites/ssl/ca/Detail.tsx new file mode 100644 index 0000000..b214aa9 --- /dev/null +++ b/src/pages/websites/ssl/ca/Detail.tsx @@ -0,0 +1,116 @@ +import { Segmented, Drawer, DrawerProps, Input, Button, Flex, message, Descriptions } from 'antd' +import { useEffect, useMemo, useState } from 'react' +import { useTranslation } from '@/i18n.ts' +import { useAtom } from 'jotai' +import { detailAtom } from './store.ts' +import { useStyle } from './style.ts' +import { useCopyToClipboard } from 'react-use' + +const Detail = (props: DrawerProps) => { + + + const prefix = 'website.ssl.ca.detail' + const { t } = useTranslation() + const { styles } = useStyle() + const [ key, setKey ] = useState('base') + const [ ui, setUI ] = useAtom(detailAtom) + const [ copyState, setCopy ] = useCopyToClipboard() + const options = useMemo(() => { + return [ + { label: t(`${prefix}.base`, '机构详情'), value: 'base' }, + { label: t(`${prefix}.src`, 'src'), value: 'csr' }, + { label: t(`${prefix}.private_key`, '私钥'), value: 'private_key' }, + ] + }, []) + + useEffect(() => { + if (copyState.error) { + message.error(t('message.copyError', '复制失败')) + } else if (copyState.value) { + message.success(t('message.copySuccess', '复制成功')) + } + }, [ copyState ]) + + const render = useMemo(() => { + switch (key) { + case 'base': + return
+ + + {ui.record?.name} + + + {ui.record?.common_name} + + + {ui.record?.organization} + + + {ui.record?.organizationUint} + + + {ui.record?.country} + + + {ui.record?.province} + + + {ui.record?.city} + + +
+ case 'csr': + return + + + + + + case 'private_key': + return + + + + + + default: + return null + } + }, [ key, ui.record ]) + + return ( + + setUI(prev => ({ + ...prev, + open: false + })) + } + > + +
+ {render} +
+
+ ) +} + +export default Detail \ No newline at end of file diff --git a/src/pages/websites/ssl/ca/SelfSign.tsx b/src/pages/websites/ssl/ca/SelfSign.tsx new file mode 100644 index 0000000..6e9c0c1 --- /dev/null +++ b/src/pages/websites/ssl/ca/SelfSign.tsx @@ -0,0 +1,136 @@ +import { Form, Input, InputNumber, Modal, ModalProps, Select, Space, Switch } from 'antd' +import { WebSite } from '@/types' +import { useTranslation } from '@/i18n.ts' +import { useAtom, useAtomValue } from 'jotai' +import { selfSignAtom } from './store.ts' +import { KeyTypes } from '@/store/websites/ssl.ts' +import { useEffect } from 'react' +import { obtainSslAtom } from '@/store/websites/ca.ts' + +const SelfSign = (props: ModalProps) => { + const prefix = 'website.ssl.ca.selfSign' + const [ form ] = Form.useForm() + const { t } = useTranslation() + const [ ui, setUI ] = useAtom(selfSignAtom) + const { mutate, isPending, isSuccess } = useAtomValue(obtainSslAtom) + + useEffect(() => { + form.setFieldsValue(ui.record) + }, [ ui.open ]) + + return ( + { + setUI({ open: false, record: {} }) + }} + confirmLoading={isPending} + onOk={() => { + form.validateFields().then(() => { + mutate(ui.record) + }) + return isSuccess + }} + > + + form={form} + labelCol={{ span: 6 }} + wrapperCol={{ span: 14 }} + onValuesChange={value => { + setUI(prev => { + return { + ...prev, + record: { + ...prev.record, + ...value, + }, + } + }) + }} + > + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default SelfSign \ No newline at end of file diff --git a/src/pages/websites/ssl/ca/store.ts b/src/pages/websites/ssl/ca/store.ts new file mode 100644 index 0000000..bc690fe --- /dev/null +++ b/src/pages/websites/ssl/ca/store.ts @@ -0,0 +1,20 @@ +import { atom } from 'jotai' +import { WebSite } from '@/types' + +type DetailUI = { + open: boolean, + record?: WebSite.ICA, + +} +type SelfSignUI = { + open: boolean, + record?: WebSite.ISSLObtainByCA, +} +export const detailAtom = atom({ + open: false, +}) + +export const selfSignAtom = atom({ + open: false, +}) + diff --git a/src/pages/websites/ssl/ca/style.ts b/src/pages/websites/ssl/ca/style.ts new file mode 100644 index 0000000..cf59121 --- /dev/null +++ b/src/pages/websites/ssl/ca/style.ts @@ -0,0 +1,23 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-ca-detail-page` + + const container = css` + + ` + + const content = css` + padding: 20px 0; + + .ant-descriptions-item-label{ + font-weight: bold; + width: 50%; + } + ` + + return { + container: cx(prefix, props?.className, container), + content, + } +}) \ No newline at end of file diff --git a/src/pages/websites/ssl/components/DNSList.tsx b/src/pages/websites/ssl/dns/DNSList.tsx similarity index 100% rename from src/pages/websites/ssl/components/DNSList.tsx rename to src/pages/websites/ssl/dns/DNSList.tsx diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx index 13a2dce..8f77f2b 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 { - deleteSslAtom, + deleteSslAtom, getProvider, KeyTypeEnum, KeyTypes, ProviderTypeEnum, @@ -16,12 +16,13 @@ import { useTranslation } from '@/i18n.ts' import { Button, Form, Popconfirm } from 'antd' import { PlusOutlined } from '@ant-design/icons' import DrawerPicker from '@/components/drawer-picker/DrawerPicker.tsx' -import AcmeList from './components/AcmeList.tsx' +import AcmeList from './acme/AcmeList.tsx' import { acmeListAtom, AcmeType, getAcmeAccountTypeName } from '@/store/websites/acme.ts' import { dnsListAtom, getDNSTypeName } from '@/store/websites/dns.ts' -import DNSList from './components/DNSList.tsx' -import CAList from '@/pages/websites/ssl/components/CAList.tsx' +import DNSList from './dns/DNSList.tsx' +import CAList from './ca/CAList.tsx' import { WebSite } from '@/types' +import Switch from '@/components/switch' const SSL = () => { @@ -51,7 +52,7 @@ const SSL = () => { }, { title: t('website.ssl.columns.primaryDomain', '域名'), - dataIndex: 'primaryDomain', + dataIndex: 'primary_domain', formItemProps: { label: t('website.ssl.form.primaryDomain', '主域名'), rules: [ { required: true, message: t('message.required', '主域名') } ] @@ -59,11 +60,11 @@ const SSL = () => { }, { title: t('website.ssl.columns.otherDomains', '其它域名'), - dataIndex: 'otherDomains', + dataIndex: 'domains', }, { title: t('website.ssl.columns.acmeAccountId', 'Acme帐号'), - dataIndex: 'acmeAccountId', + dataIndex: 'acme_account_id', valueType: 'select', fieldProps: { loading: acmeLoading, @@ -80,7 +81,7 @@ const SSL = () => { }, { title: t('website.ssl.columns.keyType', '密钥算法'), - dataIndex: 'keyType', + dataIndex: 'key_type', hideInTable: true, valueType: 'select', fieldProps: { @@ -96,7 +97,6 @@ const SSL = () => { title: t('website.ssl.columns.provider', '申请方式'), dataIndex: 'provider', valueType: 'radio', - initialValue: ProviderTypeEnum.DnsAccount, valueEnum: { [ProviderTypeEnum.DnsAccount]: { text: t('website.ssl.providerTypeEnum.DnsAccount', 'DNS帐号'), @@ -109,6 +109,9 @@ const SSL = () => { } }, dependencies: [ 'provider' ], + renderText: (text) => { + return getProvider(text) + }, formItemProps: (form, config) => { const val = form.getFieldValue(config.dataIndex) const help = { @@ -127,11 +130,12 @@ const SSL = () => { name: [ 'provider' ], valueType: 'dependency', hideInSetting: true, + hideInTable: true, columns: ({ provider }) => { if (provider === ProviderTypeEnum.DnsAccount) { return [ { title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'), - dataIndex: 'dnsAccountId', + dataIndex: 'dns_account_id', valueType: 'select', formItemProps: { rules: [ { required: true, message: t('message.required', '请输入DNS帐号') } ] @@ -151,22 +155,25 @@ const SSL = () => { }, { title: t('website.ssl.columns.autoRenew', '自动续签'), - dataIndex: 'autoRenew', + dataIndex: 'auto_renew', valueType: 'switch', - + render: (_, record) => { + return + } }, { title: t('website.ssl.columns.pushDir', '推送证书到本地目录'), - dataIndex: 'pushDir', + dataIndex: 'push_dir', valueType: 'switch', hideInTable: true, hideInSearch: true, }, { - name: [ 'pushDir' ], + name: [ 'push_dir' ], valueType: 'dependency', hideInSetting: true, + hideInTable: true, columns: ({ pushDir }) => { if (pushDir) { return [ { @@ -187,6 +194,11 @@ const SSL = () => { dataIndex: 'description', }, { + title: t('website.ssl.columns.expire_date', '过期时间'), + dataIndex: 'expire_date', + valueType: 'dateTime' + }, + { title: t('website.ssl.columns.option', '操作'), valueType: 'option', key: 'option', render: (_, record) => [ diff --git a/src/service/websites.ts b/src/service/websites.ts index 0191bd7..90848ef 100644 --- a/src/service/websites.ts +++ b/src/service/websites.ts @@ -1,18 +1,23 @@ import { createCURD } from '@/service/base.ts' import { WebSite } from '@/types' +import request from '@/request.ts' const websitesServ = { ssl: { ...createCURD('/website/ssl') }, - acme:{ + acme: { ...createCURD('/website/acme') }, - dns:{ - ...createCURD('/website/dns') + dns: { + ...createCURD('/website/dns_account') }, - ca:{ - ...createCURD('/website/ca') + ca: { + ...createCURD('/website/ca'), + obtainSsl: async (params: WebSite.ISSLObtainByCA) => { + return request.post('/website/ca/obtain_ssl', params) + }, + } } diff --git a/src/store/websites/acme.ts b/src/store/websites/acme.ts index 676724a..9ccf02a 100644 --- a/src/store/websites/acme.ts +++ b/src/store/websites/acme.ts @@ -7,13 +7,13 @@ import { atom } from 'jotai' import { WebSite } from '@/types' export enum AcmeType { - LetsEncrypt = 'letsencrypt', + LetsEncrypt = 'LetsEncrypt', //zerossl - ZeroSSl = 'zerossl', + ZeroSSl = 'ZeroSSl', //buypass - Buypass = 'buypass', + Buypass = 'Buypass', //google - Google = 'google', + Google = 'Google', } export const AcmeAccountTypes = [ diff --git a/src/store/websites/ca.ts b/src/store/websites/ca.ts index e8c0898..c91c6ea 100644 --- a/src/store/websites/ca.ts +++ b/src/store/websites/ca.ts @@ -5,6 +5,8 @@ import websitesServ from '@/service/websites.ts' import { message, } from 'antd' import { t } from 'i18next' import { WebSite } from '@/types' +import { ISSLObtainByCA } from '@/types/website/ca' +import { sslListAtom } from '@/store/websites/ssl.ts' export const caPageAtom = atom({ page: 1, pageSize: 10, @@ -52,4 +54,17 @@ export const deleteCaAtom = atomWithMutation(get => ({ message.success(t('message.deleteSuccess', '删除成功')) get(caListAtom).refetch() } +})) + + +export const obtainSslAtom = atomWithMutation(get => ({ + mutationKey: [ 'obtainSsl' ], + mutationFn: async (data) => { + return await websitesServ.ca.obtainSsl(data) + }, + onSuccess: (res) => { + message.success(t('message.obtainSsl', '签发成功')) + get(sslListAtom).refetch() + return res + } })) \ No newline at end of file diff --git a/src/store/websites/ssl.ts b/src/store/websites/ssl.ts index 017923d..a020f73 100644 --- a/src/store/websites/ssl.ts +++ b/src/store/websites/ssl.ts @@ -12,6 +12,22 @@ export enum ProviderTypeEnum { Http = 'http' } + +export function getProvider(provider: string): string { + switch (provider) { + case 'dnsAccount': + return t('website.dnsAccount', 'DNS账号') + case 'dnsManual': + return t('website.dnsManual', '手动解析') + case 'http': + return 'HTTP' + case 'selfSigned': + return t('website.ssl.selfSigned', '自签证书') + default: + return t('website.ssl.manualCreate', '手动创建') + } +} + export enum KeyTypeEnum { EC256 = 'P256', EC384 = 'P384', @@ -26,8 +42,11 @@ export const KeyTypes = [ { label: 'RSA 2048', value: '2048' }, { label: 'RSA 3072', value: '3072' }, { label: 'RSA 4096', value: '4096' }, -]; +] +export const getKeyType = (key: string): string => { + return KeyTypes.find(item => item.value === key)?.label || '' +} export const sslPageAtom = atom({ page: 1, diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 0272155..b7e8cf3 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -8,7 +8,7 @@ export namespace System { export namespace WebSite { export { IAcmeAccount } from './website/acme' - export { ICA } from './website/ca' + export { ICA, ISSLObtainByCA } from './website/ca' export { IDnsAccount } from './website/dns' export { ISSL, ProviderType, SSLSearchParam } from './website/ssl' } \ No newline at end of file diff --git a/src/types/website/ca.d.ts b/src/types/website/ca.d.ts index fab3f47..b33148c 100644 --- a/src/types/website/ca.d.ts +++ b/src/types/website/ca.d.ts @@ -14,4 +14,17 @@ export interface ICA { city: string; private_key: string; key_type: string; +} + + +export interface ISSLObtainByCA { + id: number; + domains: string; + key_type: string; + time: number; + unit: string; + push_dir: boolean; + dir: string; + auto_renew: boolean; + description: string; } \ No newline at end of file