mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-17 01:49:52 +00:00
feat(ui): multiple input domains & nameservers in ApplyNodeForm
This commit is contained in:
parent
75cf552e72
commit
fb2d292cbf
@ -40,12 +40,7 @@ const AccessEditFormSSHConfig = ({ form, formName, disabled, initialValues, onVa
|
|||||||
},
|
},
|
||||||
{ message: t("common.errmsg.host_invalid") }
|
{ message: t("common.errmsg.host_invalid") }
|
||||||
),
|
),
|
||||||
port: z
|
port: z.number().int().gte(1, t("common.errmsg.port_invalid")).lte(65535, t("common.errmsg.port_invalid")),
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.gte(1, t("common.errmsg.port_invalid"))
|
|
||||||
.lte(65535, t("common.errmsg.port_invalid"))
|
|
||||||
.transform((v) => +v),
|
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "access.form.ssh_username.placeholder")
|
.min(1, "access.form.ssh_username.placeholder")
|
||||||
|
@ -27,7 +27,17 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
|
|||||||
<>
|
<>
|
||||||
{triggerDom}
|
{triggerDom}
|
||||||
|
|
||||||
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
|
<Drawer
|
||||||
|
afterOpenChange={setOpen}
|
||||||
|
closable
|
||||||
|
destroyOnClose
|
||||||
|
open={open}
|
||||||
|
loading={loading}
|
||||||
|
placement="right"
|
||||||
|
title={data?.id}
|
||||||
|
width={640}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
<Show when={!!data}>
|
<Show when={!!data}>
|
||||||
<CertificateDetail data={data!} />
|
<CertificateDetail data={data!} />
|
||||||
</Show>
|
</Show>
|
||||||
|
99
ui/src/components/core/DrawerForm.tsx
Normal file
99
ui/src/components/core/DrawerForm.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { useControllableValue } from "ahooks";
|
||||||
|
import { Button, Drawer, Form, Space, type DrawerProps, type FormProps, type ModalProps } from "antd";
|
||||||
|
|
||||||
|
import { useAntdForm, useTriggerElement } from "@/hooks";
|
||||||
|
|
||||||
|
export interface DrawerFormProps<T extends NonNullable<unknown> = any> extends Omit<FormProps<T>, "title" | "onFinish"> {
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
cancelButtonProps?: ModalProps["cancelButtonProps"];
|
||||||
|
cancelText?: ModalProps["cancelText"];
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
drawerProps?: Omit<DrawerProps, "open" | "title" | "width">;
|
||||||
|
okButtonProps?: ModalProps["okButtonProps"];
|
||||||
|
okText?: ModalProps["okText"];
|
||||||
|
open?: boolean;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
width?: string | number;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
onFinish?: (values: T) => void | Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerForm = <T extends NonNullable<unknown> = any>({
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
children,
|
||||||
|
form,
|
||||||
|
drawerProps,
|
||||||
|
title,
|
||||||
|
trigger,
|
||||||
|
width,
|
||||||
|
onFinish,
|
||||||
|
...props
|
||||||
|
}: DrawerFormProps<T>) => {
|
||||||
|
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||||
|
valuePropName: "open",
|
||||||
|
defaultValuePropName: "defaultOpen",
|
||||||
|
trigger: "onOpenChange",
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
|
|
||||||
|
const {
|
||||||
|
form: formInst,
|
||||||
|
formPending,
|
||||||
|
formProps,
|
||||||
|
submit,
|
||||||
|
} = useAntdForm({
|
||||||
|
form,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const ret = await onFinish?.(values);
|
||||||
|
if (ret != null && !ret) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mergedFormProps = { ...formProps, ...props };
|
||||||
|
|
||||||
|
const handleOkClick = async () => {
|
||||||
|
const ret = await submit();
|
||||||
|
if (ret != null && !ret) return;
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelClick = () => {
|
||||||
|
if (formPending) return;
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{triggerDom}
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
footer={
|
||||||
|
<Space>
|
||||||
|
<Button onClick={handleCancelClick}>1</Button>
|
||||||
|
<Button type="primary" loading={formPending} onClick={handleOkClick}>
|
||||||
|
2
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
open={open}
|
||||||
|
title={title}
|
||||||
|
width={width}
|
||||||
|
{...drawerProps}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<Form className={className} style={style} form={formInst} {...mergedFormProps}>
|
||||||
|
{children}
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerForm;
|
119
ui/src/components/core/ModalForm.tsx
Normal file
119
ui/src/components/core/ModalForm.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { useControllableValue } from "ahooks";
|
||||||
|
import { Form, Modal, type FormProps, type ModalProps } from "antd";
|
||||||
|
|
||||||
|
import { useAntdForm, useTriggerElement } from "@/hooks";
|
||||||
|
|
||||||
|
export interface ModalFormProps<T extends NonNullable<unknown> = any> extends Omit<FormProps<T>, "title" | "onFinish"> {
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
cancelButtonProps?: ModalProps["cancelButtonProps"];
|
||||||
|
cancelText?: ModalProps["cancelText"];
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
modalProps?: Omit<
|
||||||
|
ModalProps,
|
||||||
|
| "cancelButtonProps"
|
||||||
|
| "cancelText"
|
||||||
|
| "confirmLoading"
|
||||||
|
| "forceRender"
|
||||||
|
| "okButtonProps"
|
||||||
|
| "okText"
|
||||||
|
| "okType"
|
||||||
|
| "open"
|
||||||
|
| "title"
|
||||||
|
| "width"
|
||||||
|
| "onOk"
|
||||||
|
| "onCancel"
|
||||||
|
>;
|
||||||
|
okButtonProps?: ModalProps["okButtonProps"];
|
||||||
|
okText?: ModalProps["okText"];
|
||||||
|
open?: boolean;
|
||||||
|
title?: ModalProps["title"];
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
width?: ModalProps["width"];
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
onFinish?: (values: T) => void | Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalForm = <T extends NonNullable<unknown> = any>({
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
children,
|
||||||
|
cancelButtonProps,
|
||||||
|
cancelText,
|
||||||
|
form,
|
||||||
|
modalProps,
|
||||||
|
okButtonProps,
|
||||||
|
okText,
|
||||||
|
title,
|
||||||
|
trigger,
|
||||||
|
width,
|
||||||
|
onFinish,
|
||||||
|
...props
|
||||||
|
}: ModalFormProps<T>) => {
|
||||||
|
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||||
|
valuePropName: "open",
|
||||||
|
defaultValuePropName: "defaultOpen",
|
||||||
|
trigger: "onOpenChange",
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
|
|
||||||
|
const {
|
||||||
|
form: formInst,
|
||||||
|
formPending,
|
||||||
|
formProps,
|
||||||
|
submit,
|
||||||
|
} = useAntdForm({
|
||||||
|
form,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const ret = await onFinish?.(values);
|
||||||
|
if (ret != null && !ret) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mergedFormProps = { ...formProps, ...props };
|
||||||
|
|
||||||
|
const handleOkClick = async () => {
|
||||||
|
const ret = await submit();
|
||||||
|
if (ret != null && !ret) return;
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelClick = () => {
|
||||||
|
if (formPending) return;
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{triggerDom}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
cancelButtonProps={cancelButtonProps}
|
||||||
|
cancelText={cancelText}
|
||||||
|
confirmLoading={formPending}
|
||||||
|
forceRender={true}
|
||||||
|
okButtonProps={okButtonProps}
|
||||||
|
okText={okText}
|
||||||
|
okType="primary"
|
||||||
|
open={open}
|
||||||
|
title={title}
|
||||||
|
width={width}
|
||||||
|
{...modalProps}
|
||||||
|
onOk={handleOkClick}
|
||||||
|
onCancel={handleCancelClick}
|
||||||
|
>
|
||||||
|
<div className="pt-4 pb-2">
|
||||||
|
<Form className={className} style={style} form={formInst} {...mergedFormProps}>
|
||||||
|
{children}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalForm;
|
@ -3,10 +3,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { Button, Input, Space, type InputRef, type InputProps } from "antd";
|
import { Button, Input, Space, type InputRef, type InputProps } from "antd";
|
||||||
import {
|
import {
|
||||||
DownOutlined as DownOutlinedIcon,
|
ArrowDownOutlined as ArrowDownOutlinedIcon,
|
||||||
|
ArrowUpOutlined as ArrowUpOutlinedIcon,
|
||||||
MinusOutlined as MinusOutlinedIcon,
|
MinusOutlined as MinusOutlinedIcon,
|
||||||
PlusOutlined as PlusOutlinedIcon,
|
PlusOutlined as PlusOutlinedIcon,
|
||||||
UpOutlined as UpOutlinedIcon,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
@ -17,11 +17,11 @@ export type MultipleInputProps = Omit<InputProps, "count" | "defaultValue" | "sh
|
|||||||
minCount?: number;
|
minCount?: number;
|
||||||
showSortButton?: boolean;
|
showSortButton?: boolean;
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (index: number, e: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (value: string[]) => void;
|
||||||
onCreate?: (index: number) => void;
|
onValueChange?: (index: number, element: string) => void;
|
||||||
onRemove?: (index: number) => void;
|
onValueCreate?: (index: number) => void;
|
||||||
onSort?: (oldIndex: number, newIndex: number) => void;
|
onValueRemove?: (index: number) => void;
|
||||||
onValueChange?: (value: string[]) => void;
|
onValueSort?: (oldIndex: number, newIndex: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MultipleInput = ({
|
const MultipleInput = ({
|
||||||
@ -30,10 +30,10 @@ const MultipleInput = ({
|
|||||||
maxCount,
|
maxCount,
|
||||||
minCount,
|
minCount,
|
||||||
showSortButton = true,
|
showSortButton = true,
|
||||||
onChange,
|
onValueChange,
|
||||||
onCreate,
|
onValueCreate,
|
||||||
onSort,
|
onValueSort,
|
||||||
onRemove,
|
onValueRemove,
|
||||||
...props
|
...props
|
||||||
}: MultipleInputProps) => {
|
}: MultipleInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -44,7 +44,7 @@ const MultipleInput = ({
|
|||||||
valuePropName: "value",
|
valuePropName: "value",
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
defaultValuePropName: "defaultValue",
|
defaultValuePropName: "defaultValue",
|
||||||
trigger: "onValueChange",
|
trigger: "onChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
@ -54,16 +54,16 @@ const MultipleInput = ({
|
|||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 0);
|
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 0);
|
||||||
|
|
||||||
onCreate?.(newValue.length - 1);
|
onValueCreate?.(newValue.length - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (index: number, e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (index: number, element: string) => {
|
||||||
const newValue = produce(value, (draft) => {
|
const newValue = produce(value, (draft) => {
|
||||||
draft[index] = e.target.value;
|
draft[index] = element;
|
||||||
});
|
});
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
|
|
||||||
onChange?.(index, e);
|
onValueChange?.(index, element);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputBlur = (index: number) => {
|
const handleInputBlur = (index: number) => {
|
||||||
@ -87,7 +87,7 @@ const MultipleInput = ({
|
|||||||
});
|
});
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
|
|
||||||
onSort?.(index, index - 1);
|
onValueSort?.(index, index - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickDown = (index: number) => {
|
const handleClickDown = (index: number) => {
|
||||||
@ -102,7 +102,7 @@ const MultipleInput = ({
|
|||||||
});
|
});
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
|
|
||||||
onSort?.(index, index + 1);
|
onValueSort?.(index, index + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickAdd = (index: number) => {
|
const handleClickAdd = (index: number) => {
|
||||||
@ -112,7 +112,7 @@ const MultipleInput = ({
|
|||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
setTimeout(() => itemRefs.current[index + 1]?.focus(), 0);
|
setTimeout(() => itemRefs.current[index + 1]?.focus(), 0);
|
||||||
|
|
||||||
onCreate?.(index + 1);
|
onValueCreate?.(index + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickRemove = (index: number) => {
|
const handleClickRemove = (index: number) => {
|
||||||
@ -121,7 +121,7 @@ const MultipleInput = ({
|
|||||||
});
|
});
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
|
|
||||||
onRemove?.(index);
|
onValueRemove?.(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
return value == null || value.length === 0 ? (
|
return value == null || value.length === 0 ? (
|
||||||
@ -139,6 +139,7 @@ const MultipleInput = ({
|
|||||||
return (
|
return (
|
||||||
<MultipleInputItem
|
<MultipleInputItem
|
||||||
{...props}
|
{...props}
|
||||||
|
key={index}
|
||||||
ref={(ref) => (itemRefs.current[index] = ref!)}
|
ref={(ref) => (itemRefs.current[index] = ref!)}
|
||||||
allowAdd={allowAdd}
|
allowAdd={allowAdd}
|
||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
@ -150,12 +151,11 @@ const MultipleInput = ({
|
|||||||
showSortButton={showSortButton}
|
showSortButton={showSortButton}
|
||||||
value={element}
|
value={element}
|
||||||
onBlur={() => handleInputBlur(index)}
|
onBlur={() => handleInputBlur(index)}
|
||||||
onChange={(val) => handleInputChange(index, val)}
|
onChange={(val) => handleChange(index, val)}
|
||||||
onClickAdd={() => handleClickAdd(index)}
|
onClickAdd={() => handleClickAdd(index)}
|
||||||
onClickDown={() => handleClickDown(index)}
|
onClickDown={() => handleClickDown(index)}
|
||||||
onClickUp={() => handleClickUp(index)}
|
onClickUp={() => handleClickUp(index)}
|
||||||
onClickRemove={() => handleClickRemove(index)}
|
onClickRemove={() => handleClickRemove(index)}
|
||||||
onValueChange={undefined}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -165,7 +165,7 @@ const MultipleInput = ({
|
|||||||
|
|
||||||
type MultipleInputItemProps = Omit<
|
type MultipleInputItemProps = Omit<
|
||||||
MultipleInputProps,
|
MultipleInputProps,
|
||||||
"defaultValue" | "maxCount" | "minCount" | "preset" | "value" | "onChange" | "onCreate" | "onRemove" | "onSort" | "onValueChange"
|
"defaultValue" | "maxCount" | "minCount" | "preset" | "value" | "onChange" | "onValueCreate" | "onValueRemove" | "onValueSort" | "onValueChange"
|
||||||
> & {
|
> & {
|
||||||
allowAdd: boolean;
|
allowAdd: boolean;
|
||||||
allowRemove: boolean;
|
allowRemove: boolean;
|
||||||
@ -173,12 +173,11 @@ type MultipleInputItemProps = Omit<
|
|||||||
allowDown: boolean;
|
allowDown: boolean;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (value: string) => void;
|
||||||
onClickAdd?: () => void;
|
onClickAdd?: () => void;
|
||||||
onClickDown?: () => void;
|
onClickDown?: () => void;
|
||||||
onClickUp?: () => void;
|
onClickUp?: () => void;
|
||||||
onClickRemove?: () => void;
|
onClickRemove?: () => void;
|
||||||
onValueChange?: (value: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type MultipleInputItemInstance = {
|
type MultipleInputItemInstance = {
|
||||||
@ -198,7 +197,6 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
|
|||||||
disabled,
|
disabled,
|
||||||
showSortButton,
|
showSortButton,
|
||||||
size,
|
size,
|
||||||
onChange,
|
|
||||||
onClickAdd,
|
onClickAdd,
|
||||||
onClickDown,
|
onClickDown,
|
||||||
onClickUp,
|
onClickUp,
|
||||||
@ -213,16 +211,16 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
|
|||||||
valuePropName: "value",
|
valuePropName: "value",
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
defaultValuePropName: "defaultValue",
|
defaultValuePropName: "defaultValue",
|
||||||
trigger: "onValueChange",
|
trigger: "onChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const upBtn = useMemo(() => {
|
const upBtn = useMemo(() => {
|
||||||
if (!showSortButton) return null;
|
if (!showSortButton) return null;
|
||||||
return <Button icon={<UpOutlinedIcon />} color="default" disabled={disabled || !allowUp} type="text" onClick={onClickUp} />;
|
return <Button icon={<ArrowUpOutlinedIcon />} color="default" disabled={disabled || !allowUp} type="text" onClick={onClickUp} />;
|
||||||
}, [allowUp, disabled, showSortButton, onClickUp]);
|
}, [allowUp, disabled, showSortButton, onClickUp]);
|
||||||
const downBtn = useMemo(() => {
|
const downBtn = useMemo(() => {
|
||||||
if (!showSortButton) return null;
|
if (!showSortButton) return null;
|
||||||
return <Button icon={<DownOutlinedIcon />} color="default" disabled={disabled || !allowDown} type="text" onClick={onClickDown} />;
|
return <Button icon={<ArrowDownOutlinedIcon />} color="default" disabled={disabled || !allowDown} type="text" onClick={onClickDown} />;
|
||||||
}, [allowDown, disabled, showSortButton, onClickDown]);
|
}, [allowDown, disabled, showSortButton, onClickDown]);
|
||||||
const removeBtn = useMemo(() => {
|
const removeBtn = useMemo(() => {
|
||||||
return <Button icon={<MinusOutlinedIcon />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onClickRemove} />;
|
return <Button icon={<MinusOutlinedIcon />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onClickRemove} />;
|
||||||
@ -231,10 +229,8 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
|
|||||||
return <Button icon={<PlusOutlinedIcon />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onClickAdd} />;
|
return <Button icon={<PlusOutlinedIcon />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onClickAdd} />;
|
||||||
}, [allowAdd, disabled, onClickAdd]);
|
}, [allowAdd, disabled, onClickAdd]);
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
|
|
||||||
onChange?.(e);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@ -260,7 +256,7 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
|
|||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
defaultValue={undefined}
|
defaultValue={undefined}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button.Group size={size}>
|
<Button.Group size={size}>
|
||||||
|
@ -15,8 +15,7 @@ const NotifyChannelEditFormEmailFields = () => {
|
|||||||
.number({ message: t("settings.notification.channel.form.email_smtp_port.placeholder") })
|
.number({ message: t("settings.notification.channel.form.email_smtp_port.placeholder") })
|
||||||
.int()
|
.int()
|
||||||
.gte(1, t("common.errmsg.port_invalid"))
|
.gte(1, t("common.errmsg.port_invalid"))
|
||||||
.lte(65535, t("common.errmsg.port_invalid"))
|
.lte(65535, t("common.errmsg.port_invalid")),
|
||||||
.transform((v) => +v),
|
|
||||||
smtpTLS: z.boolean().nullish(),
|
smtpTLS: z.boolean().nullish(),
|
||||||
username: z
|
username: z
|
||||||
.string({ message: t("settings.notification.channel.form.email_username.placeholder") })
|
.string({ message: t("settings.notification.channel.form.email_username.placeholder") })
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from "react";
|
import { memo, useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { AutoComplete, Button, Divider, Form, Input, Select, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd";
|
import { AutoComplete, Button, Divider, Form, Input, Select, Space, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||||
import AccessSelect from "@/components/access/AccessSelect";
|
import AccessSelect from "@/components/access/AccessSelect";
|
||||||
|
import ModalForm from "@/components/core/ModalForm";
|
||||||
|
import MultipleInput from "@/components/core/MultipleInput";
|
||||||
import { usePanel } from "../PanelProvider";
|
import { usePanel } from "../PanelProvider";
|
||||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
||||||
import { ACCESS_PROVIDER_USAGES, accessProvidersMap } from "@/domain/access";
|
import { ACCESS_PROVIDER_USAGES, accessProvidersMap } from "@/domain/access";
|
||||||
@ -20,6 +22,8 @@ export type ApplyNodeFormProps = {
|
|||||||
data: WorkflowNode;
|
data: WorkflowNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MULTIPLE_INPUT_DELIMITER = ";";
|
||||||
|
|
||||||
const initFormModel = (): WorkflowNodeConfig => {
|
const initFormModel = (): WorkflowNodeConfig => {
|
||||||
return {
|
return {
|
||||||
domain: "",
|
domain: "",
|
||||||
@ -38,10 +42,10 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
const { hidePanel } = usePanel();
|
const { hidePanel } = usePanel();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
domain: z.string({ message: t("workflow.nodes.apply.form.domain.placeholder") }).refine(
|
domain: z.string({ message: t("workflow.nodes.apply.form.domains.placeholder") }).refine(
|
||||||
(str) => {
|
(v) => {
|
||||||
return String(str)
|
return String(v)
|
||||||
.split(";")
|
.split(MULTIPLE_INPUT_DELIMITER)
|
||||||
.every((e) => validDomainName(e, true));
|
.every((e) => validDomainName(e, true));
|
||||||
},
|
},
|
||||||
{ message: t("common.errmsg.domain_invalid") }
|
{ message: t("common.errmsg.domain_invalid") }
|
||||||
@ -52,20 +56,20 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
nameservers: z
|
nameservers: z
|
||||||
.string()
|
.string()
|
||||||
.refine(
|
.refine(
|
||||||
(str) => {
|
(v) => {
|
||||||
if (!str) return true;
|
if (!v) return true;
|
||||||
return String(str)
|
return String(v)
|
||||||
.split(";")
|
.split(MULTIPLE_INPUT_DELIMITER)
|
||||||
.every((e) => validDomainName(e) || validIPv4Address(e) || validIPv6Address(e));
|
.every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e));
|
||||||
},
|
},
|
||||||
{ message: t("common.errmsg.host_invalid") }
|
{ message: t("common.errmsg.host_invalid") }
|
||||||
)
|
)
|
||||||
.nullish(),
|
.nullish(),
|
||||||
timeout: z
|
propagationTimeout: z
|
||||||
.number()
|
.union([
|
||||||
.int()
|
z.number().int().gte(1, t("workflow.nodes.apply.form.propagation_timeout.placeholder")),
|
||||||
.gte(1, t("workflow.nodes.apply.form.propagation_timeout.placeholder"))
|
z.string().refine((v) => !v || (parseInt(v) === +v && +v > 0), { message: t("workflow.nodes.apply.form.propagation_timeout.placeholder") }),
|
||||||
.transform((v) => +v)
|
])
|
||||||
.nullish(),
|
.nullish(),
|
||||||
disableFollowCNAME: z.boolean().nullish(),
|
disableFollowCNAME: z.boolean().nullish(),
|
||||||
});
|
});
|
||||||
@ -83,15 +87,50 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [fieldDomains, setFieldDomains] = useState(data?.config?.domain as string);
|
||||||
|
const [fieldNameservers, setFieldNameservers] = useState(data?.config?.nameservers as string);
|
||||||
|
|
||||||
|
const handleFieldDomainsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFieldDomains(value);
|
||||||
|
formInst.setFieldValue("domain", value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFieldNameserversChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFieldNameservers(value);
|
||||||
|
formInst.setFieldValue("nameservers", value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="domain"
|
name="domain"
|
||||||
label={t("workflow.nodes.apply.form.domain.label")}
|
label={t("workflow.nodes.apply.form.domains.label")}
|
||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.domain.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.domains.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input placeholder={t("workflow.nodes.apply.form.domain.placeholder")} />
|
<Space.Compact style={{ width: "100%" }}>
|
||||||
|
<Input
|
||||||
|
disabled={formPending}
|
||||||
|
value={fieldDomains}
|
||||||
|
placeholder={t("workflow.nodes.apply.form.domains.placeholder")}
|
||||||
|
onChange={handleFieldDomainsChange}
|
||||||
|
/>
|
||||||
|
<FormFieldDomainsModalForm
|
||||||
|
data={fieldDomains}
|
||||||
|
disabled={formPending}
|
||||||
|
trigger={
|
||||||
|
<Button>
|
||||||
|
<FormOutlinedIcon />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onFinish={(v) => {
|
||||||
|
setFieldDomains(v);
|
||||||
|
formInst.setFieldValue("domain", v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -100,12 +139,12 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.email.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.email.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<ContactEmailSelect placeholder={t("workflow.nodes.apply.form.email.placeholder")} />
|
<FormFieldEmailSelect placeholder={t("workflow.nodes.apply.form.email.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<label className="block mb-1">
|
<label className="block mb-1">
|
||||||
<div className="flex items-center justify-between gap-4 w-full overflow-hidden">
|
<div className="flex items-center justify-between gap-4 w-full">
|
||||||
<div className="flex-grow max-w-full truncate">
|
<div className="flex-grow max-w-full truncate">
|
||||||
<span>{t("workflow.nodes.apply.form.access.label")}</span>
|
<span>{t("workflow.nodes.apply.form.access.label")}</span>
|
||||||
<Tooltip title={t("workflow.nodes.apply.form.access.tooltip")}>
|
<Tooltip title={t("workflow.nodes.apply.form.access.tooltip")}>
|
||||||
@ -166,7 +205,28 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.nameservers.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.nameservers.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input allowClear placeholder={t("workflow.nodes.apply.form.nameservers.placeholder")} />
|
<Space.Compact style={{ width: "100%" }}>
|
||||||
|
<Input
|
||||||
|
allowClear
|
||||||
|
disabled={formPending}
|
||||||
|
value={fieldNameservers}
|
||||||
|
placeholder={t("workflow.nodes.apply.form.nameservers.placeholder")}
|
||||||
|
onChange={handleFieldNameserversChange}
|
||||||
|
/>
|
||||||
|
<FormFieldNameserversModalForm
|
||||||
|
data={fieldNameservers}
|
||||||
|
disabled={formPending}
|
||||||
|
trigger={
|
||||||
|
<Button>
|
||||||
|
<FormOutlinedIcon />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onFinish={(v) => {
|
||||||
|
setFieldNameservers(v);
|
||||||
|
formInst.setFieldValue("nameservers", v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -203,7 +263,7 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContactEmailSelect = ({
|
const FormFieldEmailSelect = ({
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
disabled,
|
disabled,
|
||||||
@ -268,4 +328,126 @@ const ContactEmailSelect = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FormFieldDomainsModalForm = ({
|
||||||
|
data,
|
||||||
|
disabled,
|
||||||
|
trigger,
|
||||||
|
onFinish,
|
||||||
|
}: {
|
||||||
|
data: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
onFinish?: (data: string) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
domains: z.array(z.string()).refine(
|
||||||
|
(v) => {
|
||||||
|
return v.every((e) => !e?.trim() || validDomainName(e.trim(), true));
|
||||||
|
},
|
||||||
|
{ message: t("common.errmsg.domain_invalid") }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
const [formInst] = Form.useForm<z.infer<typeof formSchema>>();
|
||||||
|
|
||||||
|
const [model, setModel] = useState<Partial<z.infer<typeof formSchema>>>({ domains: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||||
|
useEffect(() => {
|
||||||
|
setModel({ domains: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleFinish = useCallback(
|
||||||
|
(values: z.infer<typeof formSchema>) => {
|
||||||
|
onFinish?.(
|
||||||
|
values.domains
|
||||||
|
.map((e) => e.trim())
|
||||||
|
.filter((e) => !!e)
|
||||||
|
.join(MULTIPLE_INPUT_DELIMITER)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[onFinish]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalForm
|
||||||
|
disabled={disabled}
|
||||||
|
layout="vertical"
|
||||||
|
form={formInst}
|
||||||
|
initialValues={model}
|
||||||
|
modalProps={{ destroyOnClose: true }}
|
||||||
|
title={t("workflow.nodes.apply.form.domains.multiple_input_modal.title")}
|
||||||
|
trigger={trigger}
|
||||||
|
validateTrigger="onSubmit"
|
||||||
|
width={480}
|
||||||
|
onFinish={handleFinish}
|
||||||
|
>
|
||||||
|
<Form.Item name="domains" rules={[formRule]}>
|
||||||
|
<MultipleInput placeholder={t("workflow.nodes.apply.form.domains.multiple_input_modal.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</ModalForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormFieldNameserversModalForm = ({
|
||||||
|
data,
|
||||||
|
disabled,
|
||||||
|
trigger,
|
||||||
|
onFinish,
|
||||||
|
}: {
|
||||||
|
data: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
trigger?: React.ReactNode;
|
||||||
|
onFinish?: (data: string) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
nameservers: z.array(z.string()).refine(
|
||||||
|
(v) => {
|
||||||
|
return v.every((e) => !e?.trim() || validIPv4Address(e) || validIPv6Address(e) || validDomainName(e));
|
||||||
|
},
|
||||||
|
{ message: t("common.errmsg.domain_invalid") }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
const [formInst] = Form.useForm<z.infer<typeof formSchema>>();
|
||||||
|
|
||||||
|
const [model, setModel] = useState<Partial<z.infer<typeof formSchema>>>({ nameservers: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||||
|
useEffect(() => {
|
||||||
|
setModel({ nameservers: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleFinish = useCallback(
|
||||||
|
(values: z.infer<typeof formSchema>) => {
|
||||||
|
onFinish?.(
|
||||||
|
values.nameservers
|
||||||
|
.map((e) => e.trim())
|
||||||
|
.filter((e) => !!e)
|
||||||
|
.join(MULTIPLE_INPUT_DELIMITER)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[onFinish]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalForm
|
||||||
|
disabled={disabled}
|
||||||
|
layout="vertical"
|
||||||
|
form={formInst}
|
||||||
|
initialValues={model}
|
||||||
|
modalProps={{ destroyOnClose: true }}
|
||||||
|
title={t("workflow.nodes.apply.form.nameservers.multiple_input_modal.title")}
|
||||||
|
trigger={trigger}
|
||||||
|
validateTrigger="onSubmit"
|
||||||
|
width={480}
|
||||||
|
onFinish={handleFinish}
|
||||||
|
>
|
||||||
|
<Form.Item name="nameservers" rules={[formRule]}>
|
||||||
|
<MultipleInput placeholder={t("workflow.nodes.apply.form.nameservers.multiple_input_modal.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</ModalForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default memo(ApplyNodeForm);
|
export default memo(ApplyNodeForm);
|
||||||
|
@ -71,7 +71,7 @@ const NotifyNodeForm = ({ data }: NotifyNodeFormProps) => {
|
|||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<label className="block mb-1">
|
<label className="block mb-1">
|
||||||
<div className="flex items-center justify-between gap-4 w-full overflow-hidden">
|
<div className="flex items-center justify-between gap-4 w-full">
|
||||||
<div className="flex-grow max-w-full truncate">{t("workflow.nodes.notify.form.channel.label")}</div>
|
<div className="flex-grow max-w-full truncate">{t("workflow.nodes.notify.form.channel.label")}</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Link className="ant-typography" to="/settings/notification" target="_blank">
|
<Link className="ant-typography" to="/settings/notification" target="_blank">
|
||||||
|
@ -5,7 +5,7 @@ import { useDeepCompareEffect } from "ahooks";
|
|||||||
export interface UseAntdFormOptions<T extends NonNullable<unknown> = any> {
|
export interface UseAntdFormOptions<T extends NonNullable<unknown> = any> {
|
||||||
form?: FormInstance<T>;
|
form?: FormInstance<T>;
|
||||||
initialValues?: Partial<T> | (() => Partial<T> | Promise<Partial<T>>);
|
initialValues?: Partial<T> | (() => Partial<T> | Promise<Partial<T>>);
|
||||||
onSubmit?: (values: T) => void | Promise<void>;
|
onSubmit?: (values: T) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseAntdFormReturns<T extends NonNullable<unknown> = any> {
|
export interface UseAntdFormReturns<T extends NonNullable<unknown> = any> {
|
||||||
|
@ -40,9 +40,11 @@
|
|||||||
"workflow.nodes.start.form.trigger_cron.tooltip": "Time zone is based on the server.",
|
"workflow.nodes.start.form.trigger_cron.tooltip": "Time zone is based on the server.",
|
||||||
"workflow.nodes.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
|
"workflow.nodes.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
|
||||||
"workflow.nodes.start.form.trigger_cron_alert.content": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Let’s Encrypt (ACME) client run at a random time?</a>",
|
"workflow.nodes.start.form.trigger_cron_alert.content": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Let’s Encrypt (ACME) client run at a random time?</a>",
|
||||||
"workflow.nodes.apply.form.domain.label": "Domain",
|
"workflow.nodes.apply.form.domains.label": "Domains",
|
||||||
"workflow.nodes.apply.form.domain.placeholder": "Please enter domain (separated by semicolons)",
|
"workflow.nodes.apply.form.domains.placeholder": "Please enter domains (separated by semicolons)",
|
||||||
"workflow.nodes.apply.form.domain.tooltip": "Wildcard domain: *.example.com",
|
"workflow.nodes.apply.form.domains.tooltip": "Wildcard domain: *.example.com",
|
||||||
|
"workflow.nodes.apply.form.domains.multiple_input_modal.title": "Change Domains",
|
||||||
|
"workflow.nodes.apply.form.domains.multiple_input_modal.placeholder": "Please enter domain",
|
||||||
"workflow.nodes.apply.form.email.label": "Contact Email",
|
"workflow.nodes.apply.form.email.label": "Contact Email",
|
||||||
"workflow.nodes.apply.form.email.placeholder": "Please enter contact email",
|
"workflow.nodes.apply.form.email.placeholder": "Please enter contact email",
|
||||||
"workflow.nodes.apply.form.email.tooltip": "Contact information required for SSL certificate application. Please pay attention to the <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">rate limits</a>.",
|
"workflow.nodes.apply.form.email.tooltip": "Contact information required for SSL certificate application. Please pay attention to the <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">rate limits</a>.",
|
||||||
@ -56,6 +58,8 @@
|
|||||||
"workflow.nodes.apply.form.nameservers.label": "DNS Recursive Nameservers",
|
"workflow.nodes.apply.form.nameservers.label": "DNS Recursive Nameservers",
|
||||||
"workflow.nodes.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
"workflow.nodes.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
||||||
"workflow.nodes.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.",
|
"workflow.nodes.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.",
|
||||||
|
"workflow.nodes.apply.form.nameservers.multiple_input_modal.title": "Change Nameservers",
|
||||||
|
"workflow.nodes.apply.form.nameservers.multiple_input_modal.placeholder": "Please enter nameserver",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.label": "DNS Propagation Timeout",
|
"workflow.nodes.apply.form.propagation_timeout.label": "DNS Propagation Timeout",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.placeholder": "Please enter DNS propagation timeout",
|
"workflow.nodes.apply.form.propagation_timeout.placeholder": "Please enter DNS propagation timeout",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.suffix": "Seconds",
|
"workflow.nodes.apply.form.propagation_timeout.suffix": "Seconds",
|
||||||
|
@ -40,9 +40,11 @@
|
|||||||
"workflow.nodes.start.form.trigger_cron.tooltip": "时区以服务器设置为准。",
|
"workflow.nodes.start.form.trigger_cron.tooltip": "时区以服务器设置为准。",
|
||||||
"workflow.nodes.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
|
"workflow.nodes.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
|
||||||
"workflow.nodes.start.form.trigger_cron_alert.content": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?</a>",
|
"workflow.nodes.start.form.trigger_cron_alert.content": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?</a>",
|
||||||
"workflow.nodes.apply.form.domain.label": "域名",
|
"workflow.nodes.apply.form.domains.label": "域名",
|
||||||
"workflow.nodes.apply.form.domain.placeholder": "请输入域名(多个值请用半角分号隔开)",
|
"workflow.nodes.apply.form.domains.placeholder": "请输入域名(多个值请用半角分号隔开)",
|
||||||
"workflow.nodes.apply.form.domain.tooltip": "泛域名表示形式为:*.example.com",
|
"workflow.nodes.apply.form.domains.tooltip": "泛域名表示形式为:*.example.com",
|
||||||
|
"workflow.nodes.apply.form.domains.multiple_input_modal.title": "修改域名",
|
||||||
|
"workflow.nodes.apply.form.domains.multiple_input_modal.placeholder": "请输入域名",
|
||||||
"workflow.nodes.apply.form.email.label": "联系邮箱",
|
"workflow.nodes.apply.form.email.label": "联系邮箱",
|
||||||
"workflow.nodes.apply.form.email.placeholder": "请输入联系邮箱",
|
"workflow.nodes.apply.form.email.placeholder": "请输入联系邮箱",
|
||||||
"workflow.nodes.apply.form.email.tooltip": "申请签发 SSL 证书时所需的联系方式。请注意 Let's Encrypt 账户注册的<a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">速率限制(点此了解更多)</a>。",
|
"workflow.nodes.apply.form.email.tooltip": "申请签发 SSL 证书时所需的联系方式。请注意 Let's Encrypt 账户注册的<a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">速率限制(点此了解更多)</a>。",
|
||||||
@ -56,6 +58,8 @@
|
|||||||
"workflow.nodes.apply.form.nameservers.label": "DNS 递归服务器",
|
"workflow.nodes.apply.form.nameservers.label": "DNS 递归服务器",
|
||||||
"workflow.nodes.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)",
|
"workflow.nodes.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)",
|
||||||
"workflow.nodes.apply.form.nameservers.tooltip": "在 ACME DNS-01 认证时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。",
|
"workflow.nodes.apply.form.nameservers.tooltip": "在 ACME DNS-01 认证时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。",
|
||||||
|
"workflow.nodes.apply.form.nameservers.multiple_input_modal.title": "修改 DNS 递归服务器",
|
||||||
|
"workflow.nodes.apply.form.nameservers.multiple_input_modal.placeholder": "请输入 DNS 递归服务器",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.label": "DNS 传播检查超时时间",
|
"workflow.nodes.apply.form.propagation_timeout.label": "DNS 传播检查超时时间",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间",
|
"workflow.nodes.apply.form.propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间",
|
||||||
"workflow.nodes.apply.form.propagation_timeout.suffix": "秒",
|
"workflow.nodes.apply.form.propagation_timeout.suffix": "秒",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user