feat: reserve accesses for ca or notification

This commit is contained in:
Fu Diwei
2025-04-27 11:41:09 +08:00
parent 193a19b79c
commit e533f9407f
17 changed files with 166 additions and 71 deletions

View File

@@ -14,14 +14,14 @@ export type AccessEditDrawerProps = {
data?: AccessFormProps["initialValues"];
loading?: boolean;
open?: boolean;
range?: AccessFormProps["range"];
scene: AccessFormProps["scene"];
trigger?: React.ReactNode;
usage?: AccessFormProps["usage"];
onOpenChange?: (open: boolean) => void;
afterSubmit?: (record: AccessModel) => void;
};
const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditDrawerProps) => {
const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, ...props }: AccessEditDrawerProps) => {
const { t } = useTranslation();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
@@ -109,7 +109,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, .
width={720}
onClose={() => setOpen(false)}
>
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
<AccessForm ref={formRef} initialValues={data} scene={scene === "add" ? "add" : "edit"} usage={usage} />
</Drawer>
</>
);

View File

@@ -14,14 +14,14 @@ export type AccessEditModalProps = {
data?: AccessFormProps["initialValues"];
loading?: boolean;
open?: boolean;
range?: AccessFormProps["range"];
usage?: AccessFormProps["usage"];
scene: AccessFormProps["scene"];
trigger?: React.ReactNode;
onOpenChange?: (open: boolean) => void;
afterSubmit?: (record: AccessModel) => void;
};
const AccessEditModal = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditModalProps) => {
const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ...props }: AccessEditModalProps) => {
const { t } = useTranslation();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
@@ -105,7 +105,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, range, afterSubmit, ..
onCancel={handleCancelClick}
>
<div className="pb-2 pt-4">
<AccessForm ref={formRef} initialValues={data} range={range} scene={scene === "add" ? "add" : "edit"} />
<AccessForm ref={formRef} initialValues={data} scene={scene === "add" ? "add" : "edit"} usage={usage} />
</div>
</Modal>
</>

View File

@@ -61,16 +61,16 @@ import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
import AccessFormZeroSSLConfig from "./AccessFormZeroSSLConfig";
type AccessFormFieldValues = Partial<MaybeModelRecord<AccessModel>>;
type AccessFormRanges = "both-dns-hosting" | "ca-only" | "notify-only";
type AccessFormScenes = "add" | "edit";
type AccessFormUsages = "both-dns-hosting" | "ca-only" | "notification-only";
export type AccessFormProps = {
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
initialValues?: AccessFormFieldValues;
range?: AccessFormRanges;
scene: AccessFormScenes;
usage?: AccessFormUsages;
onValuesChange?: (values: AccessFormFieldValues) => void;
};
@@ -80,7 +80,7 @@ export type AccessFormInstance = {
validateFields: FormInstance<AccessFormFieldValues>["validateFields"];
};
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, range, scene, onValuesChange }, ref) => {
const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, style, disabled, initialValues, usage, scene, onValuesChange }, ref) => {
const { t } = useTranslation();
const formSchema = z.object({
@@ -91,13 +91,14 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
.trim(),
provider: z.nativeEnum(ACCESS_PROVIDERS, {
message:
range === "ca-only"
usage === "ca-only"
? t("access.form.certificate_authority.placeholder")
: range === "notify-only"
: usage === "notification-only"
? t("access.form.notification_channel.placeholder")
: t("access.form.provider.placeholder"),
}),
config: z.any(),
reserve: z.string().nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const { form: formInst, formProps } = useAntdForm({
@@ -105,33 +106,33 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
});
const providerLabel = useMemo(() => {
switch (range) {
switch (usage) {
case "ca-only":
return t("access.form.certificate_authority.label");
case "notify-only":
case "notification-only":
return t("access.form.notification_channel.label");
}
return t("access.form.provider.label");
}, [range]);
}, [usage]);
const providerPlaceholder = useMemo(() => {
switch (range) {
switch (usage) {
case "ca-only":
return t("access.form.certificate_authority.placeholder");
case "notify-only":
case "notification-only":
return t("access.form.notification_channel.placeholder");
}
return t("access.form.provider.placeholder");
}, [range]);
}, [usage]);
const providerTooltip = useMemo(() => {
switch (range) {
switch (usage) {
case "both-dns-hosting":
return <span dangerouslySetInnerHTML={{ __html: t("access.form.provider.tooltip") }}></span>;
}
return undefined;
}, [range]);
}, [usage]);
const fieldProvider = Form.useWatch("provider", formInst);
@@ -269,6 +270,7 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
getFieldsValue: () => {
const values = formInst.getFieldsValue(true);
values.config = nestedFormInst.getFieldsValue();
values.reserve = usage === "ca-only" ? "ca" : usage === "notification-only" ? "notification" : undefined;
return values;
},
resetFields: (fields) => {
@@ -297,20 +299,20 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
<Form.Item name="provider" label={providerLabel} rules={[formRule]} tooltip={providerTooltip}>
<AccessProviderSelect
filter={(record) => {
if (range == null) return true;
if (usage == null) return true;
switch (range) {
switch (usage) {
case "both-dns-hosting":
return record.usages.includes(ACCESS_USAGES.DNS) || record.usages.includes(ACCESS_USAGES.HOSTING);
case "ca-only":
return record.usages.includes(ACCESS_USAGES.CA);
case "notify-only":
case "notification-only":
return record.usages.includes(ACCESS_USAGES.NOTIFICATION);
}
}}
disabled={scene !== "add"}
placeholder={providerPlaceholder}
showOptionTags={range == null || (range === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)}
showOptionTags={usage == null || (usage === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)}
showSearch={!disabled}
/>
</Form.Item>

View File

@@ -352,7 +352,6 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
</div>
<div className="text-right">
<AccessEditModal
range="both-dns-hosting"
scene="add"
trigger={
<Button size="small" type="link">
@@ -360,6 +359,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
<PlusOutlinedIcon className="text-xs" />
</Button>
}
usage="both-dns-hosting"
afterSubmit={(record) => {
const provider = accessProvidersMap.get(record.provider);
if (provider?.usages?.includes(ACCESS_USAGES.DNS)) {
@@ -374,6 +374,8 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
<Form.Item name="providerAccessId" rules={[formRule]}>
<AccessSelect
filter={(record) => {
if (record.reserve) return false;
const provider = accessProvidersMap.get(record.provider);
return !!provider?.usages?.includes(ACCESS_USAGES.DNS);
}}
@@ -429,7 +431,6 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
<div className="text-right">
<AccessEditModal
data={{ provider: caProvidersMap.get(fieldCAProvider!)?.provider }}
range="ca-only"
scene="add"
trigger={
<Button size="small" type="link">
@@ -437,6 +438,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
<PlusOutlinedIcon className="text-xs" />
</Button>
}
usage="ca-only"
afterSubmit={(record) => {
const provider = accessProvidersMap.get(record.provider);
if (provider?.usages?.includes(ACCESS_USAGES.CA)) {
@@ -450,9 +452,8 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
<Form.Item name="caProviderAccessId" rules={[formRule]}>
<AccessSelect
filter={(record) => {
if (fieldCAProvider) {
return caProvidersMap.get(fieldCAProvider)?.provider === record.provider;
}
if (!!record.reserve && record.reserve !== "ca") return false;
if (fieldCAProvider) return caProvidersMap.get(fieldCAProvider)?.provider === record.provider;
const provider = accessProvidersMap.get(record.provider);
return !!provider?.usages?.includes(ACCESS_USAGES.CA);

View File

@@ -409,7 +409,6 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
<div className="text-right">
<AccessEditModal
data={{ provider: deploymentProvidersMap.get(fieldProvider!)?.provider }}
range="both-dns-hosting"
scene="add"
trigger={
<Button size="small" type="link">
@@ -417,6 +416,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
<PlusOutlinedIcon className="text-xs" />
</Button>
}
usage="both-dns-hosting"
afterSubmit={(record) => {
const provider = accessProvidersMap.get(record.provider);
if (provider?.usages?.includes(ACCESS_USAGES.HOSTING)) {
@@ -430,9 +430,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
<Form.Item name="providerAccessId" rules={[formRule]}>
<AccessSelect
filter={(record) => {
if (fieldProvider) {
return deploymentProvidersMap.get(fieldProvider)?.provider === record.provider;
}
if (record.reserve) return false;
if (fieldProvider) return deploymentProvidersMap.get(fieldProvider)?.provider === record.provider;
const provider = accessProvidersMap.get(record.provider);
return !!provider?.usages?.includes(ACCESS_USAGES.HOSTING);

View File

@@ -228,7 +228,6 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
</div>
<div className="text-right">
<AccessEditModal
range="notify-only"
scene="add"
trigger={
<Button size="small" type="link">
@@ -236,6 +235,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
<PlusOutlinedIcon className="text-xs" />
</Button>
}
usage="notification-only"
afterSubmit={(record) => {
const provider = accessProvidersMap.get(record.provider);
if (provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION)) {
@@ -250,6 +250,8 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
<Form.Item name="providerAccessId" rules={[formRule]}>
<AccessSelect
filter={(record) => {
if (!!record.reserve && record.reserve !== "notification") return false;
const provider = accessProvidersMap.get(record.provider);
return !!provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
}}

View File

@@ -56,6 +56,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForWestcn
| AccessConfigForZeroSSL
);
reserve?: "ca" | "notification";
}
// #region AccessConfig
@@ -310,8 +311,8 @@ export type AccessConfigForWebhook = {
method: string;
headers?: string;
allowInsecureConnections?: boolean;
templateDataForDeployment?: string;
templateDataForNotification?: string;
defaultDataForDeployment?: string;
defaultDataForNotification?: string;
};
export type AccessConfigForWestcn = {

View File

@@ -18,9 +18,9 @@
"access.props.provider.usage.ca": "Certificate authority",
"access.props.provider.usage.notification": "Notification channel",
"access.props.provider.builtin": "Built-in",
"access.props.range.both_dns_hosting": "Provider",
"access.props.range.ca_only": "Certificate authority",
"access.props.range.notify_only": "Notification channel",
"access.props.usage.both_dns_hosting": "Provider",
"access.props.usage.ca_only": "Certificate authority",
"access.props.usage.notification_only": "Notification channel",
"access.props.created_at": "Created at",
"access.props.updated_at": "Updated at",

View File

@@ -18,9 +18,9 @@
"access.props.provider.usage.ca": "证书颁发机构",
"access.props.provider.usage.notification": "通知渠道",
"access.props.provider.builtin": "内置",
"access.props.range.both_dns_hosting": "提供商",
"access.props.range.ca_only": "证书颁发机构",
"access.props.range.notify_only": "通知渠道",
"access.props.usage.both_dns_hosting": "提供商",
"access.props.usage.ca_only": "证书颁发机构",
"access.props.usage.notification_only": "通知渠道",
"access.props.created_at": "创建时间",
"access.props.updated_at": "更新时间",

View File

@@ -21,7 +21,7 @@ import { useZustandShallowSelector } from "@/hooks";
import { useAccessesStore } from "@/stores/access";
import { getErrMsg } from "@/utils/error";
type AccessRanges = AccessEditDrawerProps["range"];
type AccessUsageProp = AccessEditDrawerProps["usage"];
const AccessList = () => {
const [searchParams] = useSearchParams();
@@ -87,7 +87,7 @@ const AccessList = () => {
<Space.Compact>
<AccessEditDrawer
data={record}
range={filters["range"] as AccessRanges}
usage={filters["usage"] as AccessUsageProp}
scene="edit"
trigger={
<Tooltip title={t("access.action.edit")}>
@@ -98,7 +98,7 @@ const AccessList = () => {
<AccessEditDrawer
data={{ ...record, id: undefined, name: `${record.name}-copy` }}
range={filters["range"] as AccessRanges}
usage={filters["usage"] as AccessUsageProp}
scene="add"
trigger={
<Tooltip title={t("access.action.duplicate")}>
@@ -126,7 +126,7 @@ const AccessList = () => {
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return {
range: "both-dns-hosting" satisfies AccessRanges,
usage: "both-dns-hosting" satisfies AccessUsageProp,
keyword: searchParams.get("keyword"),
};
});
@@ -160,13 +160,13 @@ const AccessList = () => {
})
.filter((e) => {
const provider = accessProvidersMap.get(e.provider);
switch (filters["range"] as AccessRanges) {
switch (filters["usage"] as AccessUsageProp) {
case "both-dns-hosting":
return provider?.usages?.includes(ACCESS_USAGES.DNS) || provider?.usages?.includes(ACCESS_USAGES.HOSTING);
return !e.reserve && (provider?.usages?.includes(ACCESS_USAGES.DNS) || provider?.usages?.includes(ACCESS_USAGES.HOSTING));
case "ca-only":
return provider?.usages?.includes(ACCESS_USAGES.CA);
case "notify-only":
return provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
return e.reserve === "ca" && provider?.usages?.includes(ACCESS_USAGES.CA);
case "notification-only":
return e.reserve === "notification" && provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
}
});
return Promise.resolve({
@@ -184,7 +184,7 @@ const AccessList = () => {
);
const handleTabChange = (key: string) => {
setFilters((prev) => ({ ...prev, range: key }));
setFilters((prev) => ({ ...prev, usage: key }));
setPage(1);
};
@@ -226,7 +226,7 @@ const AccessList = () => {
extra={[
<AccessEditDrawer
key="create"
range={filters["range"] as AccessRanges}
usage={filters["usage"] as AccessUsageProp}
scene="add"
trigger={
<Button type="primary" icon={<PlusOutlinedIcon />}>
@@ -247,18 +247,18 @@ const AccessList = () => {
tabList={[
{
key: "both-dns-hosting",
label: t("access.props.range.both_dns_hosting"),
label: t("access.props.usage.both_dns_hosting"),
},
{
key: "ca-only",
label: t("access.props.range.ca_only"),
label: t("access.props.usage.ca_only"),
},
{
key: "notify-only",
label: t("access.props.range.notify_only"),
key: "notification-only",
label: t("access.props.usage.notification_only"),
},
]}
activeTabKey={filters["range"] as string}
activeTabKey={filters["usage"] as string}
onTabChange={(key) => handleTabChange(key)}
/>