import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { deleteSslAtom, getProvider, KeyTypeEnum, KeyTypes, ProviderTypeEnum, saveOrUpdateSslAtom, sslListAtom, sslPageAtom, sslSearchAtom, uploadSslAtom } from '@/store/websites/ssl.ts' import ListPageLayout from '@/layout/ListPageLayout.tsx' import { BetaSchemaForm, ProColumns, ProFormColumnsType } from '@ant-design/pro-components' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from '@/i18n.ts' import { Button, Form, Popconfirm, Space } from 'antd' import { PlusOutlined } from '@ant-design/icons' import DrawerPicker, { DrawerPickerRef } from '@/components/drawer-picker/DrawerPicker.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 './dns/DNSList.tsx' import CAList from './ca/CAList.tsx' import { WebSite } from '@/types' import Switch from '@/components/switch' import { Else, If, Then } from 'react-if' import Action from '@/components/action/Action.tsx' import { Status } from '@/components/status' import SSLDetail from './components/Detail.tsx' import { detailAtom } from './components/store.ts' import Upload from './components/Upload.tsx' import { FormInstance } from 'antd/lib' import Download from '@/components/download/Download.tsx' import { Table as ProTable } from '@/components/table' const SSL = () => { const { t } = useTranslation() const [ form ] = Form.useForm() const uploadFormRef = useRef() 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) const { mutate: deleteSSL, isPending: isDeleting } = useAtomValue(deleteSslAtom) const { mutate: uploadSSL, isSuccess: isUploadSuccess, isPending: isUploading } = useAtomValue(uploadSslAtom) const updateDetail = useSetAtom(detailAtom) const uploadDrawerRef = useRef() const [ open, setOpen ] = useState(false) const columns = useMemo[]>(() => { return [ { title: 'ID', dataIndex: 'id', hideInTable: true, hideInSearch: false, formItemProps: { hidden: true, } }, { title: t('website.ssl.columns.primaryDomain', '域名'), dataIndex: 'primary_domain', formItemProps: { label: t('website.ssl.form.primaryDomain', '主域名'), rules: [ { required: true, message: t('message.required', '主域名') } ] } }, { title: t('website.ssl.columns.otherDomains', '其它域名'), dataIndex: 'domains', }, { title: t('website.ssl.columns.acmeAccountId', 'Acme帐号'), dataIndex: 'acme_account_id', 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.status', '状态'), dataIndex: 'status', render: (_, record) => { return }, hideInForm: true, }, { title: t('website.ssl.columns.keyType', '密钥算法'), dataIndex: 'key_type', hideInTable: true, valueType: 'select', fieldProps: { options: KeyTypes }, formItemProps: { rules: [ { required: true, message: t('message.required', '请选择') } ] }, }, { title: t('website.ssl.columns.provider', '申请方式'), dataIndex: 'provider', valueType: 'radio', valueEnum: { [ProviderTypeEnum.DnsAccount]: { text: t('website.ssl.providerTypeEnum.DnsAccount', 'DNS帐号'), }, [ProviderTypeEnum.DnsManual]: { text: t('website.ssl.providerTypeEnum.DnsManual', '手动验证'), }, [ProviderTypeEnum.Http]: { text: t('website.ssl.providerTypeEnum.Http', 'HTTP'), } }, dependencies: [ 'provider' ], renderText: (text) => { return getProvider(text) }, formItemProps: (form, config) => { const val = form.getFieldValue(config.dataIndex) const help = { [ProviderTypeEnum.DnsAccount]: t('website.ssl.form.provider_{{v}}', '', { v: val }), [ProviderTypeEnum.DnsManual]: t('website.ssl.form.provider_{{v}}', '手动解析模式需要在创建完之后点击申请按钮获取 DNS 解析值', { v: val }), [ProviderTypeEnum.Http]: t('website.ssl.form.provider_{{v}}', 'HTTP 模式需要安装 OpenResty
HTTP 模式无法申请泛域名证书', { v: val }), } return { label: t('website.ssl.form.provider', '验证方式'), help: , rules: [ { required: true, message: t('message.required', '请选择') } ] } }, }, { name: [ 'provider' ], valueType: 'dependency', hideInSetting: true, hideInTable: true, columns: ({ provider }) => { if (provider === ProviderTypeEnum.DnsAccount) { return [ { title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'), dataIndex: 'dns_account_id', 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 })) }, } ] } return [] } }, { title: t('website.ssl.columns.autoRenew', '自动续签'), dataIndex: 'auto_renew', valueType: 'switch', render: (_, record) => { return } }, { title: t('website.ssl.columns.pushDir', '推送证书到本地目录'), dataIndex: 'push_dir', valueType: 'switch', hideInTable: true, hideInSearch: true, }, { name: [ 'push_dir' ], valueType: 'dependency', hideInSetting: true, hideInTable: true, columns: ({ pushDir }) => { if (pushDir) { return [ { title: t('website.ssl.columns.dir', '目录'), dataIndex: 'dir', formItemProps: { help: t('website.ssl.form.dir_help', '会在此目录下生成两个文件,证书文件:fullchain.pem 密钥文件:privkey.pem'), rules: [ { required: true, message: t('message.required', '请输入目录') } ] } } ] } return [] } }, { title: t('website.ssl.columns.description', '备注'), dataIndex: 'description', }, { title: t('website.ssl.columns.expire_date', '过期时间'), dataIndex: 'expire_date', valueType: 'dateTime', hideInForm: true, }, { title: t('website.ssl.columns.option', '操作'), valueType: 'option', key: 'option', fixed: 'right', width: 300, render: (_, record) => [ { updateDetail({ open: true, record }) }} > {t('actions.detail', '详情')} , record.status !== 'manual'}> { }} > {t('actions.apply', '申请')} { }} > {t('actions.update', '更新')} , { }} > {t('actions.download', '下载')} , { deleteSSL(record.id) }} title={t('message.deleteConfirm')}> {t('actions.delete', '删除')} , ], }, ] }, [ acmeData, dnsData ]) useEffect(() => { if (isSuccess) { setOpen(false) } }, [ isSuccess ]) return ( headerTitle={t('website.ssl.title', '证书列表')} search={false} loading={isLoading || isFetching} rowKey={'id'} dataSource={data?.rows ?? []} columns={columns} columnsState={{ defaultValue: { option: { fixed: 'right', disable: true }, }, }} options={{ reload: () => { refetch() }, }} toolbar={{ search: { loading: isFetching && !!search.key, onSearch: (value: string) => { setSearch({ key: value }) }, placeholder: t('website.ssl.search.placeholder', '输入域名') }, actions: [ {t('website.ssl.actions.selfSigned', '自签证书')} } > , {t('website.ssl.actions.acme', 'Acme帐户')} } > , {t('website.ssl.actions.dns', 'DNS帐户')} }> , , , ] }} scroll={{}} pagination={{ pageSize: page?.pageSize ?? 10, total: data?.total ?? 0, current: page?.page ?? 1, onShowSizeChange: (current: number, size: number) => { setPage({ ...page, pageSize: size, page: current }) }, onChange: (page, pageSize) => { setPage(prev => ({ ...prev, page, pageSize, })) }, }} > shouldUpdate={false} width={600} form={form} layout={'vertical'} scrollToFirstError={true} title={t(`website.ssl.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '证书编辑' : '证书添加')} // colProps={{ span: 24 }} labelCol={{ span: 12 }} wrapperCol={{ span: 24 }} layoutType={'DrawerForm'} open={open} drawerProps={{ maskClosable: false, }} onOpenChange={(open) => { setOpen(open) }} loading={isSubmitting} onFinish={async (values) => { // console.log('values', values) saveOrUpdate(values) }} columns={columns as ProFormColumnsType[]}/> ]} target={false} > ) } export default memo(SSL)