Browse Source

增加证书页面

main
lk 3 months ago
parent
commit
456043541f
  1. 2
      .env.proxy.local
  2. 2
      mock/menus.ts
  3. 8
      src/pages/dashboard/index.tsx
  4. 4
      src/pages/globle/globle_style.css
  5. 531
      src/pages/websites/account/index.tsx
  6. 20
      src/pages/websites/account/style.ts
  7. 371
      src/pages/websites/cert/apply.tsx
  8. 525
      src/pages/websites/domain/index.tsx
  9. 10
      src/pages/websites/domain/style.ts
  10. 52
      src/pages/websites/mytest/index.tsx
  11. 4
      src/pages/websites/record/index.tsx
  12. 2
      src/service/website/domain_group.ts
  13. 26
      src/service/websites.ts
  14. 25
      src/store/websites/cert.ts
  15. 3
      vite.config.ts

2
.env.proxy.local

@ -1 +1 @@
API_URL=http://47.113.117.106:10000
API_URL=http://192.168.31.96:8686

2
mock/menus.ts

@ -12,7 +12,7 @@ export default [
{
id:1,
path: '/welcome',
name: '欢迎',
name: '欢迎1111111111111',
icon: 'ApplicationMenu',
component: './pages/dashboard',
type: 1,

8
src/pages/dashboard/index.tsx

@ -12,7 +12,7 @@ const Index = () => {
}}
>
<h1>Dashboard</h1>
<h1>Dashboard操作面板</h1>
</ProCard>
</>
@ -20,7 +20,7 @@ const Index = () => {
}
// @ts-ignore
export const Route = createFileRoute('/dashboard')({
component: Index,
})
// export const Route = createFileRoute('/dashboard')({
// component: Index,
// })
export default Index

4
src/pages/globle/globle_style.css

@ -0,0 +1,4 @@
.disabled_text {
pointer-events: none; /* 禁用鼠标事件 */
color: gray; /* 设置文本颜色为灰色 */
}

531
src/pages/websites/account/index.tsx

@ -1,300 +1,321 @@
import { useTranslation } from '../../../i18n.ts'
import { Button, Form, Space, Tooltip, Badge, Divider } from 'antd'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { useTranslation } from "../../../i18n.ts";
import { Button, Form, Space, Tooltip, Badge, Divider, Spin } from "antd";
import { useAtom, useAtomValue, useSetAtom } 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, unSetColumnRules } from '@/utils'
import { Table as ProTable } from '@/components/table'
import { DNSTypeEnum, DNSTypes, syncDNSAtom } from '@/store/websites/dns.ts'
import { WebSite } from '@/types'
import Switch from '@/components/switch'
import Popconfirm from '@/components/popconfirm'
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, unSetColumnRules } from "@/utils";
import { Table as ProTable } from "@/components/table";
import { DNSTypeEnum, DNSTypes, syncDNSAtom } from "@/store/websites/dns.ts";
import { WebSite } from "@/types";
import Switch from "@/components/switch";
import Popconfirm from "@/components/popconfirm";
import { Link } from "@tanstack/react-router";
import "@/pages/globle/globle_style.css";
const i18nPrefix = 'websiteDnsAccounts.list'
const i18nPrefix = "websiteDnsAccounts.list";
const getKeyColumn = (type: string, t) => {
const columns: ProColumns<WebSite.IDnsAccount>[] = []
const columns: ProColumns<WebSite.IDnsAccount>[] = [];
switch (type) {
case DNSTypeEnum.AliYun: {
columns.push(...[
case DNSTypeEnum.AliYun:
{
columns.push(
...[
{
title: t('website.ssl.dns.columns.accessKey', 'Access Key'),
dataIndex: [ 'authorization', 'accessKey' ],
title: t("website.ssl.dns.columns.accessKey", "Access Key"),
dataIndex: ["authorization", "accessKey"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
},
{
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'),
dataIndex: [ 'authorization', 'secretKey' ],
title: t("website.ssl.dns.columns.secretKey", "Secret Key"),
dataIndex: ["authorization", "secretKey"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
},
])
],
);
}
break
break;
case DNSTypeEnum.TencentCloud: {
columns.push(...[
columns.push(
...[
{
title: t('website.ssl.dns.columns.secretID', 'Secret ID'),
dataIndex: [ 'authorization', 'secretID' ],
title: t("website.ssl.dns.columns.secretID", "Secret ID"),
dataIndex: ["authorization", "secretID"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
}, {
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'),
dataIndex: [ 'authorization', 'secretKey' ],
rules: [{ required: true, message: t("message.required") }],
},
},
{
title: t("website.ssl.dns.columns.secretKey", "Secret Key"),
dataIndex: ["authorization", "secretKey"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
])
break
},
],
);
break;
}
case DNSTypeEnum.DnsPod: {
columns.push(...[
columns.push(
...[
{
title: t('website.ssl.dns.columns.apiId', 'ID'),
dataIndex: [ 'authorization', 'apiId' ],
title: t("website.ssl.dns.columns.apiId", "ID"),
dataIndex: ["authorization", "apiId"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
}, {
title: t('website.ssl.dns.columns.token', 'Token'),
dataIndex: [ 'authorization', 'token' ],
rules: [{ required: true, message: t("message.required") }],
},
},
{
title: t("website.ssl.dns.columns.token", "Token"),
dataIndex: ["authorization", "token"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
},
])
break
],
);
break;
}
case DNSTypeEnum.CloudFlare: {
columns.push(...[
columns.push(
...[
{
title: t('website.ssl.dns.columns.email', 'Email'),
dataIndex: [ 'authorization', 'email' ],
title: t("website.ssl.dns.columns.email", "Email"),
dataIndex: ["authorization", "email"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
}, {
title: t('website.ssl.dns.columns.apiKey', 'API ToKen'),
dataIndex: [ 'authorization', 'apiKey' ],
rules: [{ required: true, message: t("message.required") }],
},
},
{
title: t("website.ssl.dns.columns.apiKey", "API ToKen"),
dataIndex: ["authorization", "apiKey"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
]
)
break
},
],
);
break;
}
case DNSTypeEnum.Godaddy:
case DNSTypeEnum.NameCheap:
case DNSTypeEnum.NameSilo:
columns.push({
title: t('website.ssl.dns.columns.apiKey', 'API Key'),
dataIndex: [ 'authorization', 'apiKey' ],
title: t("website.ssl.dns.columns.apiKey", "API Key"),
dataIndex: ["authorization", "apiKey"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
)
});
if (type === DNSTypeEnum.NameCheap) {
columns.push({
title: t('website.ssl.dns.columns.apiUser', 'API User'),
dataIndex: [ 'authorization', 'apiUser' ],
title: t("website.ssl.dns.columns.apiUser", "API User"),
dataIndex: ["authorization", "apiUser"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
})
rules: [{ required: true, message: t("message.required") }],
},
});
} else if (type === DNSTypeEnum.Godaddy) {
columns.push({
title: t('website.ssl.dns.columns.apiSecret', 'API Secret'),
dataIndex: [ 'authorization', 'apiSecret' ],
title: t("website.ssl.dns.columns.apiSecret", "API Secret"),
dataIndex: ["authorization", "apiSecret"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
})
rules: [{ required: true, message: t("message.required") }],
},
});
}
break
break;
case DNSTypeEnum.NameCom: {
columns.push(
{
title: t('website.ssl.dns.columns.apiUser', 'UserName'),
dataIndex: [ 'authorization', 'apiUser' ],
title: t("website.ssl.dns.columns.apiUser", "UserName"),
dataIndex: ["authorization", "apiUser"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
rules: [{ required: true, message: t("message.required") }],
},
},
{
title: t('website.ssl.dns.columns.token', 'Token'),
dataIndex: [ 'authorization', 'token' ],
title: t("website.ssl.dns.columns.token", "Token"),
dataIndex: ["authorization", "token"],
formItemProps: {
rules: [ { required: true, message: t('message.required') } ]
}
}
)
break
rules: [{ required: true, message: t("message.required") }],
},
},
);
break;
}
default:
break
break;
}
return columns
}
return columns;
};
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 setWebsiteDnsAccount = useSetAtom(websiteDnsAccountAtom);
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsAccountsAtom);
const { mutate: deleteWebsiteDnsAccount, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsAccountAtom);
const { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom);
const [open, setOpen] = useState(false);
const [openFilter, setFilterOpen] = useState(false);
const [searchKey, setSearchKey] = useState(search?.title);
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 setWebsiteDnsAccount = useSetAtom(websiteDnsAccountAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsAccountsAtom)
const { mutate: deleteWebsiteDnsAccount, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsAccountAtom)
const { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom)
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const columns = useMemo(() => {
return [
{
title: 'ID',
dataIndex: 'id',
title: "ID",
dataIndex: "id",
hideInTable: true,
hideInSearch: true,
formItemProps: { hidden: true }
formItemProps: { hidden: true },
},
{
title: t(`${i18nPrefix}.columns.name`, '名称'),
dataIndex: 'name',
valueType: 'text',
title: t(`${i18nPrefix}.columns.name`, "名称"),
dataIndex: "name",
valueType: "text",
width: 250,
fieldProps: {
style: {
width: '100%'
}
width: "100%",
},
},
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请输入') }
]
}
rules: [{ required: true, message: t("message.required", "请输入") }],
},
render: (text, record) => <Link to={`/cert/domain?id=${record.name}`}>{record.name}</Link>,
},
{
title: t(`${i18nPrefix}.columns.name`, '标签'),
dataIndex: 'tag',
valueType: 'text',
title: t(`${i18nPrefix}.columns.name`, "标签"),
dataIndex: "tag",
valueType: "text",
hideInForm: true,
width: 200,
fieldProps: {
style: {
width: '100%'
}
}
width: "100%",
},
},
},
{
title: t(`${i18nPrefix}.columns.type`, '类型'),
dataIndex: 'type',
valueType: 'select',
title: t(`${i18nPrefix}.columns.type`, "类型"),
dataIndex: "type",
valueType: "select",
width: 200,
fieldProps: {
style: {
width: '100%'
width: "100%",
},
options: DNSTypes
options: DNSTypes,
},
formItemProps: {
rules: [
{ required: true, message: t('message.required', '请选择') }
]
rules: [{ required: true, message: t("message.required", "请选择") }],
},
},
{
name: [ 'type' ],
valueType: 'dependency',
name: ["type"],
valueType: "dependency",
hideInSetting: true,
hideInTable: true,
columns: ({ type }) => {
return getKeyColumn(type, t)
}
return getKeyColumn(type, t);
},
},
{
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
valueType: 'switch',
title: t(`${i18nPrefix}.columns.status`, "状态111111"),
dataIndex: "status",
valueType: "switch",
width: 200,
//disable禁用,enable正常,syncing正在同步中
render(_dom, record) {
return <Switch size={'small'} value={record.status}/>
// return <Switch size={"small"} checked={false} />;
if (record.status == "syncing") {
return (
<div className={cx('loadingStyle')}>
<Spin tip={t(`${i18nPrefix}.columns.syncing`, "同步中")} size="small">
</Spin>
<div className={cx('loadingStyle_table')} >{t(`${i18nPrefix}.columns.syncing`, "同步中")}</div>
</div>
);
} else {
return <Switch size={"small"} checked={record.status==='enable'} />;
}
},
},
{
title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option',
title: t(`${i18nPrefix}.columns.option`, "操作"),
key: "option",
width: 300,
valueType: 'option',
fixed: 'right',
valueType: "option",
fixed: "right",
render: (_, record) => [
<Action key="edit"
as={'a'}
disabled={record.status === 2}
<Action
key="edit"
as={"a"}
disabled={record.status === "syncing"}
onClick={() => {
record.status = record.status > 0
if (typeof record.authorization === 'string') {
record.authorization = JSON.parse(record.authorization)
}
form.setFieldsValue(record)
setOpen(true)
}}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
form.setFieldsValue(record);
setOpen(true);
}}
>
{t("actions.edit", "编辑")}
</Action>,
<Divider type={"vertical"} />,
<Popconfirm
key={'sync_confirm'}
disabled={isAsyncing || record.status === 2}
key={"sync_confirm"}
disabled={record.status == "syncing"}
onConfirm={() => {
asyncDNS(record.id)
asyncDNS(record.id);
}}
title={t('message.syncConfirm', '您确定要同步吗?')}>
{t('actions.sync', '同步')}
title={t("message.syncConfirm", "您确定要同步吗?")}
>
{t("actions.sync", "同步")}
</Popconfirm>,
<Divider type={'vertical'}/>,
<Divider type={"vertical"} />,
<Popconfirm
key={'del_confirm'}
key={"del_confirm"}
disabled={isDeleting}
onConfirm={() => {
deleteWebsiteDnsAccount([ record.id ])
deleteWebsiteDnsAccount([record.id]);
}}
title={t('message.deleteConfirm')}>
{t('actions.delete', '删除')}
</Popconfirm>
]
}
] as ProColumns[]
}, [ isAsyncing, isDeleting, form, asyncDNS, deleteWebsiteDnsAccount ])
title={t("message.deleteConfirm")}
>
{t("actions.delete", "删除")}
</Popconfirm>,
],
},
] as ProColumns[];
}, [isAsyncing, isDeleting, form, asyncDNS, deleteWebsiteDnsAccount]);
useEffect(() => {
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
}, [ search ])
setSearchKey(search?.title);
filterForm.setFieldsValue(search);
}, [search]);
useEffect(() => {
if (isSuccess) {
setOpen(false)
setOpen(false);
}
}, [ isSuccess ])
}, [isSuccess]);
return (
<ListPageLayout className={styles.container}>
@ -302,49 +323,56 @@ const WebsiteDnsAccount = () => {
rowKey="id"
headerTitle={
<Space>
<Button key={'add'}
<Button
key={"add"}
onClick={() => {
form.resetFields()
form.resetFields();
form.setFieldsValue({
id: 0,
})
setOpen(true)
});
setOpen(true);
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加帐号')}</Button>
type={"primary"}
>
{t(`${i18nPrefix}.add`, "添加帐号")}
</Button>
</Space>
}
toolbar={{
search: {
loading: isFetching && !!search?.title,
onSearch: (value: string) => {
setSearch(prev => ({
setSearch((prev) => ({
...prev,
title: value
}))
title: value,
}));
},
allowClear: true,
onChange: (e) => {
setSearchKey(e.target?.value)
setSearchKey(e.target?.value);
},
value: searchKey,
placeholder: t(`${i18nPrefix}.placeholder`, '输入账号管理名称')
placeholder: t(`${i18nPrefix}.placeholder`, "输入账号管理名称"),
},
actions: [
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}>
<Tooltip key={"filter"} title={t(`${i18nPrefix}.filter.tooltip`, "高级查询")}>
<Badge count={getValueCount(search)}>
<Button
onClick={() => {
setFilterOpen(true)
setFilterOpen(true);
}}
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/>
icon={<FilterOutlined />}
shape={"circle"}
size={"small"}
/>
</Badge>
</Tooltip>,
<Divider type={'vertical'} key={'divider'}/>,
]
<Divider type={"vertical"} key={"divider"} />,
],
}}
scroll={{
// x: 2500,
y: 'calc(100vh - 290px)'
y: "calc(100vh - 290px)",
}}
search={false}
onRow={(record) => {
@ -353,9 +381,9 @@ const WebsiteDnsAccount = () => {
// 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id
}),
onClick: () => {
setWebsiteDnsAccount(record as any)
}
}
setWebsiteDnsAccount(record as any);
},
};
}}
dateFormatter="string"
loading={isLoading || isFetching}
@ -363,7 +391,7 @@ const WebsiteDnsAccount = () => {
columns={columns}
options={{
reload: () => {
refetch()
refetch();
},
}}
pagination={{
@ -374,17 +402,17 @@ const WebsiteDnsAccount = () => {
setSearch({
...search,
pageSize: size,
page: current
})
page: current,
});
},
onChange: (current, pageSize) => {
setSearch(prev => {
setSearch((prev) => {
return {
...prev,
page: current,
pageSize: pageSize,
}
})
};
});
},
}}
/>
@ -393,70 +421,80 @@ const WebsiteDnsAccount = () => {
shouldUpdate={false}
width={1000}
form={form}
layout={'vertical'}
layout={"vertical"}
scrollToFirstError={true}
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '账号管理编辑' : '账号管理添加')}
layoutType={'DrawerForm'}
title={t(
`${i18nPrefix}.title_${form.getFieldValue("id") !== 0 ? "edit" : "add"}`,
form.getFieldValue("id") !== 0 ? "账号管理编辑" : "账号管理添加",
)}
layoutType={"DrawerForm"}
open={open}
drawerProps={{
maskClosable: false,
}}
onOpenChange={(open) => {
setOpen(open)
setOpen(open);
}}
loading={isSubmitting}
// onValuesChange={(values) => {
//
// }}
//disable禁用,enable正常,syncing正在同步中
onFinish={async (values) => {
values.status = values.status ? 1 : 0
saveOrUpdate(values)
values.status = values.status ?'enable': 'disable';
saveOrUpdate(values);
}}
columns={columns as ProFormColumnsType[]}/>
columns={columns as ProFormColumnsType[]}
/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '账号管理高级查询')}
title={t(`${i18nPrefix}.filter.title`, "账号管理高级查询")}
grid={true}
shouldUpdate={false}
width={500}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
onOpenChange={(open) => {
setFilterOpen(open);
}}
layout={'vertical'}
layout={"vertical"}
scrollToFirstError={true}
layoutType={'DrawerForm'}
layoutType={"DrawerForm"}
drawerProps={{
maskClosable: false,
mask: false,
}}
submitter={{
searchConfig: {
resetText: t(`${i18nPrefix}.filter.reset`, '清空'),
submitText: t(`${i18nPrefix}.filter.submit`, '查询'),
resetText: t(`${i18nPrefix}.filter.reset`, "清空"),
submitText: t(`${i18nPrefix}.filter.submit`, "查询"),
},
onReset: () => {
filterForm.resetFields()
filterForm.resetFields();
},
render: (props,) => {
render: (props) => {
return (
<div style={{ textAlign: 'right' }}>
<div style={{ textAlign: "right" }}>
<Space>
<Button onClick={() => {
props.reset()
}}>{props.searchConfig?.resetText}</Button>
<Button type="primary"
<Button
onClick={() => {
props.reset();
}}
>
{props.searchConfig?.resetText}
</Button>
<Button
type="primary"
onClick={() => {
props.submit()
props.submit();
}}
>{props.searchConfig?.submitText}</Button>
>
{props.searchConfig?.submitText}
</Button>
</Space>
</div>
)
);
},
}}
// onValuesChange={(values) => {
//
@ -464,19 +502,18 @@ const WebsiteDnsAccount = () => {
onFinish={async (values) => {
//处理,变成数组
Object.keys(values).forEach(key => {
if (typeof values[key] === 'string' && values[key].includes(',')) {
values[key] = values[key].split(',')
Object.keys(values).forEach((key) => {
if (typeof values[key] === "string" && values[key].includes(",")) {
values[key] = values[key].split(",");
}
})
setSearch(values)
});
setSearch(values);
}}
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/>
columns={unSetColumnRules(columns.filter((item) => !item.hideInSearch) as ProFormColumnsType[])}
/>
</ListPageLayout>
)
}
);
};
export default WebsiteDnsAccount
export default WebsiteDnsAccount;

