Browse Source

完善申请证书接口和UI 完善证书列表UI和接口 差证书申请进度

main
lk 3 months ago
parent
commit
2115c63c44
  1. 1
      package.json
  2. 179
      src/pages/login/index.tsx
  3. 183
      src/pages/websites/cert/apply.tsx
  4. 73
      src/service/websites.ts
  5. 82
      src/store/websites/cert.ts

1
package.json

@ -28,6 +28,7 @@
"antd-style": "^3.6.2",
"axios": "^1.7.7",
"bunshi": "^2.1.5",
"date-fns": "^3.6.0",
"dayjs": "^1.11.13",
"fast-copy": "^3.0.2",
"fast-deep-equal": "^3.1.3",

179
src/pages/login/index.tsx

@ -1,97 +1,102 @@
import SelectLang from '@/components/select-lang'
import { createFileRoute } from '@tanstack/react-router'
import { Button, Form, Input, Space } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { useTranslation } from '@/i18n.ts'
import { loginAtom, loginFormAtom } from '@/store/system/user.ts'
import { memo, useLayoutEffect } from 'react'
import { useStyles } from './style.ts'
import SelectLang from "@/components/select-lang";
import { createFileRoute } from "@tanstack/react-router";
import { Button, Form, Input, Radio, Space } from "antd";
import { useAtom, useAtomValue } from "jotai";
import { useTranslation } from "@/i18n.ts";
import { loginAtom, loginFormAtom } from "@/store/system/user.ts";
import React, {memo, useLayoutEffect, useState} from "react";
import { useStyles } from "./style.ts";
const Login = memo(() => {
const { styles } = useStyles();
const { t } = useTranslation();
const [values, setValues] = useAtom(loginFormAtom);
const { isPending, mutate } = useAtomValue(loginAtom);
const [form] = Form.useForm();
const { styles } = useStyles()
const { t } = useTranslation()
const [ values, setValues ] = useAtom(loginFormAtom)
const { isPending, mutate } = useAtomValue(loginAtom)
const [ form ] = Form.useForm()
const handleSubmit = () => {
form.validateFields().then(() => {
mutate(values);
});
};
const handleSubmit = () => {
form.validateFields().then(() => {
mutate(values)
})
}
const [loginMod, setLoginMod] = useState([
{ value: "single", info: "帐号密码登录" },
{ value: "multiple-email", info: "邮箱登录" },
{ value: "multiple-plane", info: "飞机登录" },
]);
const [selectedMode, setSelectedMode] = useState(loginMod[0].value);
useLayoutEffect(() => {
const handleModeChange = (e: any) => {
setSelectedMode(e.target.value);
};
document.body.className = 'login'
return () => {
document.body.className = document.body.className.replace('login', '')
}
useLayoutEffect(() => {
document.body.className = "login";
return () => {
document.body.className = document.body.className.replace("login", "");
};
}, []);
}, [])
return (
<div className={styles.container}>
<div className={styles.language}>
<SelectLang />
</div>
<div className={styles.loginBlock}>
<div className={styles.innerBlock}>
<Radio.Group style={{ marginBottom: 8 }} value={selectedMode} onChange={handleModeChange}>
{loginMod.map((mod) => (
<Radio.Button key={mod.value} value={mod.value}>
{mod.info}
</Radio.Button>
))}
</Radio.Group>
<Form
form={form}
disabled={isPending}
initialValues={values}
onValuesChange={(_, allValues) => {
setValues(allValues);
}}
size="large"
>
<Form.Item name={"username"} rules={[{ required: true, message: t("login.usernameMsg") }]}>
<Input maxLength={20} placeholder={t("login.username")} />
</Form.Item>
<Form.Item name={"password"} rules={[{ required: true, message: t("login.passwordMsg") }]}>
<Input.Password placeholder={t("login.password")} />
</Form.Item>
<Form.Item noStyle>
<Space direction="horizontal">
<Form.Item name={"code"} rules={[{ required: true, message: t("login.codeMsg") }]}>
<Input placeholder={t("login.code")} />
{/*<img src="https://img.alicdn.com/tfs/TB1KtN6mKH2gK0jSZJnXXaT1FXa-1014-200.png" alt="验证码" />*/}
</Form.Item>
</Space>
</Form.Item>
<Form.Item style={{ marginBottom: 10 }}>
<Button
htmlType={"submit"}
type="primary"
onClick={handleSubmit}
className={"submitBtn"}
loading={isPending}
disabled={isPending}
>
{t("login.submit")}
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
);
});
return (
<div className={styles.container}>
<div className={styles.language}>
<SelectLang/>
</div>
<div className={styles.loginBlock}>
<div className={styles.innerBlock}>
export const Route = createFileRoute("/login")({
component: Login,
});
<div className={styles.desc}>
<span className={styles.active}>
{t('login.title')}
</span>
</div>
<Form form={form}
disabled={isPending}
initialValues={values}
onValuesChange={(_, allValues) => {
setValues(allValues)
}}
size="large">
<Form.Item name={'username'}
rules={[ { required: true, message: t('login.usernameMsg') } ]}>
<Input maxLength={20} placeholder={t('login.username')}/>
</Form.Item>
<Form.Item name={'password'}
rules={[ { required: true, message: t('login.passwordMsg') } ]}>
<Input.Password placeholder={t('login.password')}/>
</Form.Item>
<Form.Item noStyle>
<Space direction="horizontal">
<Form.Item name={'code'}
rules={[ { required: true, message: t('login.codeMsg') } ]}>
<Input placeholder={t('login.code')}/>
{/*<img src="https://img.alicdn.com/tfs/TB1KtN6mKH2gK0jSZJnXXaT1FXa-1014-200.png" alt="验证码" />*/}
</Form.Item>
</Space>
</Form.Item>
<Form.Item style={{ marginBottom: 10 }}>
<Button
htmlType={'submit'}
type="primary"
onClick={handleSubmit}
className={'submitBtn'}
loading={isPending}
disabled={isPending}
>
{t('login.submit')}
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
)
})
export const Route = createFileRoute('/login')({
component: Login
})
export default Login
export default Login;

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

