diff --git a/src/components/status/Status.tsx b/src/components/status/Status.tsx index 5415959..b54e873 100644 --- a/src/components/status/Status.tsx +++ b/src/components/status/Status.tsx @@ -3,57 +3,57 @@ import { useTranslation } from '@/i18n.ts' import { SyncOutlined } from '@ant-design/icons' export interface StatusProps extends TagProps { - status: string + status: string } const getColor = (status: string) => { - if (status.includes('error') || status.includes('err')) { - return 'danger' - } - switch (status) { - case 'running': - return 'success' - case 'stopped': - return 'danger' - case 'unhealthy': - case 'paused': - case 'exited': - case 'dead': - case 'removing': - return 'warning' - default: - return 'primary' - } + if (status.includes('error') || status.includes('err')) { + return 'danger' + } + switch (status) { + case 'running': + return 'success' + case 'stopped': + return 'danger' + case 'unhealthy': + case 'paused': + case 'exited': + case 'dead': + case 'removing': + return 'warning' + default: + return 'default' + } } const loadingStatus = [ - 'installing', - 'building', - 'restarting', - 'upgrading', - 'rebuilding', - 'recreating', - 'creating', - 'starting', - 'removing', - 'applying', + 'installing', + 'building', + 'restarting', + 'upgrading', + 'rebuilding', + 'recreating', + 'creating', + 'starting', + 'removing', + 'applying', ] const loadingIcon = (status: string): boolean => { - return loadingStatus.indexOf(status) > -1 + return loadingStatus.indexOf(status) > -1 } export const Status = ({ status = 'running', ...props }: StatusProps) => { - const { t } = useTranslation() - const icon = loadingIcon(status) ? : null - return ( - <> - {t(`status.${status}`, status)} - - ) + const { t } = useTranslation() + const icon = loadingIcon(status) ? : null + return ( + <> + {t(`status.${status}`, status)} + + ) } export default Status \ No newline at end of file diff --git a/src/pages/websites/ssl/acme/AcmeList.tsx b/src/pages/websites/ssl/acme/AcmeList.tsx index 1f685c0..1abf58b 100644 --- a/src/pages/websites/ssl/acme/AcmeList.tsx +++ b/src/pages/websites/ssl/acme/AcmeList.tsx @@ -1,171 +1,184 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components' import { useTranslation } from '@/i18n.ts' -import { AcmeAccountTypes, acmeListAtom, acmePageAtom, AcmeType, saveOrUpdateAcmeAtom } from '@/store/websites/acme.ts' +import { + AcmeAccountTypes, + acmeListAtom, + acmePageAtom, + AcmeType, + deleteAcmeAtom, + saveOrUpdateAcmeAtom +} from '@/store/websites/acme.ts' import { useAtom, useAtomValue } from 'jotai' import { Alert, Button, Form, Popconfirm } from 'antd' import { KeyTypeEnum, KeyTypes } from '@/store/websites/ssl.ts' -import { deleteDNSAtom } from '@/store/websites/dns.ts' import { WebSite } from '@/types' const AcmeList = () => { - const { t } = useTranslation() - const [ form ] = Form.useForm() - const [ page, setPage ] = useAtom(acmePageAtom) - const { data, isLoading, refetch } = useAtomValue(acmeListAtom) - const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateAcmeAtom) - const { mutate: deleteDNS, isPending: isDeleting } = useAtomValue(deleteDNSAtom) - const [ open, setOpen ] = useState(false) + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ page, setPage ] = useAtom(acmePageAtom) + const { data, isLoading, isFetching, refetch } = useAtomValue(acmeListAtom) + const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateAcmeAtom) + const { mutate: deleteAcme, isPending: isDeleting } = useAtomValue(deleteAcmeAtom) + const [ open, setOpen ] = useState(false) - const columns = useMemo[]>(() => { - return [ - { - title: 'ID', - dataIndex: 'id', - hideInTable: true, - formItemProps: { - hidden: true, - } - }, - { - title: t('website.ssl.acme.columns.email', '邮箱'), - dataIndex: 'email', - valueType: 'text', - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请输入') } - ] - } - }, - { - title: t('website.ssl.acme.columns.type', '帐号类型'), - dataIndex: 'type', - valueType: 'select', - fieldProps: { - options: AcmeAccountTypes - }, - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请选择') } - ] - } - }, - { - title: t('website.ssl.acme.columns.keyType', '密钥算法'), - dataIndex: 'keyType', - valueType: 'select', - fieldProps: { - options: KeyTypes - }, - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请选择') } - ] - }, - }, - { - title: t('website.ssl.acme.columns.url', 'URL'), - dataIndex: 'url', - valueType: 'text', - ellipsis: true, // 文本溢出省略 - hideInForm: true, - }, { - title: '操作', - valueType: 'option', - render: (_, record) => { - return [ - { - deleteDNS(record.id) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - } - ] - }, []) + const columns = useMemo[]>(() => { + return [ + { + title: 'ID', + dataIndex: 'id', + hideInTable: true, + formItemProps: { + hidden: true, + } + }, + { + title: t('website.ssl.acme.columns.email', '邮箱'), + dataIndex: 'email', + valueType: 'text', + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请输入') } + ] + } + }, + { + title: t('website.ssl.acme.columns.type', '帐号类型'), + dataIndex: 'type', + valueType: 'select', + fieldProps: { + options: AcmeAccountTypes + }, + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请选择') } + ] + } + }, + { + title: t('website.ssl.acme.columns.keyType', '密钥算法'), + dataIndex: 'key_type', + valueType: 'select', + fieldProps: { + options: KeyTypes + }, + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请选择') } + ] + }, + }, + { + title: t('website.ssl.acme.columns.url', 'URL'), + dataIndex: 'url', + valueType: 'text', + ellipsis: true, // 文本溢出省略 + hideInForm: true, + }, { + title: t('website.ssl.acme.columns.option', '操作'), + valueType: 'option', + fixed: 'right', + render: (_, record) => { + return [ + { + deleteAcme(record.id) + }} + title={t('message.deleteConfirm')}> + + {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, - } - }) - }, + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) - }} - /> - - shouldUpdate={false} - width={600} - form={form} - layout={'horizontal'} - scrollToFirstError={true} - title={t(`website.ssl.acme.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[]}/> - - ) + return ( + <> + + + cardProps={{ + bodyStyle: { + padding: 0, + } + }} + rowKey="id" + headerTitle={ + + } + loading={isLoading || isFetching} + 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.acme.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) + + }} + columns={columns as ProFormColumnsType[]}/> + + ) } export default AcmeList \ No newline at end of file diff --git a/src/pages/websites/ssl/ca/CAList.tsx b/src/pages/websites/ssl/ca/CAList.tsx index c503334..c185043 100644 --- a/src/pages/websites/ssl/ca/CAList.tsx +++ b/src/pages/websites/ssl/ca/CAList.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react' +import { useEffect, 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' @@ -13,233 +13,238 @@ import SelfSign from './SelfSign.tsx' 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 { mutate: deleteCA, isPending: isDeleting } = useAtomValue(deleteCaAtom) - const [ open, setOpen ] = useState(false) - const updateUI = useSetAtom(detailAtom) - const selfSignUI = useSetAtom(selfSignAtom) - 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.form.common_name', '证书主体名称(CN)'), - dataIndex: 'common_name', - hideInTable: true, - hideInSetting: true, - valueType: 'text', - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请输入') } - ] - } - }, + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ page, setPage ] = useAtom(caPageAtom) + const { data, isLoading, isFetching, refetch } = useAtomValue(caListAtom) + 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 [ + { + 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.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', '公司/组织'), + 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.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.columns.keyType', '密钥算法'), + dataIndex: 'key_type', + valueType: 'select', + fieldProps: { + options: KeyTypes + }, + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请选择') } + ] + }, + }, + { + title: t('website.ssl.ca.columns.createAt', '时间'), + dataIndex: 'created_at', + valueType: 'dateTime', + hideInForm: true, + }, + { + title: '操作', + valueType: 'option', + render: (_, record) => { + return [ + { + selfSignUI({ + open: true, + record: { + id: record.id, + key_type: KeyTypeEnum.EC256, + unit: 'year', + auto_renew: true, + time: 10, } - }, - { - 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: {} - }, + }) + }}>{t('website.actions.selfSign', '签发证书')}, + { + updateUI({ + open: true, record, + }) + }}>{t('website.actions.detail', '详情')}, - { - title: t('website.ssl.ca.columns.keyType', '密钥算法'), - dataIndex: 'key_type', - valueType: 'select', - fieldProps: { - options: KeyTypes - }, - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请选择') } - ] - }, - }, - { - title: t('website.ssl.ca.columns.createAt', '时间'), - dataIndex: 'created_at', - valueType: 'dateTime', - hideInForm: true, - }, - { - title: '操作', - 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', '详情')}, + { + deleteCA(record.id) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + + ] + } + } + ] + }, []) - { - deleteCA(record.id) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - } - ] - }, []) + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) - 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, - } - }) - }, + return ( + <> + + cardProps={{ + bodyStyle: { + padding: 0, + } + }} + rowKey="id" + headerTitle={ + + } + loading={isLoading || isFetching} + 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[]}/> - - - - ) + }} + /> + + 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) + }} + columns={columns as ProFormColumnsType[]}/> + + + + ) } export default CAList \ No newline at end of file diff --git a/src/pages/websites/ssl/dns/DNSList.tsx b/src/pages/websites/ssl/dns/DNSList.tsx index ef25654..08c020f 100644 --- a/src/pages/websites/ssl/dns/DNSList.tsx +++ b/src/pages/websites/ssl/dns/DNSList.tsx @@ -1,296 +1,301 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components' import { useTranslation } from '@/i18n.ts' import { useAtom, useAtomValue } from 'jotai' import { Button, Form, Popconfirm } from 'antd' import { - deleteDNSAtom, - dnsListAtom, - dnsPageAtom, - DNSTypeEnum, - DNSTypes, - saveOrUpdateDNSAtom + deleteDNSAtom, + dnsListAtom, + dnsPageAtom, + DNSTypeEnum, + DNSTypes, + saveOrUpdateDNSAtom } from '@/store/websites/dns.ts' import { WebSite } from '@/types' const getKeyColumn = (type: string, t) => { - const columns: ProColumns[] = [] - switch (type) { - case DNSTypeEnum.AliYun: { - columns.push(...[ - { - title: t('website.ssl.dns.columns.accessKey', 'Access Key'), - dataIndex: 'accessKey', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - { - title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), - dataIndex: 'secretKey', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - ]) - } - break - case DNSTypeEnum.TencentCloud: { - columns.push(...[ - { - title: t('website.ssl.dns.columns.secretID', 'Secret ID'), - dataIndex: 'secretID', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, { - title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), - dataIndex: 'secretKey', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - ]) - break - } - case DNSTypeEnum.DnsPod: { - columns.push(...[ - { - title: t('website.ssl.dns.columns.apiId', 'ID'), - dataIndex: 'apiId', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, { - title: t('website.ssl.dns.columns.token', 'Token'), - dataIndex: 'token', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - ]) - break - } - case DNSTypeEnum.CloudFlare: { - columns.push(...[ - { - title: t('website.ssl.dns.columns.email', 'Email'), - dataIndex: 'email', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, { - title: t('website.ssl.dns.columns.apiKey', 'API ToKen'), - dataIndex: 'apiKey', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - ] - ) - break - } - case DNSTypeEnum.Godaddy: - case DNSTypeEnum.NameCheap: - case DNSTypeEnum.NameSilo: - columns.push({ - title: t('website.ssl.dns.columns.apiKey', 'API Key'), - dataIndex: 'apiKey', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - ) - if (type === DNSTypeEnum.NameCheap) { - columns.push({ - title: t('website.ssl.dns.columns.apiUser', 'API User'), - dataIndex: 'apiUser', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }) - } else if (type === DNSTypeEnum.Godaddy) { - columns.push({ - title: t('website.ssl.dns.columns.apiSecret', 'API Secret'), - dataIndex: 'apiSecret', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }) + const columns: ProColumns[] = [] + switch (type) { + case DNSTypeEnum.AliYun: { + columns.push(...[ + { + title: t('website.ssl.dns.columns.accessKey', 'Access Key'), + dataIndex: 'accessKey', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + { + title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), + dataIndex: 'secretKey', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + ]) + } + break + case DNSTypeEnum.TencentCloud: { + columns.push(...[ + { + title: t('website.ssl.dns.columns.secretID', 'Secret ID'), + dataIndex: 'secretID', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, { + title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), + dataIndex: 'secretKey', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + ]) + break + } + case DNSTypeEnum.DnsPod: { + columns.push(...[ + { + title: t('website.ssl.dns.columns.apiId', 'ID'), + dataIndex: 'apiId', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, { + title: t('website.ssl.dns.columns.token', 'Token'), + dataIndex: 'token', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + ]) + break + } + case DNSTypeEnum.CloudFlare: { + columns.push(...[ + { + title: t('website.ssl.dns.columns.email', 'Email'), + dataIndex: 'email', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, { + title: t('website.ssl.dns.columns.apiKey', 'API ToKen'), + dataIndex: 'apiKey', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] } - break - case DNSTypeEnum.NameCom: { - columns.push( - { - title: t('website.ssl.dns.columns.apiUser', 'UserName'), - dataIndex: 'apiUser', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - }, - { - title: t('website.ssl.dns.columns.token', 'Token'), - dataIndex: 'token', - formItemProps: { - rules: [ { required: true, message: t('message.required') } ] - } - } - ) - break + }, + ] + ) + break + } + case DNSTypeEnum.Godaddy: + case DNSTypeEnum.NameCheap: + case DNSTypeEnum.NameSilo: + columns.push({ + title: t('website.ssl.dns.columns.apiKey', 'API Key'), + dataIndex: 'apiKey', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + ) + if (type === DNSTypeEnum.NameCheap) { + columns.push({ + title: t('website.ssl.dns.columns.apiUser', 'API User'), + dataIndex: 'apiUser', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }) + } else if (type === DNSTypeEnum.Godaddy) { + columns.push({ + title: t('website.ssl.dns.columns.apiSecret', 'API Secret'), + dataIndex: 'apiSecret', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }) + } + break + case DNSTypeEnum.NameCom: { + columns.push( + { + title: t('website.ssl.dns.columns.apiUser', 'UserName'), + dataIndex: 'apiUser', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } + }, + { + title: t('website.ssl.dns.columns.token', 'Token'), + dataIndex: 'token', + formItemProps: { + rules: [ { required: true, message: t('message.required') } ] + } } - default: - break - + ) + break } - return columns + default: + break + + } + return columns } const DNSList = () => { - const { t } = useTranslation() - const [ form ] = Form.useForm() - const [ page, setPage ] = useAtom(dnsPageAtom) - const { data, isLoading, refetch } = useAtomValue(dnsListAtom) - const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateDNSAtom) - const { mutate: deleteDNS, isPending: isDeleting } = useAtomValue(deleteDNSAtom) - const [ open, setOpen ] = useState(false) + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ page, setPage ] = useAtom(dnsPageAtom) + const { data, isLoading, isFetching, refetch } = useAtomValue(dnsListAtom) + const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateDNSAtom) + const { mutate: deleteDNS, isPending: isDeleting } = useAtomValue(deleteDNSAtom) + const [ open, setOpen ] = useState(false) - const columns = useMemo[]>(() => { - return [ - { - title: 'ID', - dataIndex: 'id', - hideInTable: true, - formItemProps: { - hidden: true, - } - }, - { - title: t('website.ssl.dns.columns.name', '名称'), - dataIndex: 'name', - valueType: 'text', - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请输入') } - ] - } - }, + const columns = useMemo[]>(() => { + return [ + { + title: 'ID', + dataIndex: 'id', + hideInTable: true, + formItemProps: { + hidden: true, + } + }, + { + title: t('website.ssl.dns.columns.name', '名称'), + dataIndex: 'name', + valueType: 'text', + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请输入') } + ] + } + }, - { - title: t('website.ssl.dns.columns.type', '类型'), - dataIndex: 'type', - valueType: 'select', - fieldProps: { - options: DNSTypes - }, - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请选择') } - ] - }, - }, - { - name: [ 'type' ], - valueType: 'dependency', - hideInSetting: true, - columns: ({ type }) => { - return getKeyColumn(type, t) - } - }, - { - title: '操作', - valueType: 'option', - render: (_, record) => { - return [ - { - deleteDNS(record.id) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - } - ] - }, []) + { + title: t('website.ssl.dns.columns.type', '类型'), + dataIndex: 'type', + valueType: 'select', + fieldProps: { + options: DNSTypes + }, + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请选择') } + ] + }, + }, + { + name: [ 'type' ], + valueType: 'dependency', + hideInSetting: true, + columns: ({ type }) => { + return getKeyColumn(type, t) + } + }, + { + title: '操作', + valueType: 'option', + render: (_, record) => { + return [ + { + deleteDNS(record.id) + }} + title={t('message.deleteConfirm')}> + + {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, - } - }) - }, + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) + + return ( + <> + + cardProps={{ + bodyStyle: { + padding: 0, + } + }} + rowKey="id" + headerTitle={ + + } + loading={isLoading || isFetching} + 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.dns.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? 'DNS帐号编辑' : 'DNS帐号添加')} - // 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[]}/> - - ) + }} + /> + + shouldUpdate={false} + width={600} + form={form} + layout={'horizontal'} + scrollToFirstError={true} + title={t(`website.ssl.dns.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? 'DNS帐号编辑' : 'DNS帐号添加')} + // 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) + }} + columns={columns as ProFormColumnsType[]}/> + + ) } export default DNSList \ No newline at end of file diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx index b9504ab..cc766a0 100644 --- a/src/pages/websites/ssl/index.tsx +++ b/src/pages/websites/ssl/index.tsx @@ -1,17 +1,17 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { - deleteSslAtom, getProvider, - KeyTypeEnum, - KeyTypes, - ProviderTypeEnum, - saveOrUpdateSslAtom, - sslListAtom, - sslPageAtom, - sslSearchAtom, uploadSslAtom + 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, ProTable } from '@ant-design/pro-components' -import { memo, useMemo, useRef, useState } from 'react' +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' @@ -35,403 +35,409 @@ import Download from '@/components/download/Download.tsx' 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 { 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 [] + 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, + } + }, + { + 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 [] + }, + { + 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) => [ + } + }, + { + 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'}> - - { + updateDetail({ + open: true, + record + }) + }} + > + {t('actions.detail', '详情')} + , + record.status !== 'manual'}> + + { + onClick={() => { - }} - > - {t('actions.apply', '申请')} - - - - { + }} + > + {t('actions.apply', '申请')} + + + + { - }} - > - {t('actions.update', '更新')} - - + }} + > + {t('actions.update', '更新')} + + - , - { - }} - > - {t('actions.download', '下载')} - , - { - deleteSSL(record.id) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - , - ], - }, - ] - }, []) + , + { + }} + > + {t('actions.download', '下载')} + , + { + deleteSSL(record.id) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + + , + ], + }, + ] + + }, [ acmeData, dnsData ]) - 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帐户')} - }> - - , - , - , - ] - }} - pagination={{ - pageSize: page?.pageSize ?? 10, - total: data?.total ?? 0, - current: page?.page ?? 1, - onChange: (page, pageSize) => { - setPage(prev => ({ - ...prev, - page, - pageSize, - })) - }, - }} - > + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) - - - 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) - return isSuccess - }} - columns={columns as ProFormColumnsType[]}/> - - - - - ]} - target={false} - > - - - - - ) + 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帐户')} + }> + + , + , + , + ] + }} + pagination={{ + pageSize: page?.pageSize ?? 10, + total: data?.total ?? 0, + current: page?.page ?? 1, + 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) \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index 7c0adc0..f334478 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -13,13 +13,13 @@ import { Route as LoginRouteImport } from '@/pages/login' import { generateUUID } from '@/utils/uuid.ts' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { - AnyRoute, - createRootRouteWithContext, - createRoute, - createRouter, lazyRouteComponent, - Outlet, - redirect, - RouterProvider, + AnyRoute, + createRootRouteWithContext, + createRoute, + createRouter, lazyRouteComponent, + Outlet, + redirect, + RouterProvider, } from '@tanstack/react-router' // import { TanStackRouterDevtools } from '@tanstack/router-devtools' import { memo, useEffect, useRef } from 'react' @@ -30,70 +30,70 @@ import { useAtomValue } from 'jotai' import { userMenuDataAtom } from '@/store/system/user.ts' const PageRootLayout = () => { - return - - + return + + } export const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - } + defaultOptions: { + queries: { + retry: false, } + } }) const rootRoute = createRootRouteWithContext()({ - component: () => ( - - <> - - - - {/**/} - - ), - beforeLoad: ({ location }) => { - if (location.pathname === '/') { - return redirect({ to: '/dashboard' }) - } - }, - loader: () => { + component: () => ( + + <> + + + + {/**/} + + ), + beforeLoad: ({ location }) => { + if (location.pathname === '/') { + return redirect({ to: '/dashboard' }) + } + }, + loader: () => { - }, - notFoundComponent: NotFound, - pendingComponent: PageLoading, - errorComponent: ({ error }) => , + }, + notFoundComponent: NotFound, + pendingComponent: PageLoading, + errorComponent: ({ error }) => , }) const emptyRoute = createRoute({ - getParentRoute: () => rootRoute, - id: '/_empty', - component: EmptyLayout, + getParentRoute: () => rootRoute, + id: '/_empty', + component: EmptyLayout, }) const authRoute = AuthenticatedImport.update({ - getParentRoute: () => rootRoute, - id: '/_authenticated', + getParentRoute: () => rootRoute, + id: '/_authenticated', } as any) const layoutNormalRoute = createRoute({ - getParentRoute: () => rootRoute, - id: '/_normal_layout', - component: PageRootLayout, + getParentRoute: () => rootRoute, + id: '/_normal_layout', + component: PageRootLayout, }) const layoutAuthRoute = createRoute({ - getParentRoute: () => authRoute, - id: '/_auth_layout', - component: PageRootLayout, + getParentRoute: () => authRoute, + id: '/_auth_layout', + component: PageRootLayout, }) const notAuthRoute = createRoute({ - getParentRoute: () => layoutNormalRoute, - path: '/not-auth', - component: NotPermission + getParentRoute: () => layoutNormalRoute, + path: '/not-auth', + component: NotPermission }) // const dashboardRoute = DashboardImport.update({ @@ -102,8 +102,8 @@ const notAuthRoute = createRoute({ // } as any) const loginRoute = LoginRouteImport.update({ - path: '/login', - getParentRoute: () => emptyRoute, + path: '/login', + getParentRoute: () => emptyRoute, } as any) // @@ -129,189 +129,191 @@ const loginRoute = LoginRouteImport.update({ declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/_authenticated': { - preLoaderRoute: typeof AuthenticatedImport - parentRoute: typeof rootRoute - }, - '/_normal_layout': { - preLoaderRoute: typeof layoutNormalRoute - parentRoute: typeof rootRoute - }, - '/_layout': { - preLoaderRoute: typeof layoutAuthRoute - parentRoute: typeof rootRoute - }, - // '/': { - // preLoaderRoute: typeof DashboardImport - // parentRoute: typeof layoutAuthRoute - // }, - // '/dashboard': { - // preLoaderRoute: typeof DashboardImport - // parentRoute: typeof layoutAuthRoute - // }, - '/login': { - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRoute - }, - // '/system/menus': { - // preLoaderRoute: typeof menusRoute - // parentRoute: typeof layoutAuthRoute - // }, - // '/system/departments': { - // preLoaderRoute: typeof departmentsRoute - // parentRoute: typeof layoutAuthRoute - // }, - // '/system/users': { - // preLoaderRoute: typeof usersRoute - // parentRoute: typeof layoutAuthRoute - // }, - // '/system/roles': { - // preLoaderRoute: typeof rolesRoute - // parentRoute: typeof layoutAuthRoute - // }, - '/welcome': { - preLoaderRoute: typeof rootRoute - parentRoute: typeof layoutAuthRoute - }, - } + interface FileRoutesByPath { + '/_authenticated': { + preLoaderRoute: typeof AuthenticatedImport + parentRoute: typeof rootRoute + }, + '/_normal_layout': { + preLoaderRoute: typeof layoutNormalRoute + parentRoute: typeof rootRoute + }, + '/_layout': { + preLoaderRoute: typeof layoutAuthRoute + parentRoute: typeof rootRoute + }, + // '/': { + // preLoaderRoute: typeof DashboardImport + // parentRoute: typeof layoutAuthRoute + // }, + // '/dashboard': { + // preLoaderRoute: typeof DashboardImport + // parentRoute: typeof layoutAuthRoute + // }, + '/login': { + preLoaderRoute: typeof LoginRouteImport + parentRoute: typeof rootRoute + }, + // '/system/menus': { + // preLoaderRoute: typeof menusRoute + // parentRoute: typeof layoutAuthRoute + // }, + // '/system/departments': { + // preLoaderRoute: typeof departmentsRoute + // parentRoute: typeof layoutAuthRoute + // }, + // '/system/users': { + // preLoaderRoute: typeof usersRoute + // parentRoute: typeof layoutAuthRoute + // }, + // '/system/roles': { + // preLoaderRoute: typeof rolesRoute + // parentRoute: typeof layoutAuthRoute + // }, + '/welcome': { + preLoaderRoute: typeof rootRoute + parentRoute: typeof layoutAuthRoute + }, + } } const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => { - // 递归生成路由,如果有routes则递归生成子路由 - - const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { - const path = menu.path?.replace(parentRoute.options?.path, '') - const isLayout = menu.children && menu.children.length > 0 && menu.type === 'menu' - - if (isLayout && (!menu.path || !menu.component)) { - //没有component的layout,直接返回 - return createRoute({ - getParentRoute: () => layoutAuthRoute, - id: `/layout-no-path-${generateUUID()}`, - component: EmptyLayout, - }) - } - - // @ts-ignore 添加menu属性,方便后面获取 - const options = { - getParentRoute: () => parentRoute, - menu, - } as any - - if (isLayout) { - options.id = path ?? `/layout-${generateUUID()}` - } else { - if (!path) { - console.log(`${menu.name}没有设置视图`) - } else { - options.path = path - } - } - - let component = menu.component - // menu.type - // 1,组件(页面),2,IFrame,3,外链接,4,按钮 - if (menu.type === 'iframe') { - component = '@/components/Iframe' - } - - //处理component路径 - component = component.replace(/^\/pages/, '') - component = component.replace(/^\//, '') + // 递归生成路由,如果有routes则递归生成子路由 + + const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { + const path = menu.path?.replace(parentRoute.options?.path, '') + const isLayout = menu.children && menu.children.length > 0 && menu.type === 'menu' + + if (isLayout && (!menu.path || !menu.component)) { + //没有component的layout,直接返回 + return createRoute({ + getParentRoute: () => layoutAuthRoute, + id: `/layout-no-path-${generateUUID()}`, + component: EmptyLayout, + }) + } - return createRoute({ - ...options, - component: lazyRouteComponent(() => import(`./pages/${component}`)), - notFoundComponent: NotFound, - }) + // @ts-ignore 添加menu属性,方便后面获取 + const options = { + getParentRoute: () => parentRoute, + menu, + } as any + + if (isLayout) { + options.id = path ?? `/layout-${generateUUID()}` + } else { + if (!path) { + console.log(`${menu.name}没有设置视图`) + } else { + options.path = path + } } - // 对menuData递归生成路由,只处理type =1 的菜单 - const did = (menus: MenuItem[], parentRoute: AnyRoute) => { - return menus.filter((item) => item.type === 'menu').map((item, index) => { - // 如果有children则递归生成子路由,同样只处理type =1 的菜单 - const route = generateRoutes(item, parentRoute) - - // console.log(route) - if (item.children && item.children.length > 0) { - const children = did(item.children, route) - if (children.length > 0) { - route.addChildren(children) - } - } - route.init({ originalIndex: index }) - return route - }) + let component = menu.component + // menu.type + // 1,组件(页面),2,IFrame,3,外链接,4,按钮 + if (menu.type === 'iframe') { + component = '@/components/Iframe' } - const routes = did(menuData, parentRoute) + //处理component路径 + component = component.replace(/^\/pages/, '') + component = component.replace(/^\//, '') + + return createRoute({ + ...options, + component: lazyRouteComponent(() => import(`./pages/${component}`)), + notFoundComponent: NotFound, + }) + } + + // 对menuData递归生成路由,只处理type =1 的菜单 + const did = (menus: MenuItem[], parentRoute: AnyRoute) => { + return menus.filter((item) => item.type === 'menu').map((item, index) => { + // 如果有children则递归生成子路由,同样只处理type =1 的菜单 + const route = generateRoutes(item, parentRoute) + + // console.log(route) + if (item.children && item.children.length > 0) { + const children = did(item.children, route) + if (children.length > 0) { + route.addChildren(children) + } + } + route.init({ originalIndex: index }) + return route + }) + } + + const routes = did(menuData, parentRoute) - parentRoute.addChildren(routes) + parentRoute.addChildren(routes) } const routeTree = rootRoute.addChildren( - [ - //非Layout - loginRoute, - emptyRoute, - - //不带权限Layout - layoutNormalRoute.addChildren([ - notAuthRoute, - ]), - - //带权限Layout - // dashboardRoute, - authRoute.addChildren( - [ - layoutAuthRoute - /*.addChildren( - [ - menusRoute, - departmentsRoute, - usersRoute, - rolesRoute, - ] - ),*/ - ]), - ] + [ + //非Layout + loginRoute, + emptyRoute, + + //不带权限Layout + layoutNormalRoute.addChildren([ + notAuthRoute, + ]), + + //带权限Layout + // dashboardRoute, + authRoute.addChildren( + [ + layoutAuthRoute + /*.addChildren( + [ + menusRoute, + departmentsRoute, + usersRoute, + rolesRoute, + ] + ),*/ + ]), + ] ) export const RootProvider = memo((props: { context: Partial }) => { - const { data: menuData, isLoading, refetch } = useAtomValue(userMenuDataAtom) + const { data: menuData, isLoading, refetch } = useAtomValue(userMenuDataAtom) - const isFetchRef = useRef(false) + const isFetchRef = useRef(false) - useEffect(() => { + useEffect(() => { - if (isFetchRef.current) { - return - } - isFetchRef.current = true - refetch() + if (isFetchRef.current) { + return + } + isFetchRef.current = true + refetch() - }, []) + }, []) - if (isLoading) { - return - } + if (isLoading) { + return + } - generateDynamicRoutes(menuData ?? [], layoutAuthRoute) + generateDynamicRoutes(menuData ?? [], layoutAuthRoute) + + const router = createRouter({ + routeTree, + context: { queryClient, menuData: [] }, + defaultPreload: 'intent', + defaultPendingComponent: () => + }) - const router = createRouter({ - routeTree, - context: { queryClient, menuData: [] }, - defaultPreload: 'intent', - defaultPendingComponent: () => - }) + return ( + + + + ) +}) - return ( - - - - ) -}) \ No newline at end of file +export default RootProvider \ No newline at end of file diff --git a/src/store/websites/acme.ts b/src/store/websites/acme.ts index 9ccf02a..96a80e0 100644 --- a/src/store/websites/acme.ts +++ b/src/store/websites/acme.ts @@ -7,65 +7,65 @@ import { atom } from 'jotai' import { WebSite } from '@/types' export enum AcmeType { - LetsEncrypt = 'LetsEncrypt', - //zerossl - ZeroSSl = 'ZeroSSl', - //buypass - Buypass = 'Buypass', - //google - Google = 'Google', + LetsEncrypt = 'LetsEncrypt', + //zerossl + ZeroSSl = 'ZeroSsl', + //buypass + Buypass = 'Buypass', + //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 }, + { 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 + return AcmeAccountTypes.find(item => item.value === type)?.label } export const acmePageAtom = atom({ - page: 1, pageSize: 10, + page: 1, pageSize: 10, }) //list export const acmeListAtom = atomWithQuery(get => ({ - queryKey: [ 'acmeList', get(acmePageAtom) ], - queryFn: async ({ queryKey: [ , page ] }) => { - return await websitesServ.acme.list(page) - }, - select: (data) => { - return data.data - } + queryKey: [ 'acmeList', get(acmePageAtom) ], + queryFn: async ({ queryKey: [ , page ] }) => { + return await websitesServ.acme.list(page) + }, + select: (data) => { + return data.data + } })) //saveOrUpdate -export const saveOrUpdateAcmeAtom = atomWithMutation(get => ({ - mutationKey: [ 'saveOrUpdateAcme' ], - mutationFn: async (data: WebSite.IAcmeAccount) => { - if (data.id > 0) { - return await websitesServ.acme.update(data) - } - return await websitesServ.acme.add(data) - }, - onSuccess: (res) => { - const isAdd = !!res.data?.id - message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) - get(acmeListAtom).refetch() - return res +export const saveOrUpdateAcmeAtom = atomWithMutation(get => ({ + mutationKey: [ 'saveOrUpdateAcme' ], + mutationFn: async (data: WebSite.IAcmeAccount) => { + if (data.id > 0) { + return await websitesServ.acme.update(data) } + return await websitesServ.acme.add(data) + }, + onSuccess: (res) => { + const isAdd = !!res.data?.id + message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) + get(acmeListAtom).refetch() + 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() - } + 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