@@ -240,7 +233,7 @@ const Roles = memo(() => {
onFinish={async (values) => {
// console.log('values', values)
mutate(values)
- return true
+ return isSuccess
}}
columns={columns as ProFormColumnsType[]}/>
diff --git a/src/pages/system/users/index.tsx b/src/pages/system/users/index.tsx
index c55d0af..dd80e62 100644
--- a/src/pages/system/users/index.tsx
+++ b/src/pages/system/users/index.tsx
@@ -31,7 +31,7 @@ const Users = () => {
const [ page, setPage ] = useAtom(userPageAtom)
const [ search, setSearch ] = useAtom(userSearchAtom)
const [ , setCurrent ] = useAtom(userSelectedAtom)
- const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateUserAtom)
+ const { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateUserAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(userListAtom)
const { mutate: deleteUser, isPending, } = useAtomValue(deleteUserAtom)
const { mutate: resetPass, isPending: isResetting } = useAtomValue(resetPasswordAtom)
@@ -236,7 +236,7 @@ const Users = () => {
onFinish={async (values) => {
// console.log('values', values)
saveOrUpdate(values)
- return true
+ return isSuccess
}}
columns={columns as ProFormColumnsType[]}/>
diff --git a/src/pages/websites/ssl/components/AcmeList.tsx b/src/pages/websites/ssl/components/AcmeList.tsx
new file mode 100644
index 0000000..9575329
--- /dev/null
+++ b/src/pages/websites/ssl/components/AcmeList.tsx
@@ -0,0 +1,174 @@
+import { useMemo, useState } from 'react'
+import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
+import { IAcmeAccount } from '@/types/website/acme'
+import { useTranslation } from '@/i18n.ts'
+import { acmeListAtom, acmePageAtom, AcmeType, saveOrUpdateAcmeAtom } from '@/store/websites/acme.ts'
+import { useAtom, useAtomValue } from 'jotai'
+import { Alert, Button, Form } from 'antd'
+import { KeyTypeEnum } from '@/store/websites/ssl.ts'
+
+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 [ 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: [
+ { label: 'Let\'s Encrypt', value: AcmeType.LetsEncrypt },
+ { label: 'ZeroSSl', value: AcmeType.ZeroSSl },
+ { label: 'Buypass', value: AcmeType.Buypass },
+ { label: 'Google Cloud', value: AcmeType.Google },
+ ]
+ },
+ formItemProps: {
+ rules: [
+ { required: true, message: t('message.required', '请选择') }
+ ]
+ }
+ },
+ {
+ title: t('website.ssl.acme.columns.keyType', '密钥算法'),
+ dataIndex: 'keyType',
+ valueType: 'select',
+ initialValue: KeyTypeEnum.EC256,
+ fieldProps: {
+ options: [
+ { label: t('website.ssl.keyTypeEnum.EC256', 'EC 256'), value: KeyTypeEnum.EC256 },
+ { label: t('website.ssl.keyTypeEnum.EC384', 'EC 384'), value: KeyTypeEnum.EC384 },
+ { label: t('website.ssl.keyTypeEnum.RSA2048', 'RSA 2048'), value: KeyTypeEnum.RSA2048 },
+ { label: t('website.ssl.keyTypeEnum.RSA3072', 'RSA 3072'), value: KeyTypeEnum.RSA3072 },
+ { label: t('website.ssl.keyTypeEnum.RSA4096', 'RSA 4096'), value: KeyTypeEnum.RSA4096 },
+ ]
+ },
+ 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 [
+ {
+ }}>{t('actions.edit', '编辑')},
+ {
+ }}>{t('actions.delete', '删除')},
+ ]
+ }
+ }
+ ]
+ }, [])
+
+ return (
+ <>
+
+
+ cardProps={{
+ bodyStyle: {
+ padding: 0,
+ }
+ }}
+ rowKey="id"
+ headerTitle={
+
+ }
+ loading={isLoading}
+ dataSource={data?.rows ?? []}
+ columns={columns}
+ search={false}
+ options={{
+ reload: () => {
+ refetch()
+ },
+ }}
+ pagination={{
+ total: data?.total,
+ pageSize: page.pageSize,
+ current: page.page,
+ onChange: (current, pageSize) => {
+ setPage(prev => {
+ return {
+ ...prev,
+ page: current,
+ pageSize: pageSize,
+ }
+ })
+ },
+
+ }}
+ />
+
+ shouldUpdate={false}
+ width={600}
+ form={form}
+ layout={'horizontal'}
+ scrollToFirstError={true}
+ title={t(`website.ssl.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[]}/>
+ >
+ )
+}
+
+export default AcmeList
\ No newline at end of file
diff --git a/src/pages/websites/ssl/index.tsx b/src/pages/websites/ssl/index.tsx
index cf2a28c..538492e 100644
--- a/src/pages/websites/ssl/index.tsx
+++ b/src/pages/websites/ssl/index.tsx
@@ -1,12 +1,21 @@
import { useAtom, useAtomValue } from 'jotai'
-import { ProviderTypeEnum, saveOrUpdateSslAtom, sslListAtom, sslPageAtom, sslSearchAtom } from '@/store/websites/ssl.ts'
+import {
+ KeyTypeEnum,
+ ProviderTypeEnum,
+ saveOrUpdateSslAtom,
+ sslListAtom,
+ sslPageAtom,
+ sslSearchAtom
+} from '@/store/websites/ssl.ts'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
import { memo, useMemo, useState } from 'react'
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Popconfirm } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
-import { ISSL } from '@/types/ssl'
+import { ISSL } from '@/types/website/ssl'
+import DrawerPicker from '@/components/drawer-picker/DrawerPicker.tsx'
+import AcmeList from '@/pages/websites/ssl/components/AcmeList.tsx'
const SSL = () => {
@@ -16,7 +25,7 @@ const SSL = () => {
const [ page, setPage ] = useAtom(sslPageAtom)
const [ search, setSearch ] = useAtom(sslSearchAtom)
const { data, isLoading, isFetching, refetch } = useAtomValue(sslListAtom)
- const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom)
+ const { mutate: saveOrUpdate, isSuccess, isPending: isSubmitting } = useAtomValue(saveOrUpdateSslAtom)
const [ open, setOpen ] = useState(false)
@@ -34,7 +43,7 @@ const SSL = () => {
{
title: t('website.ssl.columns.primaryDomain', '域名'),
dataIndex: 'primaryDomain',
- formItemProps:{
+ formItemProps: {
label: t('website.ssl.form.primaryDomain', '主域名'),
rules: [ { required: true, message: t('message.required', '主域名') } ]
}
@@ -48,6 +57,27 @@ const SSL = () => {
dataIndex: 'acmeAccountId',
},
{
+ title: t('website.ssl.columns.keyType', '密钥算法'),
+ dataIndex: 'keyType',
+ hideInTable: true,
+ valueType: 'select',
+ initialValue: KeyTypeEnum.EC256,
+ fieldProps: {
+ options: [
+ { label: t('website.ssl.keyTypeEnum.EC256', 'EC 256'), value: KeyTypeEnum.EC256 },
+ { label: t('website.ssl.keyTypeEnum.EC384', 'EC 384'), value: KeyTypeEnum.EC384 },
+ { label: t('website.ssl.keyTypeEnum.RSA2048', 'RSA 2048'), value: KeyTypeEnum.RSA2048 },
+ { label: t('website.ssl.keyTypeEnum.RSA3072', 'RSA 3072'), value: KeyTypeEnum.RSA3072 },
+ { label: t('website.ssl.keyTypeEnum.RSA4096', 'RSA 4096'), value: KeyTypeEnum.RSA4096 },
+ ]
+ },
+ formItemProps: {
+ rules: [
+ { required: true, message: t('message.required', '请选择') }
+ ]
+ },
+ },
+ {
title: t('website.ssl.columns.provider', '申请方式'),
dataIndex: 'provider',
valueType: 'radio',
@@ -63,8 +93,8 @@ const SSL = () => {
text: t('website.ssl.providerTypeEnum.Http', 'HTTP'),
}
},
- dependencies : [ 'provider' ],
- formItemProps: (form, config)=> {
+ dependencies: [ 'provider' ],
+ formItemProps: (form, config) => {
const val = form.getFieldValue(config.dataIndex)
const help = {
[ProviderTypeEnum.DnsAccount]: t('website.ssl.form.provider_{{v}}', '', { v: val }),
@@ -73,7 +103,7 @@ const SSL = () => {
}
return {
label: t('website.ssl.form.provider', '验证方式'),
- help: ,
+ help: ,
rules: [ { required: true, message: t('message.required', '请选择') } ]
}
},
@@ -86,6 +116,10 @@ const SSL = () => {
return [ {
title: t('website.ssl.columns.dnsAccountId', 'DNS帐号'),
dataIndex: 'dnsAccountId',
+ formItemProps: {
+ rules: [ { required: true, message: t('message.required', '请输入DNS帐号') } ]
+ }
+
} ]
}
return []
@@ -179,6 +213,21 @@ const SSL = () => {
placeholder: t('website.ssl.search.placeholder', '输入域名')
},
actions: [
+
+ {t('website.ssl.actions.acme', 'Acme帐户')}
+ }
+ >
+
+ ,
+
+ {t( 'website.ssl.actions.dns', 'DNS帐户')}
+ }>
+
+ ,
,
]
}}
@@ -232,7 +281,7 @@ const SSL = () => {
onFinish={async (values) => {
// console.log('values', values)
saveOrUpdate(values)
- return true
+ return isSuccess
}}
columns={columns as ProFormColumnsType[]}/>
diff --git a/src/request.ts b/src/request.ts
index d8ab432..8df22eb 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -84,6 +84,7 @@ axiosInstance.interceptors.response.use(
}, (error) => {
// console.log('error', error)
+ message.destroy()
const { response } = error
if (response) {
switch (response.status) {
@@ -105,7 +106,7 @@ axiosInstance.interceptors.response.use(
window.location.href = `/login?redirect=${encodeURIComponent(redirect)}`
return
default:
- message.error(response.data.message)
+ message.error(response.data.message ?? response.data ?? error.message ?? '请求失败')
return Promise.reject(response)
}
}
diff --git a/src/service/websites.ts b/src/service/websites.ts
index 0458552..2e7546a 100644
--- a/src/service/websites.ts
+++ b/src/service/websites.ts
@@ -1,8 +1,13 @@
import { createCURD } from '@/service/base.ts'
+import { ISSL } from '@/types/website/ssl'
+import { IAcmeAccount } from '@/types/website/acme'
const websitesServ = {
ssl: {
- ...createCURD('/website/ssl')
+ ...createCURD('/website/ssl')
+ },
+ acme:{
+ ...createCURD('/website/acme')
}
}
diff --git a/src/store/websites/acme.ts b/src/store/websites/acme.ts
new file mode 100644
index 0000000..88e4bb5
--- /dev/null
+++ b/src/store/websites/acme.ts
@@ -0,0 +1,49 @@
+import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
+import { IAcmeAccount } from '@/types/website/acme'
+import websitesServ from '@/service/websites.ts'
+import { message } from 'antd'
+import { t } from 'i18next'
+import { IPage } from '@/global'
+import { atom } from 'jotai'
+
+export enum AcmeType {
+ LetsEncrypt = 'letsencrypt',
+ //zerossl
+ ZeroSSl = 'zerossl',
+ //buypass
+ Buypass = 'buypass',
+ //google
+ Google = 'google',
+}
+
+export const acmePageAtom = atom({
+ 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
+ }
+}))
+
+//saveOrUpdate
+export const saveOrUpdateAcmeAtom = atomWithMutation(get => ({
+ mutationKey: [ 'saveOrUpdateAcme' ],
+ mutationFn: async (data: 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
+ }
+}))
diff --git a/src/store/websites/ssl.ts b/src/store/websites/ssl.ts
index 6cc0770..e4c7804 100644
--- a/src/store/websites/ssl.ts
+++ b/src/store/websites/ssl.ts
@@ -4,7 +4,7 @@ import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
import websitesServ from '@/service/websites.ts'
import { message } from 'antd'
import { t } from 'i18next'
-import { ISSL, SSLSearchParam } from '@/types/ssl'
+import { ISSL, SSLSearchParam } from '@/types/website/ssl'
export enum ProviderTypeEnum {
DnsAccount = 'dnsAccount',
@@ -12,6 +12,14 @@ export enum ProviderTypeEnum {
Http = 'http'
}
+export enum KeyTypeEnum {
+ EC256 = 'P256',
+ EC384 = 'P384',
+ RSA2048 = '2048',
+ RSA3072 = '3072',
+ RSA4096 = '4096',
+}
+
export const sslPageAtom = atom({
page: 1,
pageSize: 20,
@@ -42,10 +50,14 @@ export const saveOrUpdateSslAtom = atomWithMutation(get => ({
return await websitesServ.ssl.update(data)
}
},
+ // onError: (error: any) => {
+ // const msg = error?.data?.message ||t('message.saveFailed', '保存失败')
+ // message.error(msg)
+ // return error
+ // },
onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
-
get(sslListAtom).refetch()
return res
},
diff --git a/src/types/website/acme.d.ts b/src/types/website/acme.d.ts
new file mode 100644
index 0000000..e62f62a
--- /dev/null
+++ b/src/types/website/acme.d.ts
@@ -0,0 +1,14 @@
+export interface IAcmeAccount {
+ id: number;
+ createdAt?: string;
+ createdBy: number;
+ updatedAt?: string;
+ updatedBy: number;
+ email: string;
+ url: string;
+ privateKey: string;
+ type: string;
+ eabKid: string;
+ eabHmacKey: string;
+ keyType: string;
+}
\ No newline at end of file
diff --git a/src/types/ca.ts b/src/types/website/ca.d.ts
similarity index 73%
rename from src/types/ca.ts
rename to src/types/website/ca.d.ts
index de2d5ed..182d70f 100644
--- a/src/types/ca.ts
+++ b/src/types/website/ca.d.ts
@@ -1,8 +1,8 @@
export interface ICA {
id: number;
- createdAt: Date | null;
+ createdAt: string | null;
createdBy: number;
- updatedAt: Date | null;
+ updatedAt: string | null;
updatedBy: number;
csr: string;
name: string;
diff --git a/src/types/dns.ts b/src/types/website/dns.d.ts
similarity index 71%
rename from src/types/dns.ts
rename to src/types/website/dns.d.ts
index 36a8c9d..0b9cdbc 100644
--- a/src/types/dns.ts
+++ b/src/types/website/dns.d.ts
@@ -1,8 +1,8 @@
export interface IDnsAccount {
id: number;
- createdAt: Date | null;
+ createdAt: string | null;
createdBy: number;
- updatedAt: Date | null;
+ updatedAt: string | null;
updatedBy: number;
name: string;
type: string;
diff --git a/src/types/ssl.d.ts b/src/types/website/ssl.d.ts
similarity index 91%
rename from src/types/ssl.d.ts
rename to src/types/website/ssl.d.ts
index 17f674a..446fbe1 100644
--- a/src/types/ssl.d.ts
+++ b/src/types/website/ssl.d.ts
@@ -16,8 +16,8 @@ export interface ISSL {
acmeAccountId: number;
caId: number;
autoRenew: boolean;
- expireDate: Date | null;
- startDate: Date | null;
+ expireDate: string | null;
+ startDate: string | null;
status: string;
message: string;
keyType: string;