@ -6,6 +6,7 @@ import {
checkDomainAtom,
applyTxtCertificateAtom,
Req_ApplyTxtCertificate,
getCertConfigAtom,
} from "@/store/websites/cert.ts";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button, Flex, Form, Input, message, Radio, Select, Space, Steps, Table, Tooltip } from "antd";
@ -28,19 +29,60 @@ import {
import { useQueryClient } from "@tanstack/react-query";
const i18nPrefix = "cert.apply";
const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTxtCertificateData }) => {
const { styles, cx } = useStyle();
const DomainsInput = (props: { domains; setDomains; currentDomainMod; setCurrentDomainMod; currentStep }) => {
useEffect(() => {
onChange(props.value);
}, [props.value]);
if (props.currentDomainMod === "single" && props.domains !== "") {
props.setDomains(props.domains.replace(/[\r\n]/g, ""));
}
}, [props.currentDomainMod]);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Radio.Group
style={{ marginBottom: 8 }}
value={props.currentDomainMod}
onChange={(e) => props.setCurrentDomainMod(e.target.value)}
>
<Radio.Button value="single" disabled={props.currentStep !== 0}>
</Radio.Button>
<Radio.Button value="multiple" disabled={props.currentStep !== 0}>
</Radio.Button>
</Radio.Group>
{props.currentDomainMod === "single" && (
<Input
disabled={props.currentStep !== 0}
placeholder="请输入域名,支持泛解析域名。如果为多个域名注册到一个证书,域名之间用<逗号(英文输入)>分割。如:a.com,*.b.com"
onChange={(e) => {
props.setDomains(e.target.value);
}}
value={props.domains} // 设置输入框的值
/>
)}
{props.currentDomainMod === "multiple" && (
<Input.TextArea
disabled={props.currentStep !== 0}
rows={6}
placeholder={`多个域名多个证书申请,用回车分隔。以下示例为6个域名申请3个证书,每行对应一个证书,支持泛解析域名;如:
*.a.baidu.com, *.baidu.com
hello.alibaba.com,b.com,*.c.com
sss.ddd.com`}
onChange={(e) => {
props.setDomains(e.target.value);
}}
value={props.domains} // 设置输入框的值
/>
)}
</Space>
);
};
const onChange = useCallback((val: string) => {
props.setApplyTxtCertificateData((prevState) => ({
...prevState,
acme_type: val,
}));
}, []);
const BrandSelect = (props: { acme_type; setAcme_type }) => {
const { styles, cx } = useStyle();
const onChange = (val: string) => {
props.setAcme_type(val);
};
return (
<>
@ -49,7 +91,7 @@ const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTx
vertical={true}
onClick={() => onChange("Google")}
className={cx("band-normal", {
"band-active": props.applyTxtCertificateData.acme_type === "Google" || !props.value,
"band-active": props.acme_type === "Google",
})}
>
<img src={google} style={{ height: "2rem" }} />
@ -59,7 +101,7 @@ const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTx
vertical={true}
onClick={() => onChange("ZeroSSL")}
className={cx("band-normal", {
"band-active": props.applyTxtCertificateData.acme_type === "ZeroSSL",
"band-active": props.acme_type === "ZeroSSL",
})}
>
<img src={zerossl} style={{ height: "2rem" }} />
@ -69,7 +111,7 @@ const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTx
vertical={true}
onClick={() => onChange("Let's Encrypt")}
className={cx("band-normal", {
"band-active": props.applyTxtCertificateData.acme_type === "Let's Encrypt",
"band-active": props.acme_type === "Let's Encrypt",
})}
>
<img src={lets_encrypt} style={{ height: "2rem" }} />
@ -80,7 +122,7 @@ const BrandSelect = (props: { value: string; applyTxtCertificateData; setApplyTx
);
};
const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertificateData }) => {
const StatusTable = (props: { value: string; setCurrentStep; setDns_list }) => {
// const [data, setData] = useState<any>(null); // 临时状态来存储模拟数据
const [forceUpdate, setForceUpdate] = useState(0);
const { data, isFetching: isVerifyFetching } = useAtomValue(
@ -249,7 +291,7 @@ const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertific
iconPosition={"end"}
onClick={() => handleCopy(text)}
>
{t(`actions.clickCopy`)}
{text}
</Button>
);
},
@ -262,10 +304,7 @@ const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertific
if ((data as any)?.check_ok === true) {
props.setCurrentStep(2);
props.setApplyTxtCertificateData((prevState) => ({
...prevState,
dns_list: (data as any).dns_list,
}));
props.setDns_list((data as any).dns_list);
return;
}
@ -279,10 +318,7 @@ const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertific
// 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,
}));
props.setDns_list((data as any).dns_list);
window.clearInterval(timerRef.current);
} else {
refetch();
@ -316,48 +352,9 @@ const StatusTable = (props: { value: string; setCurrentStep; setApplyTxtCertific
);
};
const DomainsInput = (props: { setDomains; currentDomainMod; setCurrentDomainMod; currentStep }) => {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Radio.Group
style={{ marginBottom: 8 }}
value={props.currentDomainMod}
onChange={(e) => props.setCurrentDomainMod(e.target.value)}
>
<Radio.Button value="single" disabled={props.currentStep !== 0}>
</Radio.Button>
<Radio.Button value="multiple" disabled={props.currentStep !== 0}>
</Radio.Button>
</Radio.Group>
{props.currentDomainMod === "single" && (
<Input
disabled={props.currentStep !== 0}
placeholder="请输入域名,支持泛解析域名。如果为多个域名注册到一个证书,域名之间用<逗号(英文输入)>分割。如:a.com,*.b.com"
onBlur={(e) => {
props.setDomains(e.target.value);
}}
/>
)}
{props.currentDomainMod === "multiple" && (
<Input.TextArea
disabled={props.currentStep !== 0}
rows={6}
placeholder={`多个域名多个证书申请,用回车分隔。以下示例为6个域名申请3个证书,每行对应一个证书,支持泛解析域名;如:
*.a.baidu.com, *.baidu.com
hello.alibaba.com,b.com,*.c.com
sss.ddd.com`}
onBlur={(e) => {
props.setDomains(e.target.value);
}}
/>
)}
</Space>
);
};
const domainsAtom = atomWithStorage<string>("domains", "");
const domainsAtom = atomWithStorage<string>("cert_domains", "");
// 打印初始值
console.log("初始文本值:", localStorage.getItem("cert_domains") || "");
const Apply = () => {
const { styles } = useStyle();
@ -367,33 +364,36 @@ const Apply = () => {
isPending: applyTxtCertificatePending,
isSuccess: applyTxtCertificateIsSuccess,
} = useAtomValue(applyTxtCertificateAtom);
const [domains, setDomains] = useAtom(domainsAtom);
const [currentStep, setCurrentStep] = useState(0);
const [currentDomainMod, setCurrentDomainMod] = useState<"single" | "multiple">("single");
const [dns_list, setDns_list] = useState<ICertificate[]>([]);
const [acme_type, setAcme_type] = useState<string>("Google");
const [key_rsa, setKey_rsa] = useState<string>("");
const { data: configData } = useAtomValue(useMemo(() => getCertConfigAtom(), []));
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,
}));
setKey_rsa(value);
};
const applyTxtCertificateClick = () => {
applyTxtCertificateFun(applyTxtCertificateData);
const data: Req_ApplyTxtCertificate = {
is_sync: true,
acme_type: acme_type,
key_rsa: key_rsa,
dns_list: dns_list,
remark: form.getFieldValue("remark"),
};
applyTxtCertificateFun(data);
};
useEffect(() => {
if (domains) {
form.setFieldsValue({
domains,
});
}
}, [domains]);
form.setFieldsValue({ domains: domains });
form.setFieldsValue({ brand: acme_type });
form.setFieldsValue({ algorithm: key_rsa });
}, [domains, acme_type, key_rsa, form]);
return (
<ListPageLayout
childrenClassName={styles.applyContent}
@ -453,9 +453,10 @@ const Apply = () => {
rules={[{ required: true, message: t(`${i18nPrefix}.columns.domains.required`, "请输入域名") }]}
>
<DomainsInput
domains={domains}
setDomains={setDomains}
currentDomainMod={currentDomainMod}
setCurrentDomainMod={setCurrentDomainMod}
setDomains={setDomains}
currentStep={currentStep}
/>
</Form.Item>
@ -465,11 +466,7 @@ const Apply = () => {
label={t(`${i18nPrefix}.columns.type`, "域名验证")}
rules={[{ required: true, message: t(`${i18nPrefix}.columns.type`, "域名验证没有通过") }]}
>
<StatusTable
value={domains}
setCurrentStep={setCurrentStep}
setApplyTxtCertificateData={setApplyTxtCertificateData}
/>
<StatusTable value={domains} setCurrentStep={setCurrentStep} setDns_list={setDns_list} />
</Form.Item>
<Form.Item
@ -482,11 +479,7 @@ const Apply = () => {
},
]}
>
<BrandSelect
value="Google"
applyTxtCertificateData={applyTxtCertificateData}
setApplyTxtCertificateData={setApplyTxtCertificateData}
/>
<BrandSelect acme_type={acme_type} setAcme_type={setAcme_type} />
</Form.Item>
<Form.Item
name={"algorithm"}
@ -498,7 +491,7 @@ const Apply = () => {
},
]}
>
<Select style={{ width: 120 }} options={algorithmTypes} onChange={handleAlgorithmChange} />
<Select style={{ width: 120 }} options={(configData as any)?.key_rsa} onChange={handleAlgorithmChange} />
</Form.Item>
<Form.Item name={"remark"} label={t(`${i18nPrefix}.columns.remark`, "备注 ")}>
<Input style={{ width: 400 }} />