20
src/pages/websites/account/style.ts

@ -1,7 +1,7 @@
import { createStyles } from '../../../theme'
import { createStyles } from "../../../theme";
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsAccount-list-page`
const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsAccount-list-page`;
const container = css`
.ant-table-cell{
@ -18,9 +18,19 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
.ant-pro-table-highlight{
}
`
.loadingStyle{
display: flex;
align-items: center;
justify-content: flex-start; /* 将内容左对齐 */
}
.loadingStyle_table{
margin-left: 10px; /* 给内容和tip之间留一些间距 */
color : cadetblue;
}
`;
return {
container: cx(prefix, props?.className, container),
}
})
};
});

371
src/pages/websites/cert/apply.tsx

@ -1,232 +1,266 @@
import { t } from '@/i18n.ts'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { t } from "@/i18n.ts";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import {
algorithmTypes,
dnsConfigAtom,
dnsVerifyAtom, dnsVerifyOKAtom,
dnsVerifyAtom,
dnsVerifyOKAtom,
saveOrUpdateCertAtom,
} from '@/store/websites/cert.ts'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
Button,
Flex,
Form,
Input,
Select,
Space,
Table, Tooltip,
} from 'antd'
import google from '@/pages/websites/cert/assets/google.png'
import zerossl from '@/pages/websites/cert/assets/zerossl.png'
import lets_encrypt from '@/pages/websites/cert/assets/lets_encrypt.png'
import { useStyle } from './style'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { ColumnsType } from 'antd/es/table'
import { atomWithStorage } from 'jotai/utils'
import Copy from '@/components/copy'
import { InfoCircleOutlined, LoadingOutlined } from '@ant-design/icons'
} from "@/store/websites/cert.ts";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button, Flex, Form, Input, Progress, Select, Space, Table, Tooltip } from "antd";
import google from "@/pages/websites/cert/assets/google.png";
import zerossl from "@/pages/websites/cert/assets/zerossl.png";
import lets_encrypt from "@/pages/websites/cert/assets/lets_encrypt.png";
import { useStyle } from "./style";
import ListPageLayout from "@/layout/ListPageLayout.tsx";
import { ColumnsType } from "antd/es/table";
import { atomWithStorage } from "jotai/utils";
import Copy from "@/components/copy";
import { InfoCircleOutlined, LoadingOutlined } from "@ant-design/icons";
const i18nPrefix = 'cert.apply'
const i18nPrefix = "cert.apply";
const BrandSelect = (props: any) => {
const { styles, cx } = useStyle()
const [ value, setValue ] = useState(() => props.value)
const { styles, cx } = useStyle();
const [value, setValue] = useState(() => props.value);
useEffect(() => {
setValue(props.value)
}, [ props.value ])
setValue(props.value);
}, [props.value]);
const onChange = useCallback((val: string) => {
props.onChange?.(val)
}, [])
props.onChange?.(val);
}, []);
return <>
return (
<>
<Space className={styles.bandSelect}>
<Flex vertical={true}
onClick={() => onChange('Google')}
className={cx('band-normal', {
'band-active': value === 'Google'
})}>
<img src={google} style={{ height: '2rem' }}/>
<Flex
vertical={true}
onClick={() => onChange("Google")}
className={cx("band-normal", {
"band-active": value === "Google",
})}
>
<img src={google} style={{ height: "2rem" }} />
<span>Google Trust Services</span>
</Flex>
<Flex vertical={true}
onClick={() => onChange('ZeroSSL')}
className={cx('band-normal', {
'band-active': value === 'ZeroSSL'
})}>
<img src={zerossl} style={{ height: '2rem' }}/>
<Flex
vertical={true}
onClick={() => onChange("ZeroSSL")}
className={cx("band-normal", {
"band-active": value === "ZeroSSL",
})}
>
<img src={zerossl} style={{ height: "2rem" }} />
<span>ZeroSSL</span>
</Flex>
<Flex vertical={true}
onClick={() => onChange('Let\'s Encrypt')}
className={cx('band-normal', {
'band-active': value === 'Let\'s Encrypt'
})}>
<img src={lets_encrypt} style={{ height: '2rem' }}/>
<Flex
vertical={true}
onClick={() => onChange("Let's Encrypt")}
className={cx("band-normal", {
"band-active": value === "Let's Encrypt",
})}
>
<img src={lets_encrypt} style={{ height: "2rem" }} />
<span>Let's Encrypt</span>
</Flex>
</Space>
</>
}
);
};
const StatusTable = (props: { value: string }) => {
const { data, isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [ props.value ]))
const [data, setData] = useState<any>(null); // 临时状态来存储模拟数据
const { isFetching } = useAtomValue(useMemo(() => dnsConfigAtom(props.value), [props.value]));
const {
data: dnsVerifyStatus,
isFetching: isVerifyFetching,
refetch
} = useAtomValue(useMemo(() => dnsVerifyAtom(props.value, isFetching), [ props.value, isFetching ]))
refetch,
} = useAtomValue(useMemo(() => dnsVerifyAtom(props.value, isFetching), [props.value, isFetching]));
const setDnsVerifyOK = useSetAtom(dnsVerifyOKAtom)
const setDnsVerifyOK = useSetAtom(dnsVerifyOKAtom);
const timerRef = useRef<number>()
const timerRef = useRef<number>();
const columns = useMemo<ColumnsType>(() => {
return [
{
title: <>{t(`${i18nPrefix}.status.columns.status`, '状态')}<Tooltip
title={t(`${i18nPrefix}.status.columns.statusTip`, '正确配置DNS解析后,域名验证会自动通过')}><InfoCircleOutlined
style={{ paddingInlineStart: 5 }}/></Tooltip> </>,
dataIndex: 'status',
title: (
<>
{t(`${i18nPrefix}.status.columns.status`, "状态")}
<Tooltip title={t(`${i18nPrefix}.status.columns.statusTip`, "正确配置DNS解析后,域名验证会自动通过")}>
<InfoCircleOutlined style={{ paddingInlineStart: 5 }} />
</Tooltip>{" "}
</>
),
dataIndex: "status",
width: 100,
render: (_, record) => {
if (isFetching) {
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, '等待')}</span>
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, "等待")}</span>;
}
if (isVerifyFetching) {
//0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功
return <span><LoadingOutlined
style={{ paddingInlineEnd: 5 }}/>{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, '检测中')}</span>
return (
<span>
<LoadingOutlined style={{ paddingInlineEnd: 5 }} />
{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, "检测中")}
</span>
);
}
const dns = record.dns_name
const info = (dnsVerifyStatus as any)?.find((item) => item.dns_name === dns) as any
const dns = record.dns_name;
const info = (dnsVerifyStatus as any)?.find((item) => item.dns_name === dns) as any;
if (info) {
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.${info.status}`, `${info?.status_txt}`)}</span>
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.${info.status}`, `${info?.status_txt}`)}</span>;
}
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, '等待')}</span>
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, "等待")}</span>;
},
},
{
//服务商
title: t(`${i18nPrefix}.status.columns.name_servers`, '服务商'),
dataIndex: 'name_servers',
title: t(`${i18nPrefix}.status.columns.name_servers`, "服务商"),
dataIndex: "name_servers",
width: 150,
render(text) {
if (text) {
return <span className={'color-green1'}>{text}</span>
}
return <span className={'color-yellow'}></span>
return <span className={"color-green1"}>{text}</span>;
}
return <span className={"color-yellow"}></span>;
},
},
{
//域名
title: t(`${i18nPrefix}.status.columns.domain`, '域名'),
dataIndex: 'dns_name',
title: t(`${i18nPrefix}.status.columns.domain`, "域名"),
dataIndex: "dns_name",
width: 200,
},
{
//主机记录
title: t(`${i18nPrefix}.status.columns.record`, '主机记录'),
dataIndex: 'host',
title: t(`${i18nPrefix}.status.columns.record`, "主机记录"),
dataIndex: "host",
width: 200,
render: (text) => {
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />
}
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />;
},
},
{
//记录类型
title: t(`${i18nPrefix}.status.columns.record_type`, '记录类型'),
dataIndex: 'type',
title: t(`${i18nPrefix}.status.columns.record_type`, "记录类型"),
dataIndex: "type",
width: 100,
render: (text) => {
return <span className={'color-red'}>{text}</span>
}
return <span className={"color-red"}>{text}</span>;
},
},
{
//记录值
title: t(`${i18nPrefix}.status.columns.record_value`, '记录值'),
dataIndex: 'record_value',
title: t(`${i18nPrefix}.status.columns.record_value`, "记录值"),
dataIndex: "record_value",
width: 200,
render: (text) => {
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />
}
}
] as ColumnsType
}, [ isFetching, isVerifyFetching, dnsVerifyStatus ])
return <Copy {...{ text: text, tooltips: t(`actions.clickCopy`) }} />;
},
},
] as ColumnsType;
}, [isFetching, isVerifyFetching, dnsVerifyStatus]);
useEffect(() => {
// 模拟数据
const mockData = {
dns_list: [
{
status: 1,
name_servers: "Example NS",
dns_name: "example.com",
host: "example",
type: "A",
record_value: "192.168.1.1"
},
{
status: 3,
name_servers: "Another NS",
dns_name: "another.com",
host: "another",
type: "CNAME",
record_value: "cname.another.com"
}
]
};
setData(mockData);
if ((dnsVerifyStatus as any)?.every((item) => item.status === 9)) {
setDnsVerifyOK(true)
return
setDnsVerifyOK(true);
return;
}
timerRef.current = window.setInterval(() => {
if (isVerifyFetching) {
return
return;
}
//dnsVerifyStatus 如果所有status 为 9 则说明域名验证通过
// dnsVerifyStatus 如果所有status 为 9 则说明域名验证通过
if ((dnsVerifyStatus as any)?.every((item) => item.status === 9)) {
setDnsVerifyOK(true)
window.clearInterval(timerRef.current)
setDnsVerifyOK(true);
window.clearInterval(timerRef.current);
} else {
refetch()
refetch();
}
}, 2000)
}, 2000);
return () => {
window.clearInterval(timerRef.current)
}
window.clearInterval(timerRef.current);
};
}, [dnsVerifyStatus, isVerifyFetching]);
}, [ dnsVerifyStatus, isVerifyFetching ])
return <>
<div style={{ paddingBlock: 5, color: '#5a5a5a' }}>
<div>DNS解析记录 </div>
return (
<>
<div style={{ paddingBlock: 5, color: "#5a5a5a" }}>
<Flex vertical={false} align={"center"}>
<div>DNS解析记录</div>
<Button type="link"></Button>
</Flex>
<div> 1. </div>
<div> 2. 1~2</div>
</div>
<Table columns={columns}
<Table
columns={columns}
dataSource={(data as any)?.dns_list}
loading={isFetching}
size={'small'}
size={"small"}
pagination={false}
bordered={true}/>
bordered={true}
/>
</>
}
const domainsAtom = atomWithStorage<string>('domains', '')
);
};
const domainsAtom = atomWithStorage<string>("domains", "");
const Apply = ( ) => {
const { styles } = useStyle()
const [ form ] = Form.useForm()
const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom)
const [ domains, setDomains ] = useAtom(domainsAtom)
const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom)
const Apply = () => {
const { styles } = useStyle();
const [form] = Form.useForm();
const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom);
const [domains, setDomains] = useAtom(domainsAtom);
const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom);
useEffect(() => {
if (domains) {
form.setFieldsValue({
domains
})
domains,
});
}
}, [ domains ])
}, [domains]);
return (
<ListPageLayout
childrenClassName={styles.applyContent}
className={styles.applyContainer} title={t(`${i18nPrefix}.apply.title`, '证书申请')}>
className={styles.applyContainer}
title={t(`${i18nPrefix}.apply.title`, "证书申请")}
>
<Form
form={form}
{...{
@ -241,63 +275,84 @@ const Apply = ( ) => {
}}
onFinish={async (values) => {
if (dnsVerifyOK) {
saveOrUpdate(values)
saveOrUpdate(values);
}
}}
>
{/* <Form.Item*/}
{/* name={"domains"}*/}
{/* label={t(`${i18nPrefix}.columns.quota`, "证书额度")}*/}
{/* >*/}
{/* <Flex vertical gap="small" style={{ width: 300 }}>*/}
{/*<span>*/}
{/* <span style={{ color: "#00762B" }}>{t(`${i18nPrefix}.apply.remaining`, "剩余5张")}</span>*/}
{/* <span style={{ color: "#000000" }}>/ {t(`${i18nPrefix}.apply.total`, "共5张")}</span>*/}
{/*</span>*/}
{/* <Progress percent={30} strokeColor="#00762B" />*/}
{/* </Flex>*/}
{/* </Form.Item>*/}
<Form.Item
name={'domains'}
label={t(`${i18nPrefix}.columns.domains`, '域名')}
rules={[ { required: true, message: t(`${i18nPrefix}.columns.domains.required`, '请输入域名') } ]}
name={"domains"}
label={t(`${i18nPrefix}.columns.domains`, "域名")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.domains.required`, "请输入域名") }]}
>
<Input.TextArea rows={5}
<Input.TextArea
rows={5}
placeholder={`请输入域名,每行一个,支持泛解析域名;如:
*.google.com
*.a.baidu.com
hello.alibaba.com`}
onBlur={(e) => {
setDomains(e.target.value)
setDomains(e.target.value);
}}
/>
</Form.Item>
<Form.Item
label={t(`${i18nPrefix}.columns.type`, '域名验证')}
rules={[ { required: true, message: t(`${i18nPrefix}.columns.type`, '域名验证没有通过') } ]}
label={t(`${i18nPrefix}.columns.type`, "域名验证")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]}
>
<StatusTable value={domains}/>
<StatusTable value={domains} />
</Form.Item>
<Form.Item
name={'brand'}
label={t(`${i18nPrefix}.columns.brand`, '证书品牌')}
rules={[ {
name={"brand"}
label={t(`${i18nPrefix}.columns.brand`, "证书品牌")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.brand.required`, '请选择证书品牌')
} ]}
message: t(`${i18nPrefix}.columns.brand.required`, "请选择证书品牌"),
},
]}
>
<BrandSelect/>
<BrandSelect />
</Form.Item>
<Form.Item name={'algorithm'}
label={t(`${i18nPrefix}.columns.algorithm`, '加密方式')}
rules={[ {
<Form.Item
name={"algorithm"}
label={t(`${i18nPrefix}.columns.algorithm`, "加密方式")}
rules={[
{
required: true,
message: t(`${i18nPrefix}.columns.algorithm.required`, '请选择加密方式')
} ]}
message: t(`${i18nPrefix}.columns.algorithm.required`, "请选择加密方式"),
},
]}
>
<Select options={algorithmTypes}/>
<Select style={{ width: 120 }} options={algorithmTypes} />
</Form.Item>
<Form.Item name={'remark'}
label={t(`${i18nPrefix}.columns.remark`, '备注 ')}
>
<Input/>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}>
<Input style={{ width: 400 }} />
</Form.Item>
<Form.Item label={' '} colon={false}>
<Button type={'primary'}
disabled={!dnsVerifyOK || isSubmitting}
htmlType={'submit'}>{t(`${i18nPrefix}.apply.submit`, '提交申请')}</Button>
<Form.Item label={" "} colon={false}>
<Space>
<Button disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.prev`, "上一步")}
</Button>
<Button type={"primary"} disabled={!dnsVerifyOK || isSubmitting} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.submit`, "提交申请")}
</Button>
</Space>
</Form.Item>
</Form>
</ListPageLayout>
)
}
);
};
export default Apply
export default Apply;

525
src/pages/websites/domain/index.tsx

@ -1,213 +1,275 @@
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Divider, Space, Tooltip, Badge, Tag, Input, Flex, Select } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { useTranslation } from "@/i18n.ts";
import { Button, Form, Divider, Space, Tooltip, Badge, Tag, Input, Flex, Select, Spin } from "antd";
import { useAtom, useAtomValue } from "jotai";
import {
deleteWebsiteDomainAtom,
saveOrUpdateWebsiteDomainAtom, updateGroupWebsiteDomainAtom,
updateRemarkWebsiteDomainAtom, updateTagWebsiteDomainAtom,
saveOrUpdateWebsiteDomainAtom,
updateGroupWebsiteDomainAtom,
updateRemarkWebsiteDomainAtom,
updateTagWebsiteDomainAtom,
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, unSetColumnRules } from '@/utils'
import { Table as ProTable } from '@/components/table'
import { Link } from '@tanstack/react-router'
import Popconfirm from '@/components/popconfirm'
import { accountStatus, accountStatusColor } from '@/store/websites/dns_account.ts'
import Icon from '@/components/icon'
import { useDialog } from '@/components/dialog'
import { dnsListAtom } from '@/store/websites/dns.ts'
import ModalPro from '@/components/modal-pro'
import { domainGroupsAtom } from '@/store/websites/domain_groups.ts'
import NameServer from '@/pages/websites/domain/components/NameServer.tsx'
} 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, unSetColumnRules } from "@/utils";
import { Table as ProTable } from "@/components/table";
import { Link, useNavigate } from "@tanstack/react-router";
import Popconfirm from "@/components/popconfirm";
import { accountStatus, accountStatusColor } from "@/store/websites/dns_account.ts";
import Icon from "@/components/icon";
import { useDialog } from "@/components/dialog";
import { dnsListAtom } from "@/store/websites/dns.ts";
import ModalPro from "@/components/modal-pro";
import { domainGroupsAtom } from "@/store/websites/domain_groups.ts";
import NameServer from "@/pages/websites/domain/components/NameServer.tsx";
import Switch from "@/components/switch";
const i18nPrefix = 'websites.domain.list'
const i18nPrefix = "websites.domain.list";
const WebsiteDomain = () => {
const { styles, cx } = useStyle();
const { t } = useTranslation();
const [form] = Form.useForm();
const [filterForm] = Form.useForm();
const navigate = useNavigate();
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom);
const { mutate: updateRemark } = useAtomValue(updateRemarkWebsiteDomainAtom);
const { mutate: updateTags } = useAtomValue(updateTagWebsiteDomainAtom);
const { mutate: updateGroup } = useAtomValue(updateGroupWebsiteDomainAtom);
const [search, setSearch] = useAtom(websiteDomainSearchAtom);
const [currentWebsiteDomain, setWebsiteDomain] = useAtom(websiteDomainAtom);
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom);
const { data: groupData, isFetching: isGroupLoading } = useAtomValue(domainGroupsAtom);
const { styles, cx } = useStyle()
const { t } = useTranslation()
const [ form ] = Form.useForm()
const [ filterForm ] = Form.useForm()
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom)
const { mutate: updateRemark } = useAtomValue(updateRemarkWebsiteDomainAtom)
const { mutate: updateTags } = useAtomValue(updateTagWebsiteDomainAtom)
const { mutate: updateGroup } = useAtomValue(updateGroupWebsiteDomainAtom)
const [ search, setSearch ] = useAtom(websiteDomainSearchAtom)
const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom)
const { data: groupData, isFetching: isGroupLoading } = useAtomValue(domainGroupsAtom)
const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom)
const { data: dnsData } = useAtomValue(dnsListAtom)
const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom);
const { data: dnsData } = useAtomValue(dnsListAtom);
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const [open, setOpen] = useState(false);
const [openFilter, setFilterOpen] = useState(false);
const [searchKey, setSearchKey] = useState(search?.title);
const [ , dialog, openDialog ] = useDialog({
title: '编辑备注',
children: <Form form={form}>
<Form.Item name={'remark'}
label={t(`${i18nPrefix}.domain.columns.remark`, '备注信息')}>
<Input/>
const [, dialog, openDialog] = useDialog({
title: "编辑备注",
children: (
<Form form={form}>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.domain.columns.remark`, "备注信息")}>
<Input />
</Form.Item>
</Form>,
</Form>
),
onOk: () => {
const id = form.getFieldValue('id')
const remark = form.getFieldValue('remark')
return updateRemark({ remark, id } as any)
}
})
const id = form.getFieldValue("id");
const remark = form.getFieldValue("remark");
return updateRemark({ remark, id } as any);
},
});
const columns = useMemo(() => {
return [
{
title: 'ID',
dataIndex: 'id',
title: "ID",
dataIndex: "id",
hideInTable: true,
hideInSearch: true,
formItemProps: { hidden: true }
formItemProps: { hidden: true },
},
{
title: t(`${i18nPrefix}.columns.name`, '域名'),
dataIndex: 'name',
title: t(`${i18nPrefix}.columns.name`, "域名"),
dataIndex: "name",
width: 300,
fieldProps: {
style: { width: '100%' }
style: { width: "100%" },
},
render(_text, record) {
const edit = <Icon className={'hover'}
const edit = (
<Icon
className={"hover"}
onClick={() => {
form.setFieldsValue(record)
openDialog(record)
form.setFieldsValue(record);
openDialog(record);
}}
style={{ paddingBlockStart: 0 }} type={'EditTwo'} size={14}/>
return <Flex vertical={true} style={{ paddingInlineStart: 5 }}>
style={{ paddingBlockStart: 0 }}
type={"EditTwo"}
size={14}
/>
);
return (
<Flex vertical={true} style={{ paddingInlineStart: 5 }}>
<Space>
<Link to={`/websites/record?id=${record.id}`}>{record.name}</Link>
{edit}
</Space>
<Flex className={'color-gray'}>{record.remark}</Flex>
<Flex className={"color-gray"}>{record.remark}</Flex>
</Flex>
}
);
},
},
{
title: t(`${i18nPrefix}.columns.record_count`, '记录数'),
dataIndex: 'record_count',
title: t(`${i18nPrefix}.columns.record_count`, "记录数"),
dataIndex: "record_count",
hideInForm: true,
hideInSearch: true,
},
{
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'),
dataIndex: 'dns_account_id',
valueType: 'select',
title: t(`${i18nPrefix}.columns.dns_account_id`, "DNS账号"),
dataIndex: "dns_account_id",
valueType: "select",
fieldProps: {
options: dnsData?.rows?.map?.(item => {
options: dnsData?.rows?.map?.((item) => {
return {
data: item,
label: item.name,
value: item.id,
}
})
}
};
}),
},
},
{
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
title: t(`${i18nPrefix}.columns.status`, "状态"),
dataIndex: "status",
hideInForm: true,
valueType: 'select',
valueType: "select",
valueEnum: accountStatus,
render(_dom, record) {
const loading = [ 'pending', 'syncing' ].includes(record.status) ?
<Icon type={'LoadingTwo'} size={14} isLoading={true}/> : null
return <Tag
style={{ paddingInline: 5 }}
color={accountStatusColor[record.status]}>
<div style={{ display: 'inline-flex', justifyContent: 'center', alignItems: 'center' }}>
<span style={{ paddingInlineEnd: 3 }}> {t(`websites.common.status.${record.status!}`, record.status + '')}
switch (record.status) {
//active活动,pending等待,delete已经删除,disable禁用,syn正在同步
case "enable":
case "active":
return (
<Tag style={{ paddingInline: 5 }} color={accountStatusColor[record.status]}>
<div style={{ display: "inline-flex", justifyContent: "center", alignItems: "center" }}>
<span style={{ paddingInlineEnd: 3 }}>
{t(`websites.common.status.${record.status!}`, record.status + "")}
</span>
</div>
</Tag>
);
case "pending":
// return <Icon type={"LoadingTwo"} size={14} isLoading={true} />;
return (
<div className={cx("loadingStyle")}>
<Spin tip={t(`${i18nPrefix}.columns.pending`, "等待")} size="small"></Spin>
<div className={cx("loadingStyle_table")}>{t(`${i18nPrefix}.columns.syncing`, "等待")}</div>
</div>
);
case "delete":
case "disable":
return (
<Tag style={{ paddingInline: 5 }} color={accountStatusColor[record.status]}>
<div style={{ display: "inline-flex", justifyContent: "center", alignItems: "center" }}>
<span style={{ paddingInlineEnd: 3 }}>
{t(`websites.common.status.${record.status!}`, record.status + "")}
</span>
{loading}
</div>
</Tag>
);
case "syncing":
return (
<div className={cx("loadingStyle")}>
<Spin tip={t(`${i18nPrefix}.columns.syncing`, "同步中")} size="small"></Spin>
<div className={cx("loadingStyle_table")}>{t(`${i18nPrefix}.columns.syncing`, "同步中")}</div>
</div>
);
}
},
},
{
title: t(`${i18nPrefix}.columns.nameservers`, 'DNS服务器地址'),
dataIndex: 'nameservers',
title: t(`${i18nPrefix}.columns.nameservers`, "DNS服务器地址"),
dataIndex: "nameservers",
width: 150,
hideInSearch: true,
hideInForm: true,
render(_dom, record) {
return <NameServer data={record}/>
}
return <NameServer data={record} />;
},
},
{
title: t(`${i18nPrefix}.columns.created`, '创建时间'),
dataIndex: 'created',
title: t(`${i18nPrefix}.columns.created`, "创建时间"),
dataIndex: "created",
hideInSearch: true,
hideInForm: true,
},
{
title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option',
valueType: 'option',
fixed: 'right',
title: t(`${i18nPrefix}.columns.option`, "操作"),
key: "option",
valueType: "option",
fixed: "right",
render: (_, record) => [
<Link to={`/websites/record?id=${record.id}`}></Link>,
// <Link to={`/cert/record?id=${record.id}`}>解析设置</Link>,
/*<Action key="edit"
as={'a'}
<Action
key="edit"
as={"a"}
onClick={() => {
// form.setFieldsValue(record)
// setOpen(true)
}}>{t('actions.recordSet', '解析设置')}</Action>,*/
<Divider type={'vertical'}/>,
<Action key="sync"
as={'a'}
disabled={record.status === 'syncing'}
form.setFieldsValue(record);
setOpen(true);
}}
>
{t("actions.recordSet", "设置")}
</Action>,
<Divider type={"vertical"} />,
<Action
key="record"
as={"a"}
onClick={() => {
navigate({
to: `/cert/record`,
search: { id: record.id },
});
}}
>
{t("actions.recordSet", "记录")}
</Action>,
<Divider type={"vertical"} />,
<Action key="sync" as={"a"} disabled={record.status === "syncing"} onClick={() => {}}>
{t("actions.sync", "同步")}
</Action>,
}}>{t('actions.sync', '同步')}</Action>,
<Divider type={'vertical'}/>,
<Divider type={"vertical"} />,
<Popconfirm
key={'del_confirm'}
key={"del_confirm"}
disabled={isDeleting}
onConfirm={() => {
deleteWebsiteDomain([ record.id ])
deleteWebsiteDomain([record.id]);
}}
title={t('message.deleteConfirm')}>
{t('actions.delete', '删除')}
</Popconfirm>
]
}
] as ProColumns[]
}, [ isDeleting, currentWebsiteDomain, search, dnsData ])
title={t("message.deleteConfirm")}
>
{t("actions.delete", "删除")}
</Popconfirm>,
],
},
] as ProColumns[];
}, [isDeleting, currentWebsiteDomain, search, dnsData]);
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const dnsAccountId = queryParams.get("id");
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
if (dnsAccountId) {
setSearch((prev) => ({
...prev,
name: "",
dns_account_id: dnsAccountId,
}));
}
}, [location.search]);
}, [ search ])
useEffect(() => {
setSearchKey(search?.title);
filterForm.setFieldsValue(search);
}, [search]);
useEffect(() => {
if (isSuccess) {
setOpen(false)
setOpen(false);
}
}, [ isSuccess ])
}, [isSuccess]);
return (
<ListPageLayout className={styles.container}>
@ -215,102 +277,117 @@ const WebsiteDomain = () => {
rowKey="id"
headerTitle={
<Space>
<Button key={'add'}
<Button
key={"add"}
onClick={() => {
form.resetFields()
form.resetFields();
form.setFieldsValue({
id: 0,
})
setOpen(true)
});
setOpen(true);
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加域名')}</Button>
type={"primary"}
>
{t(`${i18nPrefix}.add`, "添加域名")}
</Button>
</Space>
}
tableAlertRender={(props) => {
return <Space>
return (
<Space>
<ModalPro
alterType={'confirm'}
title={'删除域名提示'}
content={<span><br/></span>}
alterType={"confirm"}
title={"删除域名提示"}
content={
<span>
<br />
</span>
}
onOk={() => {
deleteWebsiteDomain(props.selectedRows?.map(item => item.id))
deleteWebsiteDomain(props.selectedRows?.map((item) => item.id));
}}
>
<Button disabled={props.selectedRows?.length == 0}>{t('actions.delete', '删除')}</Button>
<Button disabled={props.selectedRows?.length == 0}>{t("actions.delete", "删除")}</Button>
</ModalPro>
<ModalPro
alterType={'dialog'}
title={t(`${i18nPrefix}.group.title`, '移动分组')}
content={<>
alterType={"dialog"}
title={t(`${i18nPrefix}.group.title`, "移动分组")}
content={
<>
<Form form={form}>
<Form.Item
name={'group_id'}
label={t(`${i18nPrefix}.group.columns.group`, '移动分组')}>
<Form.Item name={"group_id"} label={t(`${i18nPrefix}.group.columns.group`, "移动分组")}>
<Select
loading={isGroupLoading}
options={
[
options={[
{
label: t(`${i18nPrefix}.group.default`, '默认分组'),
label: t(`${i18nPrefix}.group.default`, "默认分组"),
value: 0,
},
...(groupData?.rows?.map(item => {
...(groupData?.rows?.map((item) => {
return {
label: item.name,
value: item.id,
}
}) ?? [])
]
}/>
};
}) ?? []),
]}
/>
</Form.Item>
</Form>
</>}
</>
}
onLoad={() => {
form.setFieldsValue({ group_id: 0 })
form.setFieldsValue({ group_id: 0 });
}}
onOk={() => {
const group_id = form.getFieldValue('group_id')
updateGroup({ id: props.selectedRows?.map(item => item.id), group_id })
const group_id = form.getFieldValue("group_id");
updateGroup({ id: props.selectedRows?.map((item) => item.id), group_id });
}}
>
<Button disabled={props.selectedRows?.length == 0}>{t(`${i18nPrefix}.actions.changeGroup`, '更换分组')}</Button>
<Button disabled={props.selectedRows?.length == 0}>
{t(`${i18nPrefix}.actions.changeGroup`, "更换分组")}
</Button>
</ModalPro>
</Space>
);
}}
tableAlertOptionRender={false}
toolbar={{
search: {
loading: isFetching && !!search?.title,
onSearch: (value: string) => {
setSearch(prev => ({
setSearch((prev) => ({
...prev,
title: value
}))
title: value,
}));
},
allowClear: true,
onChange: (e) => {
setSearchKey(e.target?.value)
setSearchKey(e.target?.value);
},
value: searchKey,
placeholder: t(`${i18nPrefix}.placeholder`, '输入域名管理名称')
placeholder: t(`${i18nPrefix}.placeholder`, "输入域名管理名称"),
},
actions: [
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}>
<Tooltip key={"filter"} title={t(`${i18nPrefix}.filter.tooltip`, "高级查询")}>
<Badge count={getValueCount(search)}>
<Button
onClick={() => {
setFilterOpen(true)
setFilterOpen(true);
}}
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/>
icon={<FilterOutlined />}
shape={"circle"}
size={"small"}
/>
</Badge>
</Tooltip>,
<Divider type={'vertical'} key={'divider'}/>,
]
<Divider type={"vertical"} key={"divider"} />,
],
}}
scroll={{
// x: 2500,
y: 'calc(100vh - 290px)'
y: "calc(100vh - 290px)",
}}
search={false}
onRow={(record) => {
@ -319,12 +396,12 @@ const WebsiteDomain = () => {
// 'ant-table-row-selected': currentWebsiteDomain?.id === record.id
}),
onClick: () => {
setWebsiteDomain(record)
}
}
setWebsiteDomain(record);
},
};
}}
rowSelection={{
type: 'checkbox',
type: "checkbox",
alwaysShowAlert: true,
}}
dateFormatter="string"
@ -333,7 +410,7 @@ const WebsiteDomain = () => {
columns={columns}
options={{
reload: () => {
refetch()
refetch();
},
}}
pagination={{
@ -344,17 +421,17 @@ const WebsiteDomain = () => {
setSearch({
...search,
pageSize: size,
page: current
})
page: current,
});
},
onChange: (current, pageSize) => {
setSearch(prev => {
setSearch((prev) => {
return {
...prev,
page: current,
pageSize: pageSize,
}
})
};
});
},
}}
/>
@ -363,86 +440,90 @@ const WebsiteDomain = () => {
shouldUpdate={false}
width={1000}
form={form}
layout={'vertical'}
layout={"vertical"}
scrollToFirstError={true}
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '域名管理编辑' : '域名管理添加')}
layoutType={'DrawerForm'}
title={t(
`${i18nPrefix}.title_${form.getFieldValue("id") !== 0 ? "edit" : "add"}`,
form.getFieldValue("id") !== 0 ? "域名管理编辑" : "域名管理添加",
)}
layoutType={"DrawerForm"}
open={open}
drawerProps={{
maskClosable: false,
}}
onOpenChange={(open) => {
setOpen(open)
setOpen(open);
}}
loading={isSubmitting}
onFinish={async (values) => {
saveOrUpdate(values)
saveOrUpdate(values);
}}
columns={columns as ProFormColumnsType[]}/>
columns={columns as ProFormColumnsType[]}
/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '域名管理高级查询')}
title={t(`${i18nPrefix}.filter.title`, "域名管理高级查询")}
grid={true}
shouldUpdate={false}
width={500}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
onOpenChange={(open) => {
setFilterOpen(open);
}}
layout={'vertical'}
layout={"vertical"}
scrollToFirstError={true}
layoutType={'DrawerForm'}
layoutType={"DrawerForm"}
drawerProps={{
maskClosable: false,
mask: false,
}}
submitter={{
searchConfig: {
resetText: t(`${i18nPrefix}.filter.reset`, '清空'),
submitText: t(`${i18nPrefix}.filter.submit`, '查询'),
resetText: t(`${i18nPrefix}.filter.reset`, "清空"),
submitText: t(`${i18nPrefix}.filter.submit`, "查询"),
},
onReset: () => {
filterForm.resetFields()
filterForm.resetFields();
},
render: (props,) => {
render: (props) => {
return (
<div style={{ textAlign: 'right' }}>
<div style={{ textAlign: "right" }}>
<Space>
<Button onClick={() => {
props.reset()
}}>{props.searchConfig?.resetText}</Button>
<Button type="primary"
<Button
onClick={() => {
props.reset();
}}
>
{props.searchConfig?.resetText}
</Button>
<Button
type="primary"
onClick={() => {
props.submit()
props.submit();
}}
>{props.searchConfig?.submitText}</Button>
>
{props.searchConfig?.submitText}
</Button>
</Space>
</div>
)
);
},
}}
onFinish={async (values) => {
//处理,变成数组
Object.keys(values).forEach(key => {
if (typeof values[key] === 'string' && values[key].includes(',')) {
values[key] = values[key].split(',')
Object.keys(values).forEach((key) => {
if (typeof values[key] === "string" && values[key].includes(",")) {
values[key] = values[key].split(",");
}
})
setSearch(values)
});
setSearch(values);
}}
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/>
{
dialog
}
columns={unSetColumnRules(columns.filter((item) => !item.hideInSearch) as ProFormColumnsType[])}
/>
{dialog}
</ListPageLayout>
)
}
);
};
export default WebsiteDomain
export default WebsiteDomain;

