Browse Source

增加证书页面申请接口

main
lk 3 months ago
parent
commit
e23d1051e9
  1. 70
      package.json
  2. 14
      src/App.css
  3. 2
      src/layout/RootLayout.tsx
  4. 273
      src/pages/websites/cert/apply.tsx
  5. 33
      src/service/websites.ts
  6. 295
      src/store/websites/cert.ts
  7. 1
      vite.config.ts

70
package.json

@ -12,58 +12,58 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/cssinjs": "^1.21.1", "@ant-design/cssinjs": "^1.21.1",
"@ant-design/icons": "^5.3.6",
"@ant-design/pro-components": "^2.7.0",
"@ant-design/icons": "^5.4.0",
"@ant-design/pro-components": "^2.7.15",
"@ant-design/pro-layout": "^7.19.12", "@ant-design/pro-layout": "^7.19.12",
"@ant-design/pro-provider": "^2.14.9", "@ant-design/pro-provider": "^2.14.9",
"@formily/antd-v5": "^1.2.0",
"@formily/core": "^2.3.1",
"@formily/react": "^2.3.1",
"@formily/antd-v5": "^1.2.3",
"@formily/core": "^2.3.2",
"@formily/react": "^2.3.2",
"@icon-park/react": "^1.4.2", "@icon-park/react": "^1.4.2",
"@melloware/react-logviewer": "^5.2.0", "@melloware/react-logviewer": "^5.2.0",
"@tanstack/query-core": "^5.52.0",
"@tanstack/react-query": "^5.52.1",
"@tanstack/react-router": "^1.50.0",
"antd": "^5.16.1",
"@tanstack/query-core": "^5.54.1",
"@tanstack/react-query": "^5.54.1",
"@tanstack/react-router": "^1.53.1",
"antd": "^5.20.5",
"antd-style": "^3.6.2", "antd-style": "^3.6.2",
"axios": "^1.6.8",
"bunshi": "^2.1.4",
"dayjs": "^1.11.10",
"axios": "^1.7.7",
"bunshi": "^2.1.5",
"dayjs": "^1.11.13",
"fast-copy": "^3.0.2", "fast-copy": "^3.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"i18next": "^23.11.2",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"jotai": "^2.8.0",
"jotai": "^2.9.3",
"jotai-devtools": "^0.9.1", "jotai-devtools": "^0.9.1",
"jotai-scope": "^0.5.1",
"jotai-tanstack-query": "^0.8.5",
"jotai-scope": "^0.5.2",
"jotai-tanstack-query": "^0.8.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.1.0",
"re-resizable": "^6.9.17",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.3",
"react-if": "^4.1.5", "react-if": "^4.1.5",
"react-layout-kit": "^1.9.0", "react-layout-kit": "^1.9.0",
"react-rnd": "^10.4.2-test2",
"react-use": "^17.5.0",
"throttle-debounce": "^5.0.0",
"react-rnd": "^10.4.12",
"react-use": "^17.5.1",
"throttle-debounce": "^5.0.2",
"use-merge-value": "^1.2.0", "use-merge-value": "^1.2.0",
"wonka": "^6.3.4" "wonka": "^6.3.4"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/router-devtools": "^1.26.20",
"@tanstack/router-vite-plugin": "^1.26.16",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"@tanstack/router-devtools": "^1.53.1",
"@tanstack/router-vite-plugin": "^1.54.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.11",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-plugin-mock": "^3.0.1"
"typescript": "^5.5.4",
"vite": "^5.4.3",
"vite-plugin-mock": "^3.0.2"
} }
} }

14
src/App.css

