mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 05:29:51 +00:00
feat(ui): AccessProviderPicker
This commit is contained in:
parent
07037e8d49
commit
0c42bb845d
@ -66,7 +66,7 @@ const DrawerForm = <T extends NonNullable<unknown> = any>({
|
||||
});
|
||||
|
||||
const mergedFormProps: FormProps = {
|
||||
clearOnDestroy: drawerProps?.destroyOnClose ? true : undefined,
|
||||
clearOnDestroy: drawerProps?.destroyOnHidden ? true : undefined,
|
||||
...formProps,
|
||||
...props,
|
||||
};
|
||||
|
@ -75,7 +75,7 @@ const ModalForm = <T extends NonNullable<unknown> = any>({
|
||||
});
|
||||
|
||||
const mergedFormProps: FormProps = {
|
||||
clearOnDestroy: modalProps?.destroyOnClose ? true : undefined,
|
||||
clearOnDestroy: modalProps?.destroyOnHidden ? true : undefined,
|
||||
...formProps,
|
||||
...props,
|
||||
};
|
||||
|
@ -93,7 +93,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, .
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
closable={!formPending}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
footer={
|
||||
<Space className="w-full justify-end">
|
||||
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
|
||||
|
@ -95,7 +95,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ..
|
||||
cancelButtonProps={{ disabled: formPending }}
|
||||
closable
|
||||
confirmLoading={formPending}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
loading={loading}
|
||||
okText={scene === "edit" ? t("common.button.save") : t("common.button.submit")}
|
||||
open={open}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { forwardRef, useImperativeHandle, useMemo } from "react";
|
||||
import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, type FormInstance, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import AccessProviderPicker from "@/components/provider/AccessProviderPicker";
|
||||
import AccessProviderSelect from "@/components/provider/AccessProviderSelect";
|
||||
import Show from "@/components/Show";
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
import { ACCESS_PROVIDERS, ACCESS_USAGES } from "@/domain/provider";
|
||||
import { ACCESS_PROVIDERS, ACCESS_USAGES, type AccessProvider } from "@/domain/provider";
|
||||
import { useAntdForm, useAntdFormName } from "@/hooks";
|
||||
|
||||
import AccessForm1PanelConfig from "./AccessForm1PanelConfig";
|
||||
@ -107,9 +109,22 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
name: "accessForm",
|
||||
initialValues: initialValues,
|
||||
});
|
||||
|
||||
const providerFilter = useMemo(() => {
|
||||
switch (usage) {
|
||||
case "both-dns-hosting":
|
||||
return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.DNS) || record.usages.includes(ACCESS_USAGES.HOSTING);
|
||||
case "ca-only":
|
||||
return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.CA);
|
||||
case "notification-only":
|
||||
return (record: AccessProvider) => record.usages.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [usage]);
|
||||
const providerLabel = useMemo(() => {
|
||||
switch (usage) {
|
||||
case "ca-only":
|
||||
@ -139,10 +154,11 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return undefined;
|
||||
}, [usage]);
|
||||
|
||||
const fieldProvider = Form.useWatch("provider", formInst);
|
||||
const fieldProvider = Form.useWatch<z.infer<typeof formSchema>["provider"]>("provider", formInst);
|
||||
const [fieldProviderPicked, setFieldProviderPicked] = useState<string>(initialValues?.provider); // bugfix: Form.useWatch 在条件渲染下不生效,这里用单独的变量存放 Picker 组件选择的值
|
||||
|
||||
const [nestedFormInst] = Form.useForm();
|
||||
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "accessEditFormConfigForm" });
|
||||
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "accessConfigForm" });
|
||||
const nestedFormEl = useMemo(() => {
|
||||
const nestedFormProps = {
|
||||
form: nestedFormInst,
|
||||
@ -272,7 +288,13 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
case ACCESS_PROVIDERS.ZEROSSL:
|
||||
return <AccessFormZeroSSLConfig {...nestedFormProps} />;
|
||||
}
|
||||
}, [disabled, initialValues?.config, fieldProvider, nestedFormInst, nestedFormName]);
|
||||
}, [usage, disabled, initialValues?.config, fieldProvider, nestedFormInst, nestedFormName]);
|
||||
|
||||
const handleProviderPick = (value: string) => {
|
||||
setFieldProviderPicked(value);
|
||||
formInst.setFieldValue("provider", value);
|
||||
onValuesChange?.(formInst.getFieldsValue(true));
|
||||
};
|
||||
|
||||
const handleFormProviderChange = (name: string) => {
|
||||
if (name === nestedFormName) {
|
||||
@ -312,30 +334,32 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
<Form.Provider onFormChange={handleFormProviderChange}>
|
||||
<div className={className} style={style}>
|
||||
<Form {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Show
|
||||
when={!!fieldProvider || !!fieldProviderPicked}
|
||||
fallback={
|
||||
<AccessProviderPicker
|
||||
autoFocus
|
||||
filter={providerFilter}
|
||||
placeholder={t("access.form.provider.search.placeholder")}
|
||||
showOptionTags={usage == null || (usage === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)}
|
||||
onSelect={handleProviderPick}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form.Item name="name" label={t("access.form.name.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("access.form.name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="provider" label={providerLabel} rules={[formRule]} tooltip={providerTooltip}>
|
||||
<AccessProviderSelect
|
||||
filter={(record) => {
|
||||
if (usage == null) return true;
|
||||
|
||||
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 "notification-only":
|
||||
return record.usages.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
}
|
||||
}}
|
||||
filter={providerFilter}
|
||||
disabled={scene !== "add"}
|
||||
placeholder={providerPlaceholder}
|
||||
showOptionTags={usage == null || (usage === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)}
|
||||
showSearch={!disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</Form>
|
||||
|
||||
{nestedFormEl}
|
||||
|
@ -283,7 +283,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
||||
</Form.Item>
|
||||
|
||||
<Show when={!usage || usage === "deployment"}>
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -312,7 +312,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
||||
</Show>
|
||||
|
||||
<Show when={!usage || usage === "notification"}>
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
|
@ -29,7 +29,7 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
open={open}
|
||||
loading={loading}
|
||||
placement="right"
|
||||
|
@ -3,17 +3,18 @@ import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { acmeDns01ProvidersMap } from "@/domain/provider";
|
||||
import { type ACMEDns01Provider, acmeDns01ProvidersMap } from "@/domain/provider";
|
||||
|
||||
export type ACMEDns01ProviderPickerProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoFocus?: boolean;
|
||||
filter?: (record: ACMEDns01Provider) => boolean;
|
||||
placeholder?: string;
|
||||
onSelect?: (value: string) => void;
|
||||
};
|
||||
|
||||
const ACMEDns01ProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: ACMEDns01ProviderPickerProps) => {
|
||||
const ACMEDns01ProviderPicker = ({ className, style, autoFocus, filter, placeholder, onSelect }: ACMEDns01ProviderPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [keyword, setKeyword] = useState<string>();
|
||||
@ -25,7 +26,15 @@ const ACMEDns01ProviderPicker = ({ className, style, autoFocus, placeholder, onS
|
||||
}, []);
|
||||
|
||||
const providers = useMemo(() => {
|
||||
return Array.from(acmeDns01ProvidersMap.values()).filter((provider) => {
|
||||
return Array.from(acmeDns01ProvidersMap.values())
|
||||
.filter((provider) => {
|
||||
if (filter) {
|
||||
return filter(provider);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((provider) => {
|
||||
if (keyword) {
|
||||
const value = keyword.toLowerCase();
|
||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
||||
@ -33,7 +42,7 @@ const ACMEDns01ProviderPicker = ({ className, style, autoFocus, placeholder, onS
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [keyword]);
|
||||
}, [filter, keyword]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
onSelect?.(value);
|
||||
|
117
ui/src/components/provider/AccessProviderPicker.tsx
Normal file
117
ui/src/components/provider/AccessProviderPicker.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tag, Typography } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvidersMap } from "@/domain/provider";
|
||||
|
||||
export type AccessProviderPickerProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoFocus?: boolean;
|
||||
filter?: (record: AccessProvider) => boolean;
|
||||
placeholder?: string;
|
||||
showOptionTags?: boolean | { [key in AccessUsageType]?: boolean };
|
||||
onSelect?: (value: string) => void;
|
||||
};
|
||||
|
||||
const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder, showOptionTags, onSelect }: AccessProviderPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [keyword, setKeyword] = useState<string>();
|
||||
const keywordInputRef = useRef<InputRef>(null);
|
||||
useEffect(() => {
|
||||
if (autoFocus) {
|
||||
setTimeout(() => keywordInputRef.current?.focus(), 1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const providers = useMemo(() => {
|
||||
return Array.from(accessProvidersMap.values())
|
||||
.filter((provider) => {
|
||||
if (filter) {
|
||||
return filter(provider);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((provider) => {
|
||||
if (keyword) {
|
||||
const value = keyword.toLowerCase();
|
||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [filter, keyword]);
|
||||
|
||||
const showOptionTagForDNS = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.DNS] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForHosting = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.HOSTING] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForCA = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.CA] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
const showOptionTagForNotification = useMemo(() => {
|
||||
return typeof showOptionTags === "object" ? !!showOptionTags[ACCESS_USAGES.NOTIFICATION] : !!showOptionTags;
|
||||
}, [showOptionTags]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
onSelect?.(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<Input.Search ref={keywordInputRef} placeholder={placeholder ?? t("common.text.search")} onChange={(e) => setKeyword(e.target.value.trim())} />
|
||||
|
||||
<div className="mt-4">
|
||||
<Show when={providers.length > 0} fallback={<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{providers.map((provider, index) => {
|
||||
return (
|
||||
<Col key={index} xs={24} md={12} span={8}>
|
||||
<Card
|
||||
className="h-20 w-full overflow-hidden shadow-sm"
|
||||
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
|
||||
hoverable
|
||||
onClick={() => {
|
||||
handleProviderTypeSelect(provider.type);
|
||||
}}
|
||||
>
|
||||
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
||||
<Avatar src={provider.icon} size="small" />
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Typography.Text className="mb-1 line-clamp-1">{t(provider.name)}</Typography.Text>
|
||||
<div className="mx-[-30px] scale-[80%]">
|
||||
<Show when={provider.builtin}>
|
||||
<Tag>{t("access.props.provider.builtin")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
||||
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
||||
<Tag color="geekblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
||||
<Tag color="magenta">{t("access.props.provider.usage.ca")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
||||
<Tag color="cyan">{t("access.props.provider.usage.notification")}</Tag>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AccessProviderPicker);
|
@ -56,19 +56,19 @@ const AccessProviderSelect = ({ filter, showOptionTags, ...props }: AccessProvid
|
||||
</Space>
|
||||
<div>
|
||||
<Show when={provider.builtin}>
|
||||
<Tag color="grey">{t("access.props.provider.builtin")}</Tag>
|
||||
<Tag>{t("access.props.provider.builtin")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
||||
<Tag color="peru">{t("access.props.provider.usage.dns")}</Tag>
|
||||
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
||||
<Tag color="royalblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
<Tag color="geekblue">{t("access.props.provider.usage.hosting")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
||||
<Tag color="crimson">{t("access.props.provider.usage.ca")}</Tag>
|
||||
<Tag color="magenta">{t("access.props.provider.usage.ca")}</Tag>
|
||||
</Show>
|
||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
||||
<Tag color="mediumaquamarine">{t("access.props.provider.usage.notification")}</Tag>
|
||||
<Tag color="cyan">{t("access.props.provider.usage.notification")}</Tag>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,17 +3,18 @@ import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tabs, Tooltip, Typography } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { DEPLOYMENT_CATEGORIES, deploymentProvidersMap } from "@/domain/provider";
|
||||
import { DEPLOYMENT_CATEGORIES, type DeploymentProvider, deploymentProvidersMap } from "@/domain/provider";
|
||||
|
||||
export type DeploymentProviderPickerProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoFocus?: boolean;
|
||||
filter?: (record: DeploymentProvider) => boolean;
|
||||
placeholder?: string;
|
||||
onSelect?: (value: string) => void;
|
||||
};
|
||||
|
||||
const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: DeploymentProviderPickerProps) => {
|
||||
const DeploymentProviderPicker = ({ className, style, autoFocus, filter, placeholder, onSelect }: DeploymentProviderPickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [category, setCategory] = useState<string>(DEPLOYMENT_CATEGORIES.ALL);
|
||||
@ -28,6 +29,13 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, on
|
||||
|
||||
const providers = useMemo(() => {
|
||||
return Array.from(deploymentProvidersMap.values())
|
||||
.filter((provider) => {
|
||||
if (filter) {
|
||||
return filter(provider);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((provider) => {
|
||||
if (category && category !== DEPLOYMENT_CATEGORIES.ALL) {
|
||||
return provider.category === category;
|
||||
@ -43,7 +51,7 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, on
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [category, keyword]);
|
||||
}, [filter, category, keyword]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
onSelect?.(value);
|
||||
|
@ -30,7 +30,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
||||
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
open={open}
|
||||
loading={loading}
|
||||
placement="right"
|
||||
|
@ -350,7 +350,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -406,7 +406,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</Divider>
|
||||
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -435,7 +435,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0" hidden={!showCAProviderAccess}>
|
||||
<Form.Item className="mb-0" htmlFor="null" hidden={!showCAProviderAccess}>
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -703,7 +703,7 @@ const DomainsModalInput = memo(({ value, trigger, onChange }: { value?: string;
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.apply.form.domains.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
@ -743,7 +743,7 @@ const NameserversModalInput = memo(({ trigger, value, onChange }: { trigger?: Re
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.apply.form.nameservers.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
|
@ -391,7 +391,9 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Show
|
||||
when={!!fieldProvider}
|
||||
fallback={<DeploymentProviderPicker autoFocus placeholder={t("workflow_node.deploy.search.provider.placeholder")} onSelect={handleProviderPick} />}
|
||||
fallback={
|
||||
<DeploymentProviderPicker autoFocus placeholder={t("workflow_node.deploy.form.provider.search.placeholder")} onSelect={handleProviderPick} />
|
||||
}
|
||||
>
|
||||
<Form.Item name="provider" label={t("workflow_node.deploy.form.provider.label")} rules={[formRule]}>
|
||||
<DeploymentProviderSelect
|
||||
@ -404,7 +406,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0" hidden={!showProviderAccess}>
|
||||
<Form.Item className="mb-0" htmlFor="null" hidden={!showProviderAccess}>
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -450,15 +452,6 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldProvider === DEPLOYMENT_PROVIDERS.LOCAL}>
|
||||
<Form.Item>
|
||||
<Alert
|
||||
type="info"
|
||||
message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.provider_access.guide_for_local") }}></span>}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item
|
||||
name="certificate"
|
||||
label={t("workflow_node.deploy.form.certificate.label")}
|
||||
|
@ -186,7 +186,7 @@ const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: stri
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
@ -226,7 +226,7 @@ const ContactIdsModalInput = memo(({ value, trigger, onChange }: { value?: strin
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
|
@ -173,7 +173,7 @@ const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
||||
import { Button, Dropdown, Form, type FormInstance, Input, Select } from "antd";
|
||||
import { Alert, Button, Dropdown, Form, type FormInstance, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
@ -289,6 +289,10 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item>
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.local.guide") }}></span>} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="format" label={t("workflow_node.deploy.form.local_format.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.local_format.placeholder")} onSelect={handleFormatSelect}>
|
||||
<Select.Option key={FORMAT_PEM} value={FORMAT_PEM}>
|
||||
@ -377,7 +381,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -407,7 +411,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
|
@ -252,7 +252,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
||||
<Select options={[{ value: t("workflow_node.deploy.form.ssh_shell_env.value") }]} value={t("workflow_node.deploy.form.ssh_shell_env.value")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
@ -282,7 +282,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
|
@ -160,7 +160,7 @@ const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: stri
|
||||
{...formProps}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
title={t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
|
@ -180,7 +180,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate line-through">{t("workflow_node.notify.form.channel.label")}</div>
|
||||
@ -224,7 +224,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<Form.Item className="mb-0" htmlFor="null">
|
||||
<label className="mb-1 block">
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="max-w-full grow truncate">
|
||||
|
@ -285,7 +285,7 @@ const SharedNodeConfigDrawer = ({
|
||||
<Drawer
|
||||
afterOpenChange={setOpen}
|
||||
closable={!pending}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
extra={
|
||||
<SharedNodeMenu
|
||||
node={node}
|
||||
|
@ -29,6 +29,7 @@
|
||||
"access.form.provider.label": "Provider",
|
||||
"access.form.provider.placeholder": "Please select a provider",
|
||||
"access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.<br>Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.<br><br><i>Cannot be edited after saving.</i>",
|
||||
"access.form.provider.search.placeholder": "Search provider ...",
|
||||
"access.form.certificate_authority.label": "Certificate authority",
|
||||
"access.form.certificate_authority.placeholder": "Please select a certificate authority",
|
||||
"access.form.notification_channel.label": "Notification channel",
|
||||
|
@ -98,14 +98,13 @@
|
||||
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "Be careful not to exceed the validity period limit of the issued certificate, otherwise the certificate may never be renewed.",
|
||||
|
||||
"workflow_node.deploy.label": "Deployment",
|
||||
"workflow_node.deploy.search.provider.placeholder": "Search deploy target ...",
|
||||
"workflow_node.deploy.form.provider.label": "Deploy target",
|
||||
"workflow_node.deploy.form.provider.placeholder": "Please select deploy target",
|
||||
"workflow_node.deploy.form.provider.search.placeholder": "Search deploy target ...",
|
||||
"workflow_node.deploy.form.provider_access.label": "Hosting provider authorization",
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of Hosting provider",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "Used to invoke API during deployment.",
|
||||
"workflow_node.deploy.form.provider_access.button": "Create",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.",
|
||||
"workflow_node.deploy.form.certificate.label": "Certificate",
|
||||
"workflow_node.deploy.form.certificate.placeholder": "Please select certificate",
|
||||
"workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.",
|
||||
@ -441,6 +440,7 @@
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key",
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key",
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/secret/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/secret/</a>",
|
||||
"workflow_node.deploy.form.local.guide": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.",
|
||||
"workflow_node.deploy.form.local_format.label": "File format",
|
||||
"workflow_node.deploy.form.local_format.placeholder": "Please select file format",
|
||||
"workflow_node.deploy.form.local_format.option.pem.label": "PEM (*.pem, *.crt, *.key)",
|
||||
|
@ -29,6 +29,7 @@
|
||||
"access.form.provider.label": "提供商",
|
||||
"access.form.provider.placeholder": "请选择提供商",
|
||||
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
||||
"access.form.provider.search.placeholder": "搜索提供商……",
|
||||
"access.form.certificate_authority.label": "证书颁发机构",
|
||||
"access.form.certificate_authority.placeholder": "请选择证书颁发机构",
|
||||
"access.form.notification_channel.label": "通知渠道",
|
||||
|
@ -97,14 +97,13 @@
|
||||
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
|
||||
|
||||
"workflow_node.deploy.label": "部署证书",
|
||||
"workflow_node.deploy.search.provider.placeholder": "搜索部署目标……",
|
||||
"workflow_node.deploy.form.provider.label": "部署目标",
|
||||
"workflow_node.deploy.form.provider.placeholder": "请选择部署目标",
|
||||
"workflow_node.deploy.form.provider.search.placeholder": "搜索部署目标……",
|
||||
"workflow_node.deploy.form.provider_access.label": "主机提供商授权",
|
||||
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
|
||||
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书时调用相关 API,注意与申请阶段所需的 DNS 提供商相区分。",
|
||||
"workflow_node.deploy.form.provider_access.button": "新建",
|
||||
"workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。",
|
||||
"workflow_node.deploy.form.certificate.label": "待部署证书",
|
||||
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
|
||||
"workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。",
|
||||
@ -440,6 +439,7 @@
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)",
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键",
|
||||
"workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/</a>",
|
||||
"workflow_node.deploy.form.local.guide": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。",
|
||||
"workflow_node.deploy.form.local_format.label": "文件格式",
|
||||
"workflow_node.deploy.form.local_format.placeholder": "请选择文件格式",
|
||||
"workflow_node.deploy.form.local_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)",
|
||||
|
@ -162,7 +162,7 @@ const SiderMenuDrawer = memo(({ trigger }: { trigger: React.ReactNode }) => {
|
||||
|
||||
<Drawer
|
||||
closable={false}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
open={siderOpen}
|
||||
placement="left"
|
||||
styles={{
|
||||
|
@ -374,7 +374,7 @@ const WorkflowBaseInfoModal = ({ trigger }: { trigger?: React.ReactNode }) => {
|
||||
disabled={formPending}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
okText={t("common.button.save")}
|
||||
title={t(`workflow.detail.baseinfo.modal.title`)}
|
||||
trigger={trigger}
|
||||
|
@ -165,7 +165,7 @@ const WorkflowNew = () => {
|
||||
disabled={formPending}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
okText={t("common.button.submit")}
|
||||
open={formModalOpen}
|
||||
title={t(`workflow.new.modal.title`)}
|
||||
|
Loading…
x
Reference in New Issue
Block a user