10
src/pages/websites/domain/style.ts

@ -18,6 +18,16 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
.ant-pro-table-highlight{
}
.loadingStyle{
display: flex;
align-items: center;
justify-content: flex-start; /* 将内容左对齐 */
}
.loadingStyle_table{
margin-left: 10px; /* 给内容和tip之间留一些间距 */
color : cadetblue;
}
`
return {

52
src/pages/websites/mytest/index.tsx

@ -0,0 +1,52 @@
import React, { useMemo } from 'react';
import { ProTable } from '@ant-design/pro-components';
import type { ProColumns } from '@ant-design/pro-components';
type DataType = {
key: number;
name: string;
age: number;
address: string;
};
const data: DataType[] = [
{ key: 1, name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park' },
{ key: 2, name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park' },
{ key: 3, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park' },
];
const MyTable: React.FC = () => {
const columns: ProColumns<DataType>[] = useMemo(() => [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
], []);
const memoizedData = useMemo(() => data, []);
return (
<ProTable<DataType>
columns={columns}
dataSource={memoizedData}
rowKey="key"
search={false}
pagination={{
pageSize: 5,
}}
/>
);
};
export default MyTable;

4
src/pages/websites/record/index.tsx

@ -9,7 +9,7 @@ import {
websiteDnsRecordsAtom,
websiteDnsRecordssAtom,
websiteDnsRecordsSearchAtom,
} from '@/store/websites/record'
} from '@/store/websites/record.ts'
import { useEffect, useMemo, useRef, useState } from 'react'
import Action from '@/components/action/Action.tsx'
import {
@ -18,7 +18,7 @@ import {
ProFormColumnsType,
} from '@ant-design/pro-components'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { useStyle } from './style'
import { useStyle } from './style.ts'
import { FilterOutlined, MinusCircleOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { getValueCount, unSetColumnRules } from '@/utils'
import { Table as ProTable } from '@/components/table'

2
src/service/website/domain_group.ts

@ -2,7 +2,7 @@ import { createCURD } from '@/service/base.ts'
import { WebSite } from '@/types/website/domain_group'
const domain_group = {
...createCURD<any, WebSite.IDomainGroup >('/website/group'),
...createCURD<any, WebSite.IDomainGroup >('/cert/dns_group'),
}
export default domain_group

26
src/service/websites.ts

@ -16,6 +16,10 @@ const websitesServ = {
dnsVerify: async (params: any) => {
return request.post<any, any>('/website/cert/dns_verify', params)
},
//cert-apply
certApply: async (params: any) => {
return request.post<any, any>('/website/cert/dns_verify', params)
},
},
ssl: {
@ -31,9 +35,9 @@ const websitesServ = {
...createCURD<any, WebSite.IAcmeAccount>('/website/acme')
},
dns: {
...createCURD<any, WebSite.IDnsAccount>('/website/dns_account'),
...createCURD<any, WebSite.IDnsAccount>('/cert/dns_account'),
sync: async (id: any) => {
return request.post<any, WebSite.IDnsAccount>('/website/dns_account/sync', { id: id })
return request.post<any, WebSite.IDnsAccount>('/cert/dns_account/sync', { id: id })
}
},
ca: {
@ -43,36 +47,36 @@ const websitesServ = {
},
},
domain: {
...createCURD<any, IWebsiteDomain>('/website/domain'),
...createCURD<any, IWebsiteDomain>('/cert/domain'),
//remark
remark: async (params: { id: string, remark: string }) => {
return request.post<any, any>('/website/domain/remark', params)
return request.post<any, any>('/cert/domain/remark', params)
},
//tag
tag: async (params: { id: string, tags: string }) => {
return request.post<any, any>('/website/domain/tag', params)
return request.post<any, any>('/cert/domain/tag', params)
},
//binding
binding: async (params: { id: string, user_id: string }) => {
return request.post<any, any>('/website/domain/binding', params)
return request.post<any, any>('/cert/domain/binding', params)
},
//group
group: async (params: { id: string[], group_id: string }) => {
return request.post<any, any>('/website/domain/group', params)
return request.post<any, any>('/cert/domain/group', params)
},
describeDomainNS: async (params: { id: number }) => {
return request.post<INameServer, any>('/website/domain/describe_domain_ns', params)
return request.post<INameServer, any>('/cert/domain/describe_domain_ns', params)
},
},
record: {
...createCURD<any, IWebsiteDnsRecords>('/website/dns_records'),
...createCURD<any, IWebsiteDnsRecords>('/cert/dns_records'),
//
},
dnsAccount: {
...createCURD<any, IWebsiteDnsAccount>('/website/dns_account'),
...createCURD<any, IWebsiteDnsAccount>('/cert/dns_account'),
sync: async (params: IWebsiteDnsAccount) => {
return request.post<any, IWebsiteDnsAccount>('/website/dns_account/sync', params)
return request.post<any, IWebsiteDnsAccount>('/cert/dns_account/sync', params)
}
},
}

25
src/store/websites/cert.ts

@ -46,6 +46,31 @@ export const certPageAtom = atom<IPage>({
page: 1,
})
//certApple
export const certAppleCertAtom = atomWithMutation<IApiResult, ICertificate>((get) => {
return {
mutationKey: [ 'appleCert' ],
mutationFn: async (data) => {
//data.status = data.status ? '1' : '0'
return await websitesServ.cert.certApply(data)
},
onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
//更新列表
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix
get(queryClientAtom).invalidateQueries({ queryKey: [ 'certs', get(certSearchAtom) ] })
return res
}
}
})
export const certsAtom = atomWithQuery((get) => {
return {
queryKey: [ 'certs', get(certSearchAtom) ],

3
vite.config.ts

@ -11,6 +11,8 @@ import jotaiReactRefresh from 'jotai/babel/plugin-react-refresh'
const proxyMap = {
'/api/v1/package': 'http://154.88.7.8:45321',
'/api/v1/movie': 'http://47.113.117.106:10000',
//'/api/v1/certold': 'http://192.168.31.41:8000',
'/api/v1/cert': 'http://127.0.0.1:8000',
} as Record<any, string>
const proxyConfig = Object.keys(proxyMap).reduce((acc, key) => {
@ -29,6 +31,7 @@ export default defineConfig(({ mode }) => {
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
// @ts-ignore fix process
const env = loadEnv(mode, process.cwd(), '')
// 你可以在这里打印出 env 变量来检查加载的内容
return {
//定义别名的路径
resolve: {

Loading…
Cancel
Save