@ -7,12 +7,10 @@
display: inherit; display: inherit;
line-height: 28px; line-height: 28px;
} }
} }
.top-breadcrumb { .top-breadcrumb {
.item { .item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -28,7 +26,6 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: #1c7ed6; color: #1c7ed6;
} }
} }
@ -50,16 +47,19 @@
color: green; color: green;
} }
.color-green1{
color: rgb(18 185 128 / 1 );
.color-green1 {
color: rgb(18 185 128 / 1);
}
.color-blue {
color: #1c7ed6;
} }
.color-red { .color-red {
color: #F56C6C;
color: #f56c6c;
} }
.color-yellow { .color-yellow {
color: rgb(250 145 0)
color: rgb(250 145 0);
} }
.text-bold { .text-bold {

2
src/layout/RootLayout.tsx

@ -127,7 +127,7 @@ export default () => {
color: '#00000012', color: '#00000012',
size: 17, size: 17,
}, },
zIndex: 1009,
zindex: 1009,
} as any } as any
} style={{ width: '100vw', height: '100vh' }}> } style={{ width: '100vw', height: '100vh' }}>
<ProLayout <ProLayout

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

@ -2,35 +2,44 @@ import { t } from "@/i18n.ts";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { import {
algorithmTypes, algorithmTypes,
dnsConfigAtom,
dnsVerifyAtom,
dnsVerifyOKAtom,
saveOrUpdateCertAtom,
certAddCnameAtom,
checkDomainAtom,
applyTxtCertificateAtom,
Req_ApplyTxtCertificate,
} from "@/store/websites/cert.ts"; } from "@/store/websites/cert.ts";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {Button, Flex, Form, Input, message, Progress, Radio, Select, Space, Steps, Table, Tooltip} from "antd";
import { Button, Flex, Form, Input, message, Radio, Select, Space, Steps, Table, Tooltip } from "antd";
import google from "@/pages/websites/cert/assets/google.png"; import google from "@/pages/websites/cert/assets/google.png";
import zerossl from "@/pages/websites/cert/assets/zerossl.png"; import zerossl from "@/pages/websites/cert/assets/zerossl.png";
import lets_encrypt from "@/pages/websites/cert/assets/lets_encrypt.png"; import lets_encrypt from "@/pages/websites/cert/assets/lets_encrypt.png";
import { useStyle } from "./style"; import { useStyle } from "./style";
import ListPageLayout from "@/layout/ListPageLayout.tsx"; import ListPageLayout from "@/layout/ListPageLayout.tsx";
import { ColumnsType } from "antd/es/table"; import { ColumnsType } from "antd/es/table";
import { atomWithStorage } from "jotai/utils";
import { atomWithStorage, RESET } from "jotai/utils";
import Copy from "@/components/copy"; import Copy from "@/components/copy";
import { CopyOutlined, InfoCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import {
ClusterOutlined,
CopyOutlined,
InfoCircleOutlined,
LoadingOutlined,
LoginOutlined,
TagOutlined,
} from "@ant-design/icons";
import { useQueryClient } from "@tanstack/react-query";
const i18nPrefix = "cert.apply"; const i18nPrefix = "cert.apply";
const BrandSelect = (props: any) => {
const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTxtCertificateData }) => {
const { styles, cx } = useStyle(); const { styles, cx } = useStyle();
const [value, setValue] = useState(() => props.value);
useEffect(() => { useEffect(() => {
setValue(props.value);
onChange(props.value);
}, [props.value]); }, [props.value]);
const onChange = useCallback((val: string) => { const onChange = useCallback((val: string) => {
props.onChange?.(val);
props.setApplyTxtCertificateData((prevState) => ({
...prevState,
acme_type: val,
}));
}, []); }, []);
return ( return (
@ -40,7 +49,7 @@ const BrandSelect = (props: any) => {
vertical={true} vertical={true}
onClick={() => onChange("Google")} onClick={() => onChange("Google")}
className={cx("band-normal", { className={cx("band-normal", {
"band-active": value === "Google" || !props.value,
"band-active": props.applyTxtCertificateData.acme_type === "Google" || !props.value,
})} })}
> >
<img src={google} style={{ height: "2rem" }} /> <img src={google} style={{ height: "2rem" }} />
@ -50,7 +59,7 @@ const BrandSelect = (props: any) => {
vertical={true} vertical={true}
onClick={() => onChange("ZeroSSL")} onClick={() => onChange("ZeroSSL")}
className={cx("band-normal", { className={cx("band-normal", {
"band-active": value === "ZeroSSL",
"band-active": props.applyTxtCertificateData.acme_type === "ZeroSSL",
})} })}
> >
<img src={zerossl} style={{ height: "2rem" }} /> <img src={zerossl} style={{ height: "2rem" }} />
@ -60,7 +69,7 @@ const BrandSelect = (props: any) => {
vertical={true} vertical={true}
onClick={() => onChange("Let's Encrypt")} onClick={() => onChange("Let's Encrypt")}
className={cx("band-normal", { className={cx("band-normal", {
"band-active": value === "Let's Encrypt",
"band-active": props.applyTxtCertificateData.acme_type === "Let's Encrypt",
})} })}
> >
<img src={lets_encrypt} style={{ height: "2rem" }} /> <img src={lets_encrypt} style={{ height: "2rem" }} />
@ -71,27 +80,40 @@ const BrandSelect = (props: any) => {
); );
}; };
const StatusTable = (props: { value: string }) => {
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]));
const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertificateData }) => {
// const [data, setData] = useState<any>(null); // 临时状态来存储模拟数据
const [forceUpdate, setForceUpdate] = useState(0);
const { data, isFetching: isVerifyFetching } = useAtomValue(
useMemo(() => checkDomainAtom(props.value, forceUpdate === 0), [props.value, forceUpdate]),
);
const refetch = () => {
setForceUpdate((prev) => prev + 1);
};
const setDnsVerifyOK = useSetAtom(dnsVerifyOKAtom);
const { mutate: addCnameFun, isPending: addCnamePending, isSuccess } = useAtomValue(certAddCnameAtom);
const handleAddCname = async (info: any) => {
addCnameFun(info);
};
// const {
// data: dnsVerifyStatus,
// isFetching: isVerifyFetching,
// refetch,
// } = useAtomValue(useMemo(() => dnsVerifyAtom(props.value, isFetching), [props.value, isFetching]));
//
// const setDnsVerifyOK = useSetAtom(dnsVerifyOKAtom);
const timerRef = useRef<number>(); const timerRef = useRef<number>();
const handleCopy = (str:string) => {
navigator.clipboard.writeText(str).then(() => {
message.info('复制成功!');
}).catch(err => {
const handleCopy = (str: string) => {
navigator.clipboard
.writeText(str)
.then(() => {
message.info("复制成功!");
})
.catch((err) => {
message.info(err); message.info(err);
}); });
};
};
const columns = useMemo<ColumnsType>(() => { const columns = useMemo<ColumnsType>(() => {
return [ return [
@ -105,27 +127,61 @@ const StatusTable = (props: { value: string }) => {
</> </>
), ),
dataIndex: "status", dataIndex: "status",
width: 100,
width: 150,
render: (_, record) => { render: (_, record) => {
if (isFetching) {
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>
);
}
const dns = record.dns_name; const dns = record.dns_name;
const info = (dnsVerifyStatus as any)?.find((item) => item.dns_name === dns) as any;
const info = (data as any)?.dns_list?.find((item) => item.dns_name === dns) as any;
if (info) { if (info) {
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.${info.status}`, `${info?.status_txt}`)}</span>;
if (info.is_self === true) {
return (
// <Button
// size="small"
// type="text"
// className={"color-blue"}
// // icon={<ClusterOutlined />}
// style={{ width: "100%", textAlign: "left" }}
// // onClick={() => handleCopy(text)}
// >
// 自动注册
// </Button>
<span
style={{
width: "100%",
textAlign: "right",
color: "#1c7ed6",
cursor: "pointer",
}}
onClick={() => {
handleAddCname(info);
}}
>
<TagOutlined style={{ paddingInlineEnd: 5 }} />
</span>
);
} else {
if (isVerifyFetching) {
//0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功
return (
<span style={{ width: "100%", textAlign: "right" }}>
<LoadingOutlined style={{ paddingInlineEnd: 5 }} />
{t(`${i18nPrefix}.actions.dnsVerifyStatus.3`, "检测中")}
</span>
);
}
//0,等待 1,域名OK,2,域名分析错误,3:检测中 4:检测成功,匹配失败 5:检测失败,9:检测成功
return (
<span style={{ width: "100%", textAlign: "left" }}>
{t(`${i18nPrefix}.actions.dnsVerifyStatus.${info.status}`, `${info?.status_txt}`)}
</span>
);
}
} }
return <span>{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, "等待")}</span>;
return (
<span style={{ width: "100%", textAlign: "left" }}>
{t(`${i18nPrefix}.actions.dnsVerifyStatus.0`, "等待")}
</span>
);
}, },
}, },
{ {
@ -147,7 +203,14 @@ const StatusTable = (props: { value: string }) => {
width: 200, width: 200,
render(text) { render(text) {
return ( return (
<Button size="small" type="text" className={"color-green1"} icon={<CopyOutlined />} iconPosition={"end"} onClick={() => handleCopy(text)}>
<Button
size="small"
type="text"
className={"color-green1"}
icon={<CopyOutlined />}
iconPosition={"end"}
onClick={() => handleCopy(text)}
>
{text} {text}
</Button> </Button>
); );
@ -178,42 +241,31 @@ const StatusTable = (props: { value: string }) => {
width: 200, width: 200,
render: (text) => { render: (text) => {
return ( return (
<Button size="small" type="text" className={"color-green1"} icon={<CopyOutlined />} iconPosition={"end"} onClick={() => handleCopy(text)}>
<Button
size="small"
type="text"
className={"color-green1"}
icon={<CopyOutlined />}
iconPosition={"end"}
onClick={() => handleCopy(text)}
>
{t(`actions.clickCopy`)} {t(`actions.clickCopy`)}
</Button> </Button>
); );
}, },
}, },
] as ColumnsType; ] as ColumnsType;
}, [isFetching, isVerifyFetching, dnsVerifyStatus]);
}, [isVerifyFetching, data]);
useEffect(() => { 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);
if ((data as any)?.check_ok === true) {
props.setCurrentStep(2);
props.setApplyTxtCertificateData((prevState) => ({
...prevState,
dns_list: (data as any).dns_list,
}));
return; return;
} }
@ -222,8 +274,15 @@ const StatusTable = (props: { value: string }) => {
return; return;
} }
// dnsVerifyStatus 如果所有status 为 9 则说明域名验证通过 // dnsVerifyStatus 如果所有status 为 9 则说明域名验证通过
if ((dnsVerifyStatus as any)?.every((item) => item.status === 9)) {
setDnsVerifyOK(true);
// if ((data as any)?.dns_list?.every((item) => item.status === 9)) {
// props.setCurrentStep(2);
// window.clearInterval(timerRef.current);
if ((data as any)?.check_ok === true) {
props.setCurrentStep(2);
props.setApplyTxtCertificateData((prevState) => ({
...prevState,
dns_list: (data as any).dns_list,
}));
window.clearInterval(timerRef.current); window.clearInterval(timerRef.current);
} else { } else {
refetch(); refetch();
@ -233,7 +292,7 @@ const StatusTable = (props: { value: string }) => {
return () => { return () => {
window.clearInterval(timerRef.current); window.clearInterval(timerRef.current);
}; };
}, [dnsVerifyStatus, isVerifyFetching]);
}, [data, isVerifyFetching]);
return ( return (
<> <>
@ -248,7 +307,7 @@ const StatusTable = (props: { value: string }) => {
<Table <Table
columns={columns} columns={columns}
dataSource={(data as any)?.dns_list} dataSource={(data as any)?.dns_list}
loading={isFetching}
loading={isVerifyFetching}
size={"small"} size={"small"}
pagination={false} pagination={false}
bordered={true} bordered={true}
@ -276,6 +335,9 @@ const DomainsInput = (props: { setDomains; currentDomainMod; setCurrentDomainMod
<Input <Input
disabled={props.currentStep !== 0} disabled={props.currentStep !== 0}
placeholder="请输入域名,支持泛解析域名。如果为多个域名注册到一个证书,域名之间用<逗号(英文输入)>分割。如:a.com,*.b.com" placeholder="请输入域名,支持泛解析域名。如果为多个域名注册到一个证书,域名之间用<逗号(英文输入)>分割。如:a.com,*.b.com"
onBlur={(e) => {
props.setDomains(e.target.value);
}}
/> />
)} )}
{props.currentDomainMod === "multiple" && ( {props.currentDomainMod === "multiple" && (
@ -300,11 +362,30 @@ const domainsAtom = atomWithStorage<string>("domains", "");
const Apply = () => { const Apply = () => {
const { styles } = useStyle(); const { styles } = useStyle();
const [form] = Form.useForm(); const [form] = Form.useForm();
const { mutate: saveOrUpdate, isPending: isSubmitting } = useAtomValue(saveOrUpdateCertAtom);
const {
mutate: applyTxtCertificateFun,
isPending: applyTxtCertificatePending,
isSuccess: applyTxtCertificateIsSuccess,
} = useAtomValue(applyTxtCertificateAtom);
const [domains, setDomains] = useAtom(domainsAtom); const [domains, setDomains] = useAtom(domainsAtom);
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
const [currentDomainMod, setCurrentDomainMod] = useState<"single" | "multiple">("single"); const [currentDomainMod, setCurrentDomainMod] = useState<"single" | "multiple">("single");
const dnsVerifyOK = useAtomValue(dnsVerifyOKAtom);
const [applyTxtCertificateData, setApplyTxtCertificateData] = useState<Req_ApplyTxtCertificate>({
is_sync: true,
acme_type: "",
key_rsa: "",
dns_list: [],
});
const handleAlgorithmChange = (value: string) => {
setApplyTxtCertificateData((prevState) => ({
...prevState,
key_rsa: value,
}));
};
const applyTxtCertificateClick = () => {
applyTxtCertificateFun(applyTxtCertificateData);
};
useEffect(() => { useEffect(() => {
if (domains) { if (domains) {
@ -313,7 +394,6 @@ const Apply = () => {
}); });
} }
}, [domains]); }, [domains]);
return ( return (
<ListPageLayout <ListPageLayout
childrenClassName={styles.applyContent} childrenClassName={styles.applyContent}
@ -350,9 +430,9 @@ const Apply = () => {
} }
}} }}
onFinish={async (values) => { onFinish={async (values) => {
if (dnsVerifyOK) {
saveOrUpdate(values);
}
// if (dnsVerifyOK) {
// saveOrUpdate(values);
// }
}} }}
> >
{/* <Form.Item*/} {/* <Form.Item*/}
@ -385,7 +465,11 @@ const Apply = () => {
label={t(`${i18nPrefix}.columns.type`, "域名验证")} label={t(`${i18nPrefix}.columns.type`, "域名验证")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]} rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]}
> >
<StatusTable value={domains} />
<StatusTable
value={domains}
setCurrentStep={setCurrentStep}
setApplyTxtCertificateData={setApplyTxtCertificateData}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -398,7 +482,11 @@ const Apply = () => {
}, },
]} ]}
> >
<BrandSelect value="Google" />
<BrandSelect
value="Google"
applyTxtCertificateData={applyTxtCertificateData}
setApplyTxtCertificateData={setApplyTxtCertificateData}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={"algorithm"} name={"algorithm"}
@ -410,7 +498,7 @@ const Apply = () => {
}, },
]} ]}
> >
<Select style={{ width: 120 }} options={algorithmTypes} />
<Select style={{ width: 120 }} options={algorithmTypes} onChange={handleAlgorithmChange} />
</Form.Item> </Form.Item>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}> <Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}>
<Input style={{ width: 400 }} /> <Input style={{ width: 400 }} />
@ -420,19 +508,24 @@ const Apply = () => {
<Form.Item label={" "} colon={false}> <Form.Item label={" "} colon={false}>
<Space> <Space>
{currentStep !== 0 && ( {currentStep !== 0 && (
<Button type="primary" onClick={() => setCurrentStep(currentStep - 1)} htmlType={"submit"}>
<Button type="primary" onClick={() => setCurrentStep(0)} htmlType={"submit"}>
{t(`${i18nPrefix}.apply.prev`, "上一步")} {t(`${i18nPrefix}.apply.prev`, "上一步")}
</Button> </Button>
)} )}
{currentStep === 0 && ( {currentStep === 0 && (
<Button type="primary" onClick={() => setCurrentStep(currentStep + 1)} htmlType={"submit"}>
<Button type="primary" onClick={() => setCurrentStep(currentStep + 1)}>
{t(`${i18nPrefix}.apply.next`, "下一步")} {t(`${i18nPrefix}.apply.next`, "下一步")}
</Button> </Button>
)} )}
{currentStep !== 0 && ( {currentStep !== 0 && (
<Button type="primary" htmlType={"submit"} disabled={currentStep === 1}>
<Button
type="primary"
htmlType={"submit"}
disabled={currentStep === 1}
onClick={applyTxtCertificateClick}
>
{t(`${i18nPrefix}.apply.submit`, "提交申请")} {t(`${i18nPrefix}.apply.submit`, "提交申请")}
</Button> </Button>
)} )}

33
src/service/websites.ts

@ -7,19 +7,32 @@ import { IWebsiteDnsAccount } from '@/types/website/dns_account'
const websitesServ = { const websitesServ = {
cert: { cert: {
...createCURD<any, ICertificate>('/website/cert'),
//dns_config
dnsConfig: async (params: any) => {
return request.post<any, any>('/website/cert/dns_config', params)
...createCURD<any, ICertificate>("/website/cert"),
// 发起域名检测
checkDomain: async (params: any) => {
return request.post<any, any>("/cert/apply/dns_config", params);
},
// 证书续签
renewCertificate: async (params: any) => {
return request.post<any, any>("/website/cert/renew_certificate", params);
},
// 添加记录
addCnameCertificate: async (params: any) => {
return request.post<any, any>("/cert/apply/add/cname", params);
}, },
//dns_verify
dnsVerify: async (params: any) => {
return request.post<any, any>('/website/cert/dns_verify', params)
// 下载证书
downloadCertificate: async (params: any) => {
return request.post<any, any>("/website/cert/download_certificate", params);
}, },
//cert-apply
certApply: async (params: any) => {
return request.post<any, any>('/website/cert/dns_verify', params)
// 获取证书申请日志
getCertificateLogs: async (params: any) => {
return request.get<any, any>("/website/cert/get_certificate_logs", { params });
}, },
applyTxtCertificate: async (params: any) => {
return request.post<any, any>("/cert/apply/resolve", params);
},
}, },
ssl: { ssl: {

295
src/store/websites/cert.ts

@ -1,185 +1,218 @@
import { atom } from 'jotai'
import { IApiResult, IPage } from '@/global'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import { message } from 'antd'
import { t } from 'i18next'
import websitesServ from '@/service/websites.ts'
type SearchParams = IPage & {
name?: string
}
import { atom } from "jotai";
import { IApiResult, IPage } from "@/global";
import { atomWithMutation, atomWithQuery, queryClientAtom } from "jotai-tanstack-query";
import { message } from "antd";
import { t } from "i18next";
import websitesServ from "@/service/websites.ts";
export type Req_SearchParams = IPage & {
name?: string;
};
export type Req_AddCname = {
is_sync: boolean;
dns_list: ICertificate[];
};
export type Req_ApplyTxtCertificate = {
is_sync: boolean;
acme_type: string;
key_rsa: string;
dns_list: ICertificate[];
};
export const bandTypes = [ export const bandTypes = [
{ label: 'Google', value: 'Google' },
{ label: 'ZeroSSL', value: 'ZeroSSL' },
{ label: 'Let\'s Encrypt', value: 'Let\'s Encrypt' },
]
{ label: "Google", value: "Google" },
{ label: "ZeroSSL", value: "ZeroSSL" },
{ label: "Let's Encrypt", value: "Let's Encrypt" },
];
export const algorithmTypes = [ export const algorithmTypes = [
{ label: 'RSA', value: 'RSA' },
{ label: 'ECC', value: 'ECC' },
]
{ label: "RSA", value: "RSA" },
{ label: "ECC", value: "ECC" },
];
export const StatusText = { export const StatusText = {
1: [ '已签发', 'green' ],
2: [ '申请中', 'default' ],
3: [ '申请失败', 'red' ]
}
1: ["已签发", "green"],
2: ["申请中", "default"],
3: ["申请失败", "red"],
};
export const certIdAtom = atom(0);
export const certIdAtom = atom(0)
export const certIdsAtom = atom<number[]>([]);
export const certIdsAtom = atom<number[]>([])
export const certAtom = atom<ICertificate>(undefined as unknown as ICertificate);
export const certAtom = atom<ICertificate>(undefined as unknown as ICertificate)
export const certSearchAtom = atom<SearchParams>({
export const certSearchAtom = atom<Req_SearchParams>({
// key: '', // key: '',
pageSize: 10, pageSize: 10,
page: 1, page: 1,
} as SearchParams)
} as Req_SearchParams);
export const certPageAtom = atom<IPage>({ export const certPageAtom = atom<IPage>({
pageSize: 10, pageSize: 10,
page: 1, page: 1,
})
});
//=================================================================================================================================================kelis
export const checkDomainAtom = (domains: string, isClear: boolean) =>
atomWithQuery<IApiResult, any>(() => {
return {
enabled: domains.length > 0 && domains.includes("."),
queryKey: ["checkDomain", domains],
queryFn: async ({ queryKey: [, domains] }) => {
if ((domains as string).length === 0) {
return Promise.reject({
data: [],
});
}
//certApple
export const certAppleCertAtom = atomWithMutation<IApiResult, ICertificate>((get) => {
return await websitesServ.cert.checkDomain({
dns_full_list: domains,
parse: true,
is_clear: isClear,
});
},
select: (res) => {
return res.data;
},
};
});
export const certListAtom = atomWithQuery((get) => {
return {
queryKey: ["certList", get(certSearchAtom)],
queryFn: async ({ queryKey: [, params] }) => {
return await websitesServ.cert.list(params as Req_SearchParams);
},
select: (res) => {
const data = res.data;
data.rows = data.rows?.map((row) => {
return {
...row,
//status: convertToBool(row.status)
};
});
return data;
},
};
});
export const certAddCnameAtom = atomWithMutation<IApiResult, ICertificate>(() => {
return { return {
mutationKey: [ 'appleCert' ],
mutationKey: ["certAddCname"],
mutationFn: async (data) => { mutationFn: async (data) => {
//data.status = data.status ? '1' : '0'
return await websitesServ.cert.certApply(data)
const dData: Req_AddCname = {
is_sync: true,
dns_list: [data],
};
return await websitesServ.cert.addCnameCertificate(dData);
}, },
onSuccess: (res) => { 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
}
}
})
const status = res.data?.item[0]?.status || 0;
const status_txt = res.data?.item[0]?.status_txt;
if (status || status === 5) {
message.error(status_txt);
} else {
message.success(status_txt);
}
return res;
},
};
});
export const applyTxtCertificateAtom = atomWithMutation<IApiResult, Req_ApplyTxtCertificate>(() => {
return {
mutationKey: ["applyTxtCertificate"],
mutationFn: async (data: Req_ApplyTxtCertificate) => {
return await websitesServ.cert.applyTxtCertificate(data);
},
onSuccess: (res) => {
return res;
},
};
});
//==================================================================================================================================================kelis
// //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) => { export const certsAtom = atomWithQuery((get) => {
return { return {
queryKey: [ 'certs', get(certSearchAtom) ],
queryFn: async ({ queryKey: [ , params ] }) => {
return await websitesServ.cert.list(params as SearchParams)
queryKey: ["certs", get(certSearchAtom)],
queryFn: async ({ queryKey: [, params] }) => {
return await websitesServ.cert.list(params as Req_SearchParams);
}, },
select: res => {
const data = res.data
data.rows = data.rows?.map(row => {
select: (res) => {
const data = res.data;
data.rows = data.rows?.map((row) => {
return { return {
...row, ...row,
//status: convertToBool(row.status) //status: convertToBool(row.status)
}
})
return data
}
}
})
};
});
return data;
},
};
});
//saveOrUpdateAtom //saveOrUpdateAtom
export const saveOrUpdateCertAtom = atomWithMutation<IApiResult, ICertificate>((get) => { export const saveOrUpdateCertAtom = atomWithMutation<IApiResult, ICertificate>((get) => {
return { return {
mutationKey: [ 'updateCert' ],
mutationKey: ["updateCert"],
mutationFn: async (data) => { mutationFn: async (data) => {
//data.status = data.status ? '1' : '0' //data.status = data.status ? '1' : '0'
if (data.id) { if (data.id) {
return await websitesServ.cert.update(data)
return await websitesServ.cert.update(data);
} }
return await websitesServ.cert.add(data)
return await websitesServ.cert.add(data);
}, },
onSuccess: (res) => { onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
const isAdd = !!res.data?.id;
message.success(t(isAdd ? "message.saveSuccess" : "message.editSuccess", "保存成功"));
//更新列表 //更新列表
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix // @ts-ignore fix
get(queryClientAtom).invalidateQueries({ queryKey: [ 'certs', get(certSearchAtom) ] })
get(queryClientAtom).invalidateQueries({ queryKey: ["certs", get(certSearchAtom)] });
return res
}
}
})
return res;
},
};
});
export const deleteCertAtom = atomWithMutation((get) => { export const deleteCertAtom = atomWithMutation((get) => {
return { return {
mutationKey: [ 'deleteCert' ],
mutationKey: ["deleteCert"],
mutationFn: async (ids: number[]) => { mutationFn: async (ids: number[]) => {
return await websitesServ.cert.batchDelete(ids ?? get(certIdsAtom))
return await websitesServ.cert.batchDelete(ids ?? get(certIdsAtom));
}, },
onSuccess: (res) => { onSuccess: (res) => {
message.success('message.deleteSuccess')
message.success("message.deleteSuccess");
//更新列表 //更新列表
get(queryClientAtom).invalidateQueries({ queryKey: [ 'certs', get(certSearchAtom) ] })
return res
}
}
})
//dnsConfig
export const dnsConfigAtom = (domains: string) => atomWithQuery<IApiResult, any>(() => {
return {
enabled: domains.length > 0 && domains.includes('.'),
queryKey: [ 'dnsConfig', domains ],
queryFn: async ({ queryKey: [ , domains ] }) => {
if ((domains as string).length === 0) {
return Promise.reject({
data: []
})
}
return await websitesServ.cert.dnsConfig({
dns_full_list: domains,
parse: (domains as string)?.includes('*')
})
},
select: res => {
return res.data
}
}
})
export const dnsVerifyOKAtom = atom<boolean>(false)
//query dnsVerify
export const dnsVerifyAtom = (domains: string, block: boolean) => atomWithQuery<IApiResult, any>(() => {
return {
enabled: !block && domains.length > 0 && domains.includes('.'),
queryKey: [ 'dnsVerify', domains ],
queryFn: async ({ queryKey: [ , domains ] }) => {
if ((domains as string).length === 0) {
return Promise.reject({
data: []
})
}
return await websitesServ.cert.dnsVerify({
dns_list: domains,
})
get(queryClientAtom).invalidateQueries({ queryKey: ["certs", get(certSearchAtom)] });
return res;
}, },
select: res => {
return res.data?.dns_list
}
}
})
};
});

1
vite.config.ts

@ -13,6 +13,7 @@ const proxyMap = {
'/api/v1/movie': 'http://47.113.117.106:10000', '/api/v1/movie': 'http://47.113.117.106:10000',
//'/api/v1/certold': 'http://192.168.31.41:8000', //'/api/v1/certold': 'http://192.168.31.41:8000',
'/api/v1/cert': 'http://127.0.0.1:8000', '/api/v1/cert': 'http://127.0.0.1:8000',
//'/api/v1/cert': 'http://192.168.31.41:8000',
} as Record<any, string> } as Record<any, string>
const proxyConfig = Object.keys(proxyMap).reduce((acc, key) => { const proxyConfig = Object.keys(proxyMap).reduce((acc, key) => {

Loading…
Cancel
Save