73
src/service/websites.ts

@ -1,18 +1,24 @@
import { createCURD } from '@/service/base.ts'
import { WebSite } from '@/types'
import request from '@/request.ts'
import { IWebsiteDomain, INameServer } from '@/types/website/domain'
import { IWebsiteDnsRecords } from '@/types/website/record'
import { IWebsiteDnsAccount } from '@/types/website/dns_account'
import { createCURD } from "@/service/base.ts";
import { WebSite } from "@/types";
import request from "@/request.ts";
import { IWebsiteDomain, INameServer } from "@/types/website/domain";
import { IWebsiteDnsRecords } from "@/types/website/record";
import { IWebsiteDnsAccount } from "@/types/website/dns_account";
const websitesServ = {
cert: {
...createCURD<any, ICertificate>("/website/cert"),
// 发起域名检测
checkDomain: async (params: any) => {
return request.post<any, any>("/cert/apply/dns_config", params);
},
getCertConfig: async () => {
return request.get<any, any>("/cert/apply/acme/key");
},
// 申请list
getCertList: async (arams: any) => {
return request.post<any, any>("/cert/apply/list");
},
// 证书续签
renewCertificate: async (params: any) => {
return request.post<any, any>("/website/cert/renew_certificate", params);
@ -32,66 +38,63 @@ const websitesServ = {
applyTxtCertificate: async (params: any) => {
return request.post<any, any>("/cert/apply/resolve", params);
},
},
ssl: {
...createCURD<any, WebSite.ISSL>('/website/ssl'),
...createCURD<any, WebSite.ISSL>("/website/ssl"),
upload: async (params: WebSite.SSLUploadDto) => {
return request.post<any, WebSite.SSLUploadDto>('/website/ssl/upload', params)
return request.post<any, WebSite.SSLUploadDto>("/website/ssl/upload", params);
},
download: async (params: any) => {
return request.download('/website/ssl/download', params)
return request.download("/website/ssl/download", params);
},
},
acme: {
...createCURD<any, WebSite.IAcmeAccount>('/website/acme')
...createCURD<any, WebSite.IAcmeAccount>("/website/acme"),
},
dns: {
...createCURD<any, WebSite.IDnsAccount>('/cert/dns_account'),
...createCURD<any, WebSite.IDnsAccount>("/cert/dns_account"),
sync: async (id: any) => {
return request.post<any, WebSite.IDnsAccount>('/cert/dns_account/sync', { id: id })
}
return request.post<any, WebSite.IDnsAccount>("/cert/dns_account/sync", { id: id });
},
},
ca: {
...createCURD<any, WebSite.ICA>('/website/ca'),
...createCURD<any, WebSite.ICA>("/website/ca"),
obtainSsl: async (params: WebSite.ISSLObtainByCA) => {
return request.post<any, WebSite.ISSLObtainByCA>('/website/ca/obtain_ssl', params)
return request.post<any, WebSite.ISSLObtainByCA>("/website/ca/obtain_ssl", params);
},
},
domain: {
...createCURD<any, IWebsiteDomain>('/cert/domain'),
...createCURD<any, IWebsiteDomain>("/cert/domain"),
//remark
remark: async (params: { id: string, remark: string }) => {
return request.post<any, any>('/cert/domain/remark', params)
remark: async (params: { id: string; remark: string }) => {
return request.post<any, any>("/cert/domain/remark", params);
},
//tag
tag: async (params: { id: string, tags: string }) => {
return request.post<any, any>('/cert/domain/tag', params)
tag: async (params: { id: string; tags: string }) => {
return request.post<any, any>("/cert/domain/tag", params);
},
//binding
binding: async (params: { id: string, user_id: string }) => {
return request.post<any, any>('/cert/domain/binding', params)
binding: async (params: { id: string; user_id: string }) => {
return request.post<any, any>("/cert/domain/binding", params);
},
//group
group: async (params: { id: string[], group_id: string }) => {
return request.post<any, any>('/cert/domain/group', params)
group: async (params: { id: string[]; group_id: string }) => {
return request.post<any, any>("/cert/domain/group", params);
},
describeDomainNS: async (params: { id: number }) => {
return request.post<INameServer, any>('/cert/domain/describe_domain_ns', params)
return request.post<INameServer, any>("/cert/domain/describe_domain_ns", params);
},
},
record: {
...createCURD<any, IWebsiteDnsRecords>('/cert/dns_records'),
...createCURD<any, IWebsiteDnsRecords>("/cert/dns_records"),
//
},
dnsAccount: {
...createCURD<any, IWebsiteDnsAccount>('/cert/dns_account'),
...createCURD<any, IWebsiteDnsAccount>("/cert/dns_account"),
sync: async (params: IWebsiteDnsAccount) => {
return request.post<any, IWebsiteDnsAccount>('/cert/dns_account/sync', params)
}
return request.post<any, IWebsiteDnsAccount>("/cert/dns_account/sync", params);
},
},
}
};
export default websitesServ
export default websitesServ;

82
src/store/websites/cert.ts

@ -9,22 +9,45 @@ export type Req_SearchParams = IPage & {
name?: string;
};
export type Req_AddCname = {
export interface Req_AddCname {
is_sync: boolean;
dns_list: ICertificate[];
};
}
export type Req_ApplyTxtCertificate = {
export interface Req_ApplyTxtCertificate {
is_sync: boolean;
acme_type: string;
key_rsa: string;
dns_list: ICertificate[];
};
remark: string;
}
//=========================证书列表
export interface Req_CertList {
order: string;
prop: string;
page: number;
pageSize: number;
}
export interface Resp_CertList {
page: number;
pageSize: number;
total: number;
rows: any;
}
//=========================证书列表
export const bandTypes = [
{ label: "Google", value: "Google" },
{ label: "ZeroSSL", value: "ZeroSSL" },
{ label: "Let's Encrypt", value: "Let's Encrypt" },
{
label: "LetsEncrypt",
value: "LetsEncrypt",
},
{
label: "ZeroSsl",
value: "ZeroSsl",
},
{
label: "Google",
value: "Google",
},
];
export const algorithmTypes = [
@ -56,6 +79,19 @@ export const certPageAtom = atom<IPage>({
});
//=================================================================================================================================================kelis
export const getCertConfigAtom = () =>
atomWithQuery<IApiResult, any>(() => {
return {
queryKey: ["getCertConfig"],
queryFn: async () => {
return await websitesServ.cert.getCertConfig();
},
select: (res) => {
return res.data;
},
};
});
export const checkDomainAtom = (domains: string, isClear: boolean) =>
atomWithQuery<IApiResult, any>(() => {
return {
@ -80,24 +116,18 @@ export const checkDomainAtom = (domains: string, isClear: boolean) =>
};
});
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 certListAtom = (params: Req_CertList) =>
atomWithQuery<IApiResult, any>(() => {
return {
queryKey: ["certList", params],
queryFn: async ({ queryKey: [, params] }) => {
return await websitesServ.cert.getCertList(params);
},
select: (res) => {
return res.data;
},
};
});
export const certAddCnameAtom = atomWithMutation<IApiResult, ICertificate>(() => {
return {

Loading…
Cancel
Save