From e361e75cc036bd1edcab70d8eb339397bed7b3b1 Mon Sep 17 00:00:00 2001 From: dark Date: Sat, 29 Jun 2024 17:58:35 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=8F=9C=E5=8D=95=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E5=8A=9F=E8=83=BD=202=E3=80=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4WebSite=E6=A8=A1=E5=9D=97=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/RootLayout.tsx | 6 +- src/pages/system/menus/index.tsx | 29 ++- src/pages/system/menus/style.ts | 8 +- src/pages/websites/account/index.tsx | 300 +++++++++++++++++++++++++++++ src/pages/websites/account/style.ts | 26 +++ src/pages/websites/acme/AcmeList.tsx | 184 ++++++++++++++++++ src/pages/websites/ca/CAList.tsx | 250 ++++++++++++++++++++++++ src/pages/websites/ca/Detail.tsx | 125 ++++++++++++ src/pages/websites/ca/SelfSign.tsx | 136 +++++++++++++ src/pages/websites/ca/store.ts | 20 ++ src/pages/websites/ca/style.ts | 23 +++ src/pages/websites/dns/DNSList.tsx | 318 +++++++++++++++++++++++++++++++ src/pages/websites/domain/index.tsx | 298 +++++++++++++++++++++++++++++ src/pages/websites/domain/style.ts | 26 +++ src/pages/websites/record/index.tsx | 318 +++++++++++++++++++++++++++++++ src/pages/websites/record/style.ts | 26 +++ src/pages/websites/ssl/account/index.tsx | 300 ----------------------------- src/pages/websites/ssl/account/style.ts | 26 --- src/pages/websites/ssl/acme/AcmeList.tsx | 184 ------------------ src/pages/websites/ssl/ca/CAList.tsx | 250 ------------------------ src/pages/websites/ssl/ca/Detail.tsx | 125 ------------ src/pages/websites/ssl/ca/SelfSign.tsx | 136 ------------- src/pages/websites/ssl/ca/store.ts | 20 -- src/pages/websites/ssl/ca/style.ts | 23 --- src/pages/websites/ssl/dns/DNSList.tsx | 318 ------------------------------- src/pages/websites/ssl/domain/index.tsx | 298 ----------------------------- src/pages/websites/ssl/domain/style.ts | 26 --- src/pages/websites/ssl/index.tsx | 12 +- src/pages/websites/ssl/record/index.tsx | 318 ------------------------------- src/pages/websites/ssl/record/style.ts | 26 --- src/service/websites.ts | 2 +- src/types/system/menus.d.ts | 51 ++--- 32 files changed, 2120 insertions(+), 2088 deletions(-) create mode 100644 src/pages/websites/account/index.tsx create mode 100644 src/pages/websites/account/style.ts create mode 100644 src/pages/websites/acme/AcmeList.tsx create mode 100644 src/pages/websites/ca/CAList.tsx create mode 100644 src/pages/websites/ca/Detail.tsx create mode 100644 src/pages/websites/ca/SelfSign.tsx create mode 100644 src/pages/websites/ca/store.ts create mode 100644 src/pages/websites/ca/style.ts create mode 100644 src/pages/websites/dns/DNSList.tsx create mode 100644 src/pages/websites/domain/index.tsx create mode 100644 src/pages/websites/domain/style.ts create mode 100644 src/pages/websites/record/index.tsx create mode 100644 src/pages/websites/record/style.ts delete mode 100644 src/pages/websites/ssl/account/index.tsx delete mode 100644 src/pages/websites/ssl/account/style.ts delete mode 100644 src/pages/websites/ssl/acme/AcmeList.tsx delete mode 100644 src/pages/websites/ssl/ca/CAList.tsx delete mode 100644 src/pages/websites/ssl/ca/Detail.tsx delete mode 100644 src/pages/websites/ssl/ca/SelfSign.tsx delete mode 100644 src/pages/websites/ssl/ca/store.ts delete mode 100644 src/pages/websites/ssl/ca/style.ts delete mode 100644 src/pages/websites/ssl/dns/DNSList.tsx delete mode 100644 src/pages/websites/ssl/domain/index.tsx delete mode 100644 src/pages/websites/ssl/domain/style.ts delete mode 100644 src/pages/websites/ssl/record/index.tsx delete mode 100644 src/pages/websites/ssl/record/style.ts diff --git a/src/layout/RootLayout.tsx b/src/layout/RootLayout.tsx index 28f312e..8743384 100644 --- a/src/layout/RootLayout.tsx +++ b/src/layout/RootLayout.tsx @@ -26,7 +26,7 @@ const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => { const findItem = (menuData: any[], pathname: string) => { for (let i = 0; i < menuData.length; i++) { if (menuData[i].path === pathname) { - menuData[i].label ={ menuData[i].name} + menuData[i].label = {menuData[i].name} breadcrumbData.push(menuData[i]) return true } @@ -56,7 +56,7 @@ export default () => { const menusFlatten = useRef() if (!menusFlatten.current) { - menusFlatten.current = flattenTree(menuData, { key: 'id', title: 'name' }) + menusFlatten.current = flattenTree(menuData, { key: 'id', title: 'name' }).filter(item => !item.hidden) } const [ rootMenuKeys, setRootMenuKeys ] = useState(() => { const item = menusFlatten.current?.find(item => item.path === location.pathname) @@ -127,7 +127,7 @@ export default () => { collapsedButtonRender={false} // collapsed={false} postMenuData={() => { - return menuData.map(item => ({ + return menuData.filter(item=>!item.hidden).map(item => ({ ...item, children: [], })) as any diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index e81e931..62c83b6 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -2,7 +2,19 @@ import Glass from '@/components/glass' import { useTranslation } from '@/i18n.ts' import { PlusOutlined } from '@ant-design/icons' import { ProCard } from '@ant-design/pro-components' -import { Button, Form, Input, Radio, TreeSelect, InputNumber, notification, Alert, InputRef, Divider } from 'antd' +import { + Button, + Form, + Input, + Radio, + TreeSelect, + InputNumber, + notification, + Alert, + InputRef, + Divider, + Checkbox +} from 'antd' import { useAtom, useAtomValue } from 'jotai' import { defaultMenu, menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from '@/store/system/menu.ts' import IconPicker from '@/components/icon/picker' @@ -45,7 +57,9 @@ const Menus = () => { - @@ -172,12 +186,23 @@ const Menus = () => { > + + + 隐藏菜单 + + + 隐藏面包屑 + + + ] + }} + scroll={{ + x: 2500, y: 'calc(100vh - 290px)' + }} + search={false} + onRow={(record) => { + return { + className: cx({ + 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id + }), + onClick: () => { + setWebsiteDnsAccount(record) + } + } + }} + dateFormatter="string" + loading={isLoading || isFetching} + dataSource={data?.rows ?? []} + columns={columns} + options={{ + reload: () => { + refetch() + }, + }} + pagination={{ + total: data?.total, + pageSize: search.pageSize, + current: search.page, + onShowSizeChange: (current: number, size: number) => { + setSearch({ + ...search, + pageSize: size, + page: current + }) + }, + onChange: (current, pageSize) => { + setSearch(prev => { + return { + ...prev, + page: current, + pageSize: pageSize, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + // onValuesChange={(values) => { + // + // }} + onFinish={async (values) => { + saveOrUpdate(values) + }} + columns={columns as ProFormColumnsType[]}/> + { + setFilterOpen(open) + }} + layout={'vertical'} + scrollToFirstError={true} + layoutType={'DrawerForm'} + drawerProps={{ + maskClosable: false, + mask: false, + }} + submitter={{ + searchConfig: { + resetText: t(`${i18nPrefix}.filter.reset`, '清空'), + submitText: t(`${i18nPrefix}.filter.submit`, '查询'), + }, + onReset: () => { + filterForm.resetFields() + }, + render: (props,) => { + return ( +
+ + + + +
+ ) + }, + + }} + // onValuesChange={(values) => { + // + // }} + + onFinish={async (values) => { + //处理,变成数组 + Object.keys(values).forEach(key => { + if (typeof values[key] === 'string' && values[key].includes(',')) { + values[key] = values[key].split(',') + } + }) + + setSearch(values) + + }} + columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> + + ) +} + +export default WebsiteDnsAccount \ No newline at end of file diff --git a/src/pages/websites/account/style.ts b/src/pages/websites/account/style.ts new file mode 100644 index 0000000..022cbff --- /dev/null +++ b/src/pages/websites/account/style.ts @@ -0,0 +1,26 @@ +import { createStyles } from '../../../theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsAccount-list-page` + + const container = css` + .ant-table-cell{ + .ant-tag{ + padding-inline: 3px; + margin-inline-end: 3px; + } + } + .ant-table-empty { + .ant-table-body{ + height: calc(100vh - 350px) + } + } + .ant-pro-table-highlight{ + + } + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/pages/websites/acme/AcmeList.tsx b/src/pages/websites/acme/AcmeList.tsx new file mode 100644 index 0000000..1abf58b --- /dev/null +++ b/src/pages/websites/acme/AcmeList.tsx @@ -0,0 +1,184 @@ +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, + 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 { WebSite } from '@/types' + +const AcmeList = () => { + + 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: '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', '删除')} + + + ] + } + } + ] + }, []) + + 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.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/ca/CAList.tsx b/src/pages/websites/ca/CAList.tsx new file mode 100644 index 0000000..c185043 --- /dev/null +++ b/src/pages/websites/ca/CAList.tsx @@ -0,0 +1,250 @@ +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' +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 = () => { + + 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_uint', '部门'), + dataIndex: 'organization_uint', + hideInTable: true, + hideInSetting: true, + valueType: 'text', + formItemProps: {} + }, + { + title: t('website.ssl.ca.form.country', '国家代号'), + dataIndex: 'country', + hideInTable: true, + hideInSetting: true, + valueType: 'text', + formItemProps: { + rules: [ + { required: true, message: t('message.required', '请输入') } + ] + } + }, + { + title: t('website.ssl.ca.form.province', '省份'), + dataIndex: 'province', + hideInTable: true, + hideInSetting: true, + valueType: 'text', + formItemProps: {} + }, + { + title: t('website.ssl.ca.form.city', '城市'), + dataIndex: 'city', + hideInTable: true, + hideInSetting: true, + valueType: 'text', + formItemProps: {} + }, + + { + title: t('website.ssl.ca.columns.keyType', '密钥算法'), + 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', '删除')} + + + ] + } + } + ] + }, []) + + 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.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/ca/Detail.tsx b/src/pages/websites/ca/Detail.tsx new file mode 100644 index 0000000..7cbf23b --- /dev/null +++ b/src/pages/websites/ca/Detail.tsx @@ -0,0 +1,125 @@ +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}.csr`, 'csr'), 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 ]) + + useEffect(() => { + return () => { + setUI({ + open: false, + record: {}, + }) + } + }, []) + + 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/ca/SelfSign.tsx b/src/pages/websites/ca/SelfSign.tsx new file mode 100644 index 0000000..6e9c0c1 --- /dev/null +++ b/src/pages/websites/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/ca/store.ts b/src/pages/websites/ca/store.ts new file mode 100644 index 0000000..bc690fe --- /dev/null +++ b/src/pages/websites/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/ca/style.ts b/src/pages/websites/ca/style.ts new file mode 100644 index 0000000..cf59121 --- /dev/null +++ b/src/pages/websites/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/dns/DNSList.tsx b/src/pages/websites/dns/DNSList.tsx new file mode 100644 index 0000000..2bb95d3 --- /dev/null +++ b/src/pages/websites/dns/DNSList.tsx @@ -0,0 +1,318 @@ +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 Action from '@/components/action/Action.tsx' +import { + deleteDNSAtom, + dnsListAtom, + dnsPageAtom, + DNSTypeEnum, + DNSTypes, + saveOrUpdateDNSAtom, syncDNSAtom +} 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') } ] + } + }) + } + 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 + } + default: + break + + } + return columns +} + +const DNSList = () => { + + 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 { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom) + 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', '请输入') } + ] + } + }, + + { + 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, + hideInTable: true, + columns: ({ type }) => { + return getKeyColumn(type, t) + } + }, + { + title: '操作', + valueType: 'option', + render: (_, record) => { + return [ + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.edit')}, + { + asyncDNS(record) + }}>{t('actions.sync', '同步')}, + { + deleteDNS(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 || 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) + }} + columns={columns as ProFormColumnsType[]}/> + + ) +} + +export default DNSList \ No newline at end of file diff --git a/src/pages/websites/domain/index.tsx b/src/pages/websites/domain/index.tsx new file mode 100644 index 0000000..181c1ae --- /dev/null +++ b/src/pages/websites/domain/index.tsx @@ -0,0 +1,298 @@ +import { useTranslation } from '@/i18n.ts' +import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' +import { useAtom, useAtomValue } from 'jotai' +import { + deleteWebsiteDomainAtom, + saveOrUpdateWebsiteDomainAtom, websiteDomainAtom, websiteDomainsAtom, websiteDomainSearchAtom, +} from '@/store/websites/domain' +import { useEffect, useMemo, useState } from 'react' +import Action from '@/components/action/Action.tsx' +import { + BetaSchemaForm, + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components' +import ListPageLayout from '@/layout/ListPageLayout.tsx' +import { useStyle } from './style' +import { FilterOutlined } from '@ant-design/icons' +import { getValueCount } from '@/utils' +import { Table as ProTable } from '@/components/table' + +const i18nPrefix = 'websiteDomains.list' + +const WebsiteDomain = () => { + + const { styles, cx } = useStyle() + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ filterForm ] = Form.useForm() + const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom) + const [ search, setSearch ] = useAtom(websiteDomainSearchAtom) + const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom) + const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom) + const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom) + + const [ open, setOpen ] = useState(false) + const [ openFilter, setFilterOpen ] = useState(false) + const [ searchKey, setSearchKey ] = useState(search?.title) + + const columns = useMemo(() => { + return [ + { + title: 'ID', + dataIndex: 'id', + hideInTable: true, + hideInSearch: true, + formItemProps: { hidden: true } + }, + { + title: t(`${i18nPrefix}.columns.name`, 'name'), + dataIndex: 'name', + }, + + { + title: t(`${i18nPrefix}.columns.dns_account_id`, 'dns_account_id'), + dataIndex: 'dns_account_id', + }, + { + title: t(`${i18nPrefix}.columns.status`, 'status'), + dataIndex: 'status', + }, + { + title: t(`${i18nPrefix}.columns.created`, 'created'), + dataIndex: 'created', + }, + { + title: t(`${i18nPrefix}.columns.modified`, 'modified'), + dataIndex: 'modified', + }, + + { + title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'), + dataIndex: 'nameservers', + }, + + { + title: t(`${i18nPrefix}.columns.tag`, 'tag'), + dataIndex: 'tag', + }, + + { + title: t(`${i18nPrefix}.columns.remark`, 'remark'), + dataIndex: 'remark', + }, + + { + title: t(`${i18nPrefix}.columns.option`, '操作'), + key: 'option', + valueType: 'option', + fixed: 'right', + render: (_, record) => [ + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.edit')}, + { + deleteWebsiteDomain([ record.id ]) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + + ] + } + ] as ProColumns[] + }, [ isDeleting, currentWebsiteDomain, search ]) + + useEffect(() => { + + setSearchKey(search?.title) + filterForm.setFieldsValue(search) + + }, [ search ]) + + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) + + return ( + + { + setSearch(prev => ({ + ...prev, + title: value + })) + }, + allowClear: true, + onChange: (e) => { + setSearchKey(e.target?.value) + }, + value: searchKey, + placeholder: t(`${i18nPrefix}.placeholder`, '输入域名管理名称') + }, + actions: [ + + + + ] + }} + scroll={{ + x: 2500, y: 'calc(100vh - 290px)' + }} + search={false} + onRow={(record) => { + return { + className: cx({ + 'ant-table-row-selected': currentWebsiteDomain?.id === record.id + }), + onClick: () => { + setWebsiteDomain(record) + } + } + }} + dateFormatter="string" + loading={isLoading || isFetching} + dataSource={data?.rows ?? []} + columns={columns} + options={{ + reload: () => { + refetch() + }, + }} + pagination={{ + total: data?.total, + pageSize: search.pageSize, + current: search.page, + onShowSizeChange: (current: number, size: number) => { + setSearch({ + ...search, + pageSize: size, + page: current + }) + }, + onChange: (current, pageSize) => { + setSearch(prev => { + return { + ...prev, + page: current, + pageSize: pageSize, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + + onFinish={async (values) => { + saveOrUpdate(values) + }} + columns={columns as ProFormColumnsType[]}/> + { + setFilterOpen(open) + }} + layout={'vertical'} + scrollToFirstError={true} + layoutType={'DrawerForm'} + drawerProps={{ + maskClosable: false, + mask: false, + }} + submitter={{ + searchConfig: { + resetText: t(`${i18nPrefix}.filter.reset`, '清空'), + submitText: t(`${i18nPrefix}.filter.submit`, '查询'), + }, + onReset: () => { + filterForm.resetFields() + }, + render: (props,) => { + return ( +
+ + + + +
+ ) + }, + + }} + + + onFinish={async (values) => { + //处理,变成数组 + Object.keys(values).forEach(key => { + if (typeof values[key] === 'string' && values[key].includes(',')) { + values[key] = values[key].split(',') + } + }) + + setSearch(values) + + }} + columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> +
+ ) +} + +export default WebsiteDomain \ No newline at end of file diff --git a/src/pages/websites/domain/style.ts b/src/pages/websites/domain/style.ts new file mode 100644 index 0000000..e1d04b4 --- /dev/null +++ b/src/pages/websites/domain/style.ts @@ -0,0 +1,26 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-websiteDomain-list-page` + + const container = css` + .ant-table-cell{ + .ant-tag{ + padding-inline: 3px; + margin-inline-end: 3px; + } + } + .ant-table-empty { + .ant-table-body{ + height: calc(100vh - 350px) + } + } + .ant-pro-table-highlight{ + + } + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/pages/websites/record/index.tsx b/src/pages/websites/record/index.tsx new file mode 100644 index 0000000..13c2d10 --- /dev/null +++ b/src/pages/websites/record/index.tsx @@ -0,0 +1,318 @@ +import { useTranslation } from '@/i18n.ts' +import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' +import { useAtom, useAtomValue } from 'jotai' +import { + deleteWebsiteDnsRecordsAtom, + saveOrUpdateWebsiteDnsRecordsAtom, websiteDnsRecordsAtom, websiteDnsRecordssAtom, websiteDnsRecordsSearchAtom, +} from '@/store/websites/record' +import { useEffect, useMemo, useState } from 'react' +import Action from '@/components/action/Action.tsx' +import { + BetaSchemaForm, + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components' +import ListPageLayout from '@/layout/ListPageLayout.tsx' +import { useStyle } from './style' +import { FilterOutlined } from '@ant-design/icons' +import { getValueCount } from '@/utils' +import { Table as ProTable } from '@/components/table' + +const i18nPrefix = 'websiteDnsRecordss.list' + +const WebsiteDnsRecords = () => { + + const { styles, cx } = useStyle() + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ filterForm ] = Form.useForm() + const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsRecordsAtom) + const [ search, setSearch ] = useAtom(websiteDnsRecordsSearchAtom) + const [ currentWebsiteDnsRecords, setWebsiteDnsRecords ] = useAtom(websiteDnsRecordsAtom) + const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsRecordssAtom) + const { mutate: deleteWebsiteDnsRecords, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsRecordsAtom) + + const [ open, setOpen ] = useState(false) + const [ openFilter, setFilterOpen ] = useState(false) + const [ searchKey, setSearchKey ] = useState(search?.title) + + const columns = useMemo(() => { + return [ + { + title: 'ID', + dataIndex: 'id', + hideInTable: true, + hideInSearch: true, + formItemProps: { hidden: true } + }, + { + title: t(`${i18nPrefix}.columns.record_id`, 'record_id'), + dataIndex: 'record_id', + }, + + { + title: t(`${i18nPrefix}.columns.domain_id`, 'domain_id'), + dataIndex: 'domain_id', + }, + + { + title: t(`${i18nPrefix}.columns.name`, 'name'), + dataIndex: 'name', + }, + + { + title: t(`${i18nPrefix}.columns.content`, 'content'), + dataIndex: 'content', + }, + + { + title: t(`${i18nPrefix}.columns.poxy`, 'poxy'), + dataIndex: 'poxy', + }, + + { + title: t(`${i18nPrefix}.columns.ttl`, 'ttl'), + dataIndex: 'ttl', + }, + + { + title: t(`${i18nPrefix}.columns.type`, 'type'), + dataIndex: 'type', + }, + + { + title: t(`${i18nPrefix}.columns.status`, 'status'), + dataIndex: 'status', + }, + + { + title: t(`${i18nPrefix}.columns.tag`, 'tag'), + dataIndex: 'tag', + }, + + { + title: t(`${i18nPrefix}.columns.remark`, 'remark'), + dataIndex: 'remark', + }, + + { + title: t(`${i18nPrefix}.columns.created`, 'created'), + dataIndex: 'created', + }, + + { + title: t(`${i18nPrefix}.columns.modified`, 'modified'), + dataIndex: 'modified', + }, + + { + title: t(`${i18nPrefix}.columns.option`, '操作'), + key: 'option', + valueType: 'option', + fixed: 'right', + render: (_, record) => [ + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.edit')}, + { + deleteWebsiteDnsRecords([ record.id ]) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + + ] + } + ] as ProColumns[] + }, [ isDeleting, currentWebsiteDnsRecords, search ]) + + useEffect(() => { + + setSearchKey(search?.title) + filterForm.setFieldsValue(search) + + }, [ search ]) + + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) + + return ( + + { + setSearch(prev => ({ + ...prev, + title: value + })) + }, + allowClear: true, + onChange: (e) => { + setSearchKey(e.target?.value) + }, + value: searchKey, + placeholder: t(`${i18nPrefix}.placeholder`, '输入记录管理名称') + }, + actions: [ + + + + ] + }} + scroll={{ + x: 2500, y: 'calc(100vh - 290px)' + }} + search={false} + onRow={(record) => { + return { + className: cx({ + 'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id + }), + onClick: () => { + setWebsiteDnsRecords(record) + } + } + }} + dateFormatter="string" + loading={isLoading || isFetching} + dataSource={data?.rows ?? []} + columns={columns} + options={{ + reload: () => { + refetch() + }, + }} + pagination={{ + total: data?.total, + pageSize: search.pageSize, + current: search.page, + onShowSizeChange: (current: number, size: number) => { + setSearch({ + ...search, + pageSize: size, + page: current + }) + }, + onChange: (current, pageSize) => { + setSearch(prev => { + return { + ...prev, + page: current, + pageSize: pageSize, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + onFinish={async (values) => { + saveOrUpdate(values) + }} + columns={columns as ProFormColumnsType[]}/> + { + setFilterOpen(open) + }} + layout={'vertical'} + scrollToFirstError={true} + layoutType={'DrawerForm'} + drawerProps={{ + maskClosable: false, + mask: false, + }} + submitter={{ + searchConfig: { + resetText: t(`${i18nPrefix}.filter.reset`, '清空'), + submitText: t(`${i18nPrefix}.filter.submit`, '查询'), + }, + onReset: () => { + filterForm.resetFields() + }, + render: (props,) => { + return ( +
+ + + + +
+ ) + }, + + }} + onFinish={async (values) => { + //处理,变成数组 + Object.keys(values).forEach(key => { + if (typeof values[key] === 'string' && values[key].includes(',')) { + values[key] = values[key].split(',') + } + }) + + setSearch(values) + + }} + columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> +
+ ) +} + +export default WebsiteDnsRecords \ No newline at end of file diff --git a/src/pages/websites/record/style.ts b/src/pages/websites/record/style.ts new file mode 100644 index 0000000..99188e0 --- /dev/null +++ b/src/pages/websites/record/style.ts @@ -0,0 +1,26 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsRecords-list-page` + + const container = css` + .ant-table-cell{ + .ant-tag{ + padding-inline: 3px; + margin-inline-end: 3px; + } + } + .ant-table-empty { + .ant-table-body{ + height: calc(100vh - 350px) + } + } + .ant-pro-table-highlight{ + + } + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/pages/websites/ssl/account/index.tsx b/src/pages/websites/ssl/account/index.tsx deleted file mode 100644 index a933271..0000000 --- a/src/pages/websites/ssl/account/index.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { useTranslation } from '@/i18n.ts' -import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' -import { useAtom, useAtomValue } from 'jotai' -import { - deleteWebsiteDnsAccountAtom, - saveOrUpdateWebsiteDnsAccountAtom, websiteDnsAccountAtom, websiteDnsAccountsAtom, websiteDnsAccountSearchAtom, -} from '@/store/websites/dns_account.ts' -import { useEffect, useMemo, useState } from 'react' -import Action from '@/components/action/Action.tsx' -import { - BetaSchemaForm, - ProColumns, - ProFormColumnsType, -} from '@ant-design/pro-components' -import ListPageLayout from '@/layout/ListPageLayout.tsx' -import { useStyle } from './style.ts' -import { FilterOutlined } from '@ant-design/icons' -import { getValueCount } from '@/utils' -import { Table as ProTable } from '@/components/table' - -const i18nPrefix = 'websiteDnsAccounts.list' - -const WebsiteDnsAccount = () => { - - const { styles, cx } = useStyle() - const { t } = useTranslation() - const [ form ] = Form.useForm() - const [ filterForm ] = Form.useForm() - const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsAccountAtom) - const [ search, setSearch ] = useAtom(websiteDnsAccountSearchAtom) - const [ currentWebsiteDnsAccount, setWebsiteDnsAccount ] = useAtom(websiteDnsAccountAtom) - const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsAccountsAtom) - const { mutate: deleteWebsiteDnsAccount, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsAccountAtom) - - const [ open, setOpen ] = useState(false) - const [ openFilter, setFilterOpen ] = useState(false) - const [ searchKey, setSearchKey ] = useState(search?.title) - - const columns = useMemo(() => { - return [ - { - title: 'ID', - dataIndex: 'id', - hideInTable: true, - hideInSearch: true, - formItemProps: { hidden: true } - }, - { - title: t(`${i18nPrefix}.columns.updated_at`, 'updated_at'), - dataIndex: 'updated_at', - }, - - { - title: t(`${i18nPrefix}.columns.name`, 'name'), - dataIndex: 'name', - }, - - { - title: t(`${i18nPrefix}.columns.type`, 'type'), - dataIndex: 'type', - }, - - { - title: t(`${i18nPrefix}.columns.authorization`, 'authorization'), - dataIndex: 'authorization', - }, - - { - title: t(`${i18nPrefix}.columns.status`, 'status'), - dataIndex: 'status', - }, - - { - title: t(`${i18nPrefix}.columns.remark`, 'remark'), - dataIndex: 'remark', - }, - - { - title: t(`${i18nPrefix}.columns.tag`, 'tag'), - dataIndex: 'tag', - }, - - { - title: t(`${i18nPrefix}.columns.option`, '操作'), - key: 'option', - valueType: 'option', - fixed: 'right', - render: (_, record) => [ - { - form.setFieldsValue(record) - setOpen(true) - }}>{t('actions.edit')}, - { - deleteWebsiteDnsAccount([ record.id ]) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - ] as ProColumns[] - }, [ isDeleting, currentWebsiteDnsAccount, search ]) - - useEffect(() => { - - setSearchKey(search?.title) - filterForm.setFieldsValue(search) - - }, [ search ]) - - useEffect(() => { - if (isSuccess) { - setOpen(false) - } - }, [ isSuccess ]) - - return ( - - { - setSearch(prev => ({ - ...prev, - title: value - })) - }, - allowClear: true, - onChange: (e) => { - setSearchKey(e.target?.value) - }, - value: searchKey, - placeholder: t(`${i18nPrefix}.placeholder`, '输入账号管理名称') - }, - actions: [ - - - - ] - }} - scroll={{ - x: 2500, y: 'calc(100vh - 290px)' - }} - search={false} - onRow={(record) => { - return { - className: cx({ - 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id - }), - onClick: () => { - setWebsiteDnsAccount(record) - } - } - }} - dateFormatter="string" - loading={isLoading || isFetching} - dataSource={data?.rows ?? []} - columns={columns} - options={{ - reload: () => { - refetch() - }, - }} - pagination={{ - total: data?.total, - pageSize: search.pageSize, - current: search.page, - onShowSizeChange: (current: number, size: number) => { - setSearch({ - ...search, - pageSize: size, - page: current - }) - }, - onChange: (current, pageSize) => { - setSearch(prev => { - return { - ...prev, - page: current, - pageSize: pageSize, - } - }) - }, - }} - /> - { - setOpen(open) - }} - loading={isSubmitting} - // onValuesChange={(values) => { - // - // }} - onFinish={async (values) => { - saveOrUpdate(values) - }} - columns={columns as ProFormColumnsType[]}/> - { - setFilterOpen(open) - }} - layout={'vertical'} - scrollToFirstError={true} - layoutType={'DrawerForm'} - drawerProps={{ - maskClosable: false, - mask: false, - }} - submitter={{ - searchConfig: { - resetText: t(`${i18nPrefix}.filter.reset`, '清空'), - submitText: t(`${i18nPrefix}.filter.submit`, '查询'), - }, - onReset: () => { - filterForm.resetFields() - }, - render: (props,) => { - return ( -
- - - - -
- ) - }, - - }} - // onValuesChange={(values) => { - // - // }} - - onFinish={async (values) => { - //处理,变成数组 - Object.keys(values).forEach(key => { - if (typeof values[key] === 'string' && values[key].includes(',')) { - values[key] = values[key].split(',') - } - }) - - setSearch(values) - - }} - columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> -
- ) -} - -export default WebsiteDnsAccount \ No newline at end of file diff --git a/src/pages/websites/ssl/account/style.ts b/src/pages/websites/ssl/account/style.ts deleted file mode 100644 index 7852918..0000000 --- a/src/pages/websites/ssl/account/style.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createStyles } from '@/theme' - -export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { - const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsAccount-list-page` - - const container = css` - .ant-table-cell{ - .ant-tag{ - padding-inline: 3px; - margin-inline-end: 3px; - } - } - .ant-table-empty { - .ant-table-body{ - height: calc(100vh - 350px) - } - } - .ant-pro-table-highlight{ - - } - ` - - return { - container: cx(prefix, props?.className, container), - } -}) \ No newline at end of file diff --git a/src/pages/websites/ssl/acme/AcmeList.tsx b/src/pages/websites/ssl/acme/AcmeList.tsx deleted file mode 100644 index 1abf58b..0000000 --- a/src/pages/websites/ssl/acme/AcmeList.tsx +++ /dev/null @@ -1,184 +0,0 @@ -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, - 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 { WebSite } from '@/types' - -const AcmeList = () => { - - 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: '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', '删除')} - - - ] - } - } - ] - }, []) - - 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.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 deleted file mode 100644 index c185043..0000000 --- a/src/pages/websites/ssl/ca/CAList.tsx +++ /dev/null @@ -1,250 +0,0 @@ -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' -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 = () => { - - 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_uint', '部门'), - dataIndex: 'organization_uint', - hideInTable: true, - hideInSetting: true, - valueType: 'text', - formItemProps: {} - }, - { - title: t('website.ssl.ca.form.country', '国家代号'), - dataIndex: 'country', - hideInTable: true, - hideInSetting: true, - valueType: 'text', - formItemProps: { - rules: [ - { required: true, message: t('message.required', '请输入') } - ] - } - }, - { - title: t('website.ssl.ca.form.province', '省份'), - dataIndex: 'province', - hideInTable: true, - hideInSetting: true, - valueType: 'text', - formItemProps: {} - }, - { - title: t('website.ssl.ca.form.city', '城市'), - dataIndex: 'city', - hideInTable: true, - hideInSetting: true, - valueType: 'text', - formItemProps: {} - }, - - { - title: t('website.ssl.ca.columns.keyType', '密钥算法'), - 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', '删除')} - - - ] - } - } - ] - }, []) - - 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.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/ca/Detail.tsx b/src/pages/websites/ssl/ca/Detail.tsx deleted file mode 100644 index 7cbf23b..0000000 --- a/src/pages/websites/ssl/ca/Detail.tsx +++ /dev/null @@ -1,125 +0,0 @@ -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}.csr`, 'csr'), 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 ]) - - useEffect(() => { - return () => { - setUI({ - open: false, - record: {}, - }) - } - }, []) - - 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 deleted file mode 100644 index 6e9c0c1..0000000 --- a/src/pages/websites/ssl/ca/SelfSign.tsx +++ /dev/null @@ -1,136 +0,0 @@ -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 deleted file mode 100644 index bc690fe..0000000 --- a/src/pages/websites/ssl/ca/store.ts +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index cf59121..0000000 --- a/src/pages/websites/ssl/ca/style.ts +++ /dev/null @@ -1,23 +0,0 @@ -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/dns/DNSList.tsx b/src/pages/websites/ssl/dns/DNSList.tsx deleted file mode 100644 index 2bb95d3..0000000 --- a/src/pages/websites/ssl/dns/DNSList.tsx +++ /dev/null @@ -1,318 +0,0 @@ -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 Action from '@/components/action/Action.tsx' -import { - deleteDNSAtom, - dnsListAtom, - dnsPageAtom, - DNSTypeEnum, - DNSTypes, - saveOrUpdateDNSAtom, syncDNSAtom -} 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') } ] - } - }) - } - 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 - } - default: - break - - } - return columns -} - -const DNSList = () => { - - 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 { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom) - 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', '请输入') } - ] - } - }, - - { - 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, - hideInTable: true, - columns: ({ type }) => { - return getKeyColumn(type, t) - } - }, - { - title: '操作', - valueType: 'option', - render: (_, record) => { - return [ - { - form.setFieldsValue(record) - setOpen(true) - }}>{t('actions.edit')}, - { - asyncDNS(record) - }}>{t('actions.sync', '同步')}, - { - deleteDNS(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 || 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) - }} - columns={columns as ProFormColumnsType[]}/> - - ) -} - -export default DNSList \ No newline at end of file diff --git a/src/pages/websites/ssl/domain/index.tsx b/src/pages/websites/ssl/domain/index.tsx deleted file mode 100644 index 181c1ae..0000000 --- a/src/pages/websites/ssl/domain/index.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { useTranslation } from '@/i18n.ts' -import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' -import { useAtom, useAtomValue } from 'jotai' -import { - deleteWebsiteDomainAtom, - saveOrUpdateWebsiteDomainAtom, websiteDomainAtom, websiteDomainsAtom, websiteDomainSearchAtom, -} from '@/store/websites/domain' -import { useEffect, useMemo, useState } from 'react' -import Action from '@/components/action/Action.tsx' -import { - BetaSchemaForm, - ProColumns, - ProFormColumnsType, -} from '@ant-design/pro-components' -import ListPageLayout from '@/layout/ListPageLayout.tsx' -import { useStyle } from './style' -import { FilterOutlined } from '@ant-design/icons' -import { getValueCount } from '@/utils' -import { Table as ProTable } from '@/components/table' - -const i18nPrefix = 'websiteDomains.list' - -const WebsiteDomain = () => { - - const { styles, cx } = useStyle() - const { t } = useTranslation() - const [ form ] = Form.useForm() - const [ filterForm ] = Form.useForm() - const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom) - const [ search, setSearch ] = useAtom(websiteDomainSearchAtom) - const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom) - const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom) - const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom) - - const [ open, setOpen ] = useState(false) - const [ openFilter, setFilterOpen ] = useState(false) - const [ searchKey, setSearchKey ] = useState(search?.title) - - const columns = useMemo(() => { - return [ - { - title: 'ID', - dataIndex: 'id', - hideInTable: true, - hideInSearch: true, - formItemProps: { hidden: true } - }, - { - title: t(`${i18nPrefix}.columns.name`, 'name'), - dataIndex: 'name', - }, - - { - title: t(`${i18nPrefix}.columns.dns_account_id`, 'dns_account_id'), - dataIndex: 'dns_account_id', - }, - { - title: t(`${i18nPrefix}.columns.status`, 'status'), - dataIndex: 'status', - }, - { - title: t(`${i18nPrefix}.columns.created`, 'created'), - dataIndex: 'created', - }, - { - title: t(`${i18nPrefix}.columns.modified`, 'modified'), - dataIndex: 'modified', - }, - - { - title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'), - dataIndex: 'nameservers', - }, - - { - title: t(`${i18nPrefix}.columns.tag`, 'tag'), - dataIndex: 'tag', - }, - - { - title: t(`${i18nPrefix}.columns.remark`, 'remark'), - dataIndex: 'remark', - }, - - { - title: t(`${i18nPrefix}.columns.option`, '操作'), - key: 'option', - valueType: 'option', - fixed: 'right', - render: (_, record) => [ - { - form.setFieldsValue(record) - setOpen(true) - }}>{t('actions.edit')}, - { - deleteWebsiteDomain([ record.id ]) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - ] as ProColumns[] - }, [ isDeleting, currentWebsiteDomain, search ]) - - useEffect(() => { - - setSearchKey(search?.title) - filterForm.setFieldsValue(search) - - }, [ search ]) - - useEffect(() => { - if (isSuccess) { - setOpen(false) - } - }, [ isSuccess ]) - - return ( - - { - setSearch(prev => ({ - ...prev, - title: value - })) - }, - allowClear: true, - onChange: (e) => { - setSearchKey(e.target?.value) - }, - value: searchKey, - placeholder: t(`${i18nPrefix}.placeholder`, '输入域名管理名称') - }, - actions: [ - - - - ] - }} - scroll={{ - x: 2500, y: 'calc(100vh - 290px)' - }} - search={false} - onRow={(record) => { - return { - className: cx({ - 'ant-table-row-selected': currentWebsiteDomain?.id === record.id - }), - onClick: () => { - setWebsiteDomain(record) - } - } - }} - dateFormatter="string" - loading={isLoading || isFetching} - dataSource={data?.rows ?? []} - columns={columns} - options={{ - reload: () => { - refetch() - }, - }} - pagination={{ - total: data?.total, - pageSize: search.pageSize, - current: search.page, - onShowSizeChange: (current: number, size: number) => { - setSearch({ - ...search, - pageSize: size, - page: current - }) - }, - onChange: (current, pageSize) => { - setSearch(prev => { - return { - ...prev, - page: current, - pageSize: pageSize, - } - }) - }, - }} - /> - { - setOpen(open) - }} - loading={isSubmitting} - - onFinish={async (values) => { - saveOrUpdate(values) - }} - columns={columns as ProFormColumnsType[]}/> - { - setFilterOpen(open) - }} - layout={'vertical'} - scrollToFirstError={true} - layoutType={'DrawerForm'} - drawerProps={{ - maskClosable: false, - mask: false, - }} - submitter={{ - searchConfig: { - resetText: t(`${i18nPrefix}.filter.reset`, '清空'), - submitText: t(`${i18nPrefix}.filter.submit`, '查询'), - }, - onReset: () => { - filterForm.resetFields() - }, - render: (props,) => { - return ( -
- - - - -
- ) - }, - - }} - - - onFinish={async (values) => { - //处理,变成数组 - Object.keys(values).forEach(key => { - if (typeof values[key] === 'string' && values[key].includes(',')) { - values[key] = values[key].split(',') - } - }) - - setSearch(values) - - }} - columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> -
- ) -} - -export default WebsiteDomain \ No newline at end of file diff --git a/src/pages/websites/ssl/domain/style.ts b/src/pages/websites/ssl/domain/style.ts deleted file mode 100644 index e1d04b4..0000000 --- a/src/pages/websites/ssl/domain/style.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createStyles } from '@/theme' - -export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { - const prefix = `${prefixCls}-${token?.proPrefix}-websiteDomain-list-page` - - const container = css` - .ant-table-cell{ - .ant-tag{ - padding-inline: 3px; - margin-inline-end: 3px; - } - } - .ant-table-empty { - .ant-table-body{ - height: calc(100vh - 350px) - } - } - .ant-pro-table-highlight{ - - } - ` - - return { - container: cx(prefix, props?.className, container), - } -}) \ No newline at end of file diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx index 063f080..ea172a2 100644 --- a/src/pages/websites/ssl/index.tsx +++ b/src/pages/websites/ssl/index.tsx @@ -16,19 +16,19 @@ 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 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 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 SSLDetail from '@/pages/websites/ssl/components/Detail.tsx' +import { detailAtom } from '@/pages/websites/ssl/components/store.ts' +import Upload from '@/pages/websites/ssl/components/Upload.tsx' import { FormInstance } from 'antd/lib' import Download from '@/components/download/Download.tsx' import { Table as ProTable } from '@/components/table' diff --git a/src/pages/websites/ssl/record/index.tsx b/src/pages/websites/ssl/record/index.tsx deleted file mode 100644 index 13c2d10..0000000 --- a/src/pages/websites/ssl/record/index.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import { useTranslation } from '@/i18n.ts' -import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' -import { useAtom, useAtomValue } from 'jotai' -import { - deleteWebsiteDnsRecordsAtom, - saveOrUpdateWebsiteDnsRecordsAtom, websiteDnsRecordsAtom, websiteDnsRecordssAtom, websiteDnsRecordsSearchAtom, -} from '@/store/websites/record' -import { useEffect, useMemo, useState } from 'react' -import Action from '@/components/action/Action.tsx' -import { - BetaSchemaForm, - ProColumns, - ProFormColumnsType, -} from '@ant-design/pro-components' -import ListPageLayout from '@/layout/ListPageLayout.tsx' -import { useStyle } from './style' -import { FilterOutlined } from '@ant-design/icons' -import { getValueCount } from '@/utils' -import { Table as ProTable } from '@/components/table' - -const i18nPrefix = 'websiteDnsRecordss.list' - -const WebsiteDnsRecords = () => { - - const { styles, cx } = useStyle() - const { t } = useTranslation() - const [ form ] = Form.useForm() - const [ filterForm ] = Form.useForm() - const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsRecordsAtom) - const [ search, setSearch ] = useAtom(websiteDnsRecordsSearchAtom) - const [ currentWebsiteDnsRecords, setWebsiteDnsRecords ] = useAtom(websiteDnsRecordsAtom) - const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsRecordssAtom) - const { mutate: deleteWebsiteDnsRecords, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsRecordsAtom) - - const [ open, setOpen ] = useState(false) - const [ openFilter, setFilterOpen ] = useState(false) - const [ searchKey, setSearchKey ] = useState(search?.title) - - const columns = useMemo(() => { - return [ - { - title: 'ID', - dataIndex: 'id', - hideInTable: true, - hideInSearch: true, - formItemProps: { hidden: true } - }, - { - title: t(`${i18nPrefix}.columns.record_id`, 'record_id'), - dataIndex: 'record_id', - }, - - { - title: t(`${i18nPrefix}.columns.domain_id`, 'domain_id'), - dataIndex: 'domain_id', - }, - - { - title: t(`${i18nPrefix}.columns.name`, 'name'), - dataIndex: 'name', - }, - - { - title: t(`${i18nPrefix}.columns.content`, 'content'), - dataIndex: 'content', - }, - - { - title: t(`${i18nPrefix}.columns.poxy`, 'poxy'), - dataIndex: 'poxy', - }, - - { - title: t(`${i18nPrefix}.columns.ttl`, 'ttl'), - dataIndex: 'ttl', - }, - - { - title: t(`${i18nPrefix}.columns.type`, 'type'), - dataIndex: 'type', - }, - - { - title: t(`${i18nPrefix}.columns.status`, 'status'), - dataIndex: 'status', - }, - - { - title: t(`${i18nPrefix}.columns.tag`, 'tag'), - dataIndex: 'tag', - }, - - { - title: t(`${i18nPrefix}.columns.remark`, 'remark'), - dataIndex: 'remark', - }, - - { - title: t(`${i18nPrefix}.columns.created`, 'created'), - dataIndex: 'created', - }, - - { - title: t(`${i18nPrefix}.columns.modified`, 'modified'), - dataIndex: 'modified', - }, - - { - title: t(`${i18nPrefix}.columns.option`, '操作'), - key: 'option', - valueType: 'option', - fixed: 'right', - render: (_, record) => [ - { - form.setFieldsValue(record) - setOpen(true) - }}>{t('actions.edit')}, - { - deleteWebsiteDnsRecords([ record.id ]) - }} - title={t('message.deleteConfirm')}> - - {t('actions.delete', '删除')} - - - ] - } - ] as ProColumns[] - }, [ isDeleting, currentWebsiteDnsRecords, search ]) - - useEffect(() => { - - setSearchKey(search?.title) - filterForm.setFieldsValue(search) - - }, [ search ]) - - useEffect(() => { - if (isSuccess) { - setOpen(false) - } - }, [ isSuccess ]) - - return ( - - { - setSearch(prev => ({ - ...prev, - title: value - })) - }, - allowClear: true, - onChange: (e) => { - setSearchKey(e.target?.value) - }, - value: searchKey, - placeholder: t(`${i18nPrefix}.placeholder`, '输入记录管理名称') - }, - actions: [ - - - - ] - }} - scroll={{ - x: 2500, y: 'calc(100vh - 290px)' - }} - search={false} - onRow={(record) => { - return { - className: cx({ - 'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id - }), - onClick: () => { - setWebsiteDnsRecords(record) - } - } - }} - dateFormatter="string" - loading={isLoading || isFetching} - dataSource={data?.rows ?? []} - columns={columns} - options={{ - reload: () => { - refetch() - }, - }} - pagination={{ - total: data?.total, - pageSize: search.pageSize, - current: search.page, - onShowSizeChange: (current: number, size: number) => { - setSearch({ - ...search, - pageSize: size, - page: current - }) - }, - onChange: (current, pageSize) => { - setSearch(prev => { - return { - ...prev, - page: current, - pageSize: pageSize, - } - }) - }, - }} - /> - { - setOpen(open) - }} - loading={isSubmitting} - onFinish={async (values) => { - saveOrUpdate(values) - }} - columns={columns as ProFormColumnsType[]}/> - { - setFilterOpen(open) - }} - layout={'vertical'} - scrollToFirstError={true} - layoutType={'DrawerForm'} - drawerProps={{ - maskClosable: false, - mask: false, - }} - submitter={{ - searchConfig: { - resetText: t(`${i18nPrefix}.filter.reset`, '清空'), - submitText: t(`${i18nPrefix}.filter.submit`, '查询'), - }, - onReset: () => { - filterForm.resetFields() - }, - render: (props,) => { - return ( -
- - - - -
- ) - }, - - }} - onFinish={async (values) => { - //处理,变成数组 - Object.keys(values).forEach(key => { - if (typeof values[key] === 'string' && values[key].includes(',')) { - values[key] = values[key].split(',') - } - }) - - setSearch(values) - - }} - columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> -
- ) -} - -export default WebsiteDnsRecords \ No newline at end of file diff --git a/src/pages/websites/ssl/record/style.ts b/src/pages/websites/ssl/record/style.ts deleted file mode 100644 index 99188e0..0000000 --- a/src/pages/websites/ssl/record/style.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createStyles } from '@/theme' - -export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { - const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsRecords-list-page` - - const container = css` - .ant-table-cell{ - .ant-tag{ - padding-inline: 3px; - margin-inline-end: 3px; - } - } - .ant-table-empty { - .ant-table-body{ - height: calc(100vh - 350px) - } - } - .ant-pro-table-highlight{ - - } - ` - - return { - container: cx(prefix, props?.className, container), - } -}) \ No newline at end of file diff --git a/src/service/websites.ts b/src/service/websites.ts index 4f80ac6..178aa9a 100644 --- a/src/service/websites.ts +++ b/src/service/websites.ts @@ -19,7 +19,7 @@ const websitesServ = { ...createCURD('/website/acme') }, dns: { - ...createCURD('/website/account'), + ...createCURD('/website/dns_account'), sync: async (params: WebSite.IDnsAccount) => { return request.post('/website/dns_account/sync', params) } diff --git a/src/types/system/menus.d.ts b/src/types/system/menus.d.ts index 607b3a3..e33597d 100644 --- a/src/types/system/menus.d.ts +++ b/src/types/system/menus.d.ts @@ -1,7 +1,6 @@ - export interface MenuButton { - code: string, - label: string + code: string, + label: string } @@ -11,27 +10,29 @@ export interface Meta { } export interface IMenu { - id: number, - key: string, - parent_id: number, - sort: number, - code: string, - name: string, - parentName: string[], - title: string, - component: string, - icon: string | any, - description: string, - sequence: number, - type: string, - path: string, - properties: string, - status: string, - parent_path: string, - affix: boolean, - redirect: string, - button: MenuButton[], - meta: Meta, + id: number, + key: string, + parent_id: number, + sort: number, + code: string, + name: string, + parentName: string[], + title: string, + component: string, + icon: string | any, + description: string, + sequence: number, + type: string, + path: string, + properties: string, + status: string, + parent_path: string, + affix: boolean, + hidden: boolean, + hidden_breadcrumb: boolean, + redirect: string, + button: MenuButton[], + meta: Meta, - children: IMenu[] + children: IMenu[] }