mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 13:39:53 +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 = {
|
const mergedFormProps: FormProps = {
|
||||||
clearOnDestroy: drawerProps?.destroyOnClose ? true : undefined,
|
clearOnDestroy: drawerProps?.destroyOnHidden ? true : undefined,
|
||||||
...formProps,
|
...formProps,
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
|
@ -75,7 +75,7 @@ const ModalForm = <T extends NonNullable<unknown> = any>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mergedFormProps: FormProps = {
|
const mergedFormProps: FormProps = {
|
||||||
clearOnDestroy: modalProps?.destroyOnClose ? true : undefined,
|
clearOnDestroy: modalProps?.destroyOnHidden ? true : undefined,
|
||||||
...formProps,
|
...formProps,
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
|
@ -93,7 +93,7 @@ const AccessEditDrawer = ({ data, loading, trigger, scene, usage, afterSubmit, .
|
|||||||
<Drawer
|
<Drawer
|
||||||
afterOpenChange={setOpen}
|
afterOpenChange={setOpen}
|
||||||
closable={!formPending}
|
closable={!formPending}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
footer={
|
footer={
|
||||||
<Space className="w-full justify-end">
|
<Space className="w-full justify-end">
|
||||||
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
|
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
|
||||||
|
@ -95,7 +95,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, ..
|
|||||||
cancelButtonProps={{ disabled: formPending }}
|
cancelButtonProps={{ disabled: formPending }}
|
||||||
closable
|
closable
|
||||||
confirmLoading={formPending}
|
confirmLoading={formPending}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
loading={loading}
|
loading={loading}
|
||||||
okText={scene === "edit" ? t("common.button.save") : t("common.button.submit")}
|
okText={scene === "edit" ? t("common.button.save") : t("common.button.submit")}
|
||||||
open={open}
|
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 { useTranslation } from "react-i18next";
|
||||||
import { Form, type FormInstance, Input } from "antd";
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import AccessProviderPicker from "@/components/provider/AccessProviderPicker";
|
||||||
import AccessProviderSelect from "@/components/provider/AccessProviderSelect";
|
import AccessProviderSelect from "@/components/provider/AccessProviderSelect";
|
||||||
|
import Show from "@/components/Show";
|
||||||
import { type AccessModel } from "@/domain/access";
|
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 { useAntdForm, useAntdFormName } from "@/hooks";
|
||||||
|
|
||||||
import AccessForm1PanelConfig from "./AccessForm1PanelConfig";
|
import AccessForm1PanelConfig from "./AccessForm1PanelConfig";
|
||||||
@ -107,9 +109,22 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
const { form: formInst, formProps } = useAntdForm({
|
const { form: formInst, formProps } = useAntdForm({
|
||||||
|
name: "accessForm",
|
||||||
initialValues: initialValues,
|
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(() => {
|
const providerLabel = useMemo(() => {
|
||||||
switch (usage) {
|
switch (usage) {
|
||||||
case "ca-only":
|
case "ca-only":
|
||||||
@ -139,10 +154,11 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [usage]);
|
}, [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 [nestedFormInst] = Form.useForm();
|
||||||
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "accessEditFormConfigForm" });
|
const nestedFormName = useAntdFormName({ form: nestedFormInst, name: "accessConfigForm" });
|
||||||
const nestedFormEl = useMemo(() => {
|
const nestedFormEl = useMemo(() => {
|
||||||
const nestedFormProps = {
|
const nestedFormProps = {
|
||||||
form: nestedFormInst,
|
form: nestedFormInst,
|
||||||
@ -272,7 +288,13 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
case ACCESS_PROVIDERS.ZEROSSL:
|
case ACCESS_PROVIDERS.ZEROSSL:
|
||||||
return <AccessFormZeroSSLConfig {...nestedFormProps} />;
|
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) => {
|
const handleFormProviderChange = (name: string) => {
|
||||||
if (name === nestedFormName) {
|
if (name === nestedFormName) {
|
||||||
@ -312,30 +334,32 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
<Form.Provider onFormChange={handleFormProviderChange}>
|
<Form.Provider onFormChange={handleFormProviderChange}>
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
<Form {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
<Form {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
<Form.Item name="name" label={t("access.form.name.label")} rules={[formRule]}>
|
<Show
|
||||||
<Input placeholder={t("access.form.name.placeholder")} />
|
when={!!fieldProvider || !!fieldProviderPicked}
|
||||||
</Form.Item>
|
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}>
|
<Form.Item name="provider" label={providerLabel} rules={[formRule]} tooltip={providerTooltip}>
|
||||||
<AccessProviderSelect
|
<AccessProviderSelect
|
||||||
filter={(record) => {
|
filter={providerFilter}
|
||||||
if (usage == null) return true;
|
disabled={scene !== "add"}
|
||||||
|
placeholder={providerPlaceholder}
|
||||||
switch (usage) {
|
showOptionTags={usage == null || (usage === "both-dns-hosting" ? { [ACCESS_USAGES.DNS]: true, [ACCESS_USAGES.HOSTING]: true } : false)}
|
||||||
case "both-dns-hosting":
|
showSearch={!disabled}
|
||||||
return record.usages.includes(ACCESS_USAGES.DNS) || record.usages.includes(ACCESS_USAGES.HOSTING);
|
/>
|
||||||
case "ca-only":
|
</Form.Item>
|
||||||
return record.usages.includes(ACCESS_USAGES.CA);
|
</Show>
|
||||||
case "notification-only":
|
|
||||||
return record.usages.includes(ACCESS_USAGES.NOTIFICATION);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
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>
|
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{nestedFormEl}
|
{nestedFormEl}
|
||||||
|
@ -283,7 +283,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Show when={!usage || usage === "deployment"}>
|
<Show when={!usage || usage === "deployment"}>
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -312,7 +312,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!usage || usage === "notification"}>
|
<Show when={!usage || usage === "notification"}>
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
|
@ -29,7 +29,7 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
|
|||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
afterOpenChange={setOpen}
|
afterOpenChange={setOpen}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={open}
|
open={open}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
placement="right"
|
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 { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd";
|
||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { acmeDns01ProvidersMap } from "@/domain/provider";
|
import { type ACMEDns01Provider, acmeDns01ProvidersMap } from "@/domain/provider";
|
||||||
|
|
||||||
export type ACMEDns01ProviderPickerProps = {
|
export type ACMEDns01ProviderPickerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
filter?: (record: ACMEDns01Provider) => boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onSelect?: (value: string) => void;
|
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 { t } = useTranslation();
|
||||||
|
|
||||||
const [keyword, setKeyword] = useState<string>();
|
const [keyword, setKeyword] = useState<string>();
|
||||||
@ -25,15 +26,23 @@ const ACMEDns01ProviderPicker = ({ className, style, autoFocus, placeholder, onS
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const providers = useMemo(() => {
|
const providers = useMemo(() => {
|
||||||
return Array.from(acmeDns01ProvidersMap.values()).filter((provider) => {
|
return Array.from(acmeDns01ProvidersMap.values())
|
||||||
if (keyword) {
|
.filter((provider) => {
|
||||||
const value = keyword.toLowerCase();
|
if (filter) {
|
||||||
return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value);
|
return filter(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
})
|
||||||
}, [keyword]);
|
.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 handleProviderTypeSelect = (value: string) => {
|
const handleProviderTypeSelect = (value: string) => {
|
||||||
onSelect?.(value);
|
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>
|
</Space>
|
||||||
<div>
|
<div>
|
||||||
<Show when={provider.builtin}>
|
<Show when={provider.builtin}>
|
||||||
<Tag color="grey">{t("access.props.provider.builtin")}</Tag>
|
<Tag>{t("access.props.provider.builtin")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={showOptionTagForDNS && provider.usages.includes(ACCESS_USAGES.DNS)}>
|
<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>
|
||||||
<Show when={showOptionTagForHosting && provider.usages.includes(ACCESS_USAGES.HOSTING)}>
|
<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>
|
||||||
<Show when={showOptionTagForCA && provider.usages.includes(ACCESS_USAGES.CA)}>
|
<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>
|
||||||
<Show when={showOptionTagForNotification && provider.usages.includes(ACCESS_USAGES.NOTIFICATION)}>
|
<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>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</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 { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tabs, Tooltip, Typography } from "antd";
|
||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { DEPLOYMENT_CATEGORIES, deploymentProvidersMap } from "@/domain/provider";
|
import { DEPLOYMENT_CATEGORIES, type DeploymentProvider, deploymentProvidersMap } from "@/domain/provider";
|
||||||
|
|
||||||
export type DeploymentProviderPickerProps = {
|
export type DeploymentProviderPickerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
filter?: (record: DeploymentProvider) => boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onSelect?: (value: string) => void;
|
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 { t } = useTranslation();
|
||||||
|
|
||||||
const [category, setCategory] = useState<string>(DEPLOYMENT_CATEGORIES.ALL);
|
const [category, setCategory] = useState<string>(DEPLOYMENT_CATEGORIES.ALL);
|
||||||
@ -28,6 +29,13 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, on
|
|||||||
|
|
||||||
const providers = useMemo(() => {
|
const providers = useMemo(() => {
|
||||||
return Array.from(deploymentProvidersMap.values())
|
return Array.from(deploymentProvidersMap.values())
|
||||||
|
.filter((provider) => {
|
||||||
|
if (filter) {
|
||||||
|
return filter(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.filter((provider) => {
|
.filter((provider) => {
|
||||||
if (category && category !== DEPLOYMENT_CATEGORIES.ALL) {
|
if (category && category !== DEPLOYMENT_CATEGORIES.ALL) {
|
||||||
return provider.category === category;
|
return provider.category === category;
|
||||||
@ -43,7 +51,7 @@ const DeploymentProviderPicker = ({ className, style, autoFocus, placeholder, on
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [category, keyword]);
|
}, [filter, category, keyword]);
|
||||||
|
|
||||||
const handleProviderTypeSelect = (value: string) => {
|
const handleProviderTypeSelect = (value: string) => {
|
||||||
onSelect?.(value);
|
onSelect?.(value);
|
||||||
|
@ -30,7 +30,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
|||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
afterOpenChange={setOpen}
|
afterOpenChange={setOpen}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={open}
|
open={open}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
placement="right"
|
placement="right"
|
||||||
|
@ -350,7 +350,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -406,7 +406,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
</Divider>
|
</Divider>
|
||||||
|
|
||||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
<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">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -435,7 +435,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</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">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -703,7 +703,7 @@ const DomainsModalInput = memo(({ value, trigger, onChange }: { value?: string;
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.apply.form.domains.multiple_input_modal.title")}
|
title={t("workflow_node.apply.form.domains.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
validateTrigger="onSubmit"
|
||||||
@ -743,7 +743,7 @@ const NameserversModalInput = memo(({ trigger, value, onChange }: { trigger?: Re
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.apply.form.nameservers.multiple_input_modal.title")}
|
title={t("workflow_node.apply.form.nameservers.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
validateTrigger="onSubmit"
|
||||||
|
@ -391,7 +391,9 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
|||||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
<Show
|
<Show
|
||||||
when={!!fieldProvider}
|
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]}>
|
<Form.Item name="provider" label={t("workflow_node.deploy.form.provider.label")} rules={[formRule]}>
|
||||||
<DeploymentProviderSelect
|
<DeploymentProviderSelect
|
||||||
@ -404,7 +406,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0" hidden={!showProviderAccess}>
|
<Form.Item className="mb-0" htmlFor="null" hidden={!showProviderAccess}>
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -450,15 +452,6 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</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
|
<Form.Item
|
||||||
name="certificate"
|
name="certificate"
|
||||||
label={t("workflow_node.deploy.form.certificate.label")}
|
label={t("workflow_node.deploy.form.certificate.label")}
|
||||||
|
@ -186,7 +186,7 @@ const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: stri
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title")}
|
title={t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
validateTrigger="onSubmit"
|
||||||
@ -226,7 +226,7 @@ const ContactIdsModalInput = memo(({ value, trigger, onChange }: { value?: strin
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title")}
|
title={t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
validateTrigger="onSubmit"
|
||||||
|
@ -173,7 +173,7 @@ const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title")}
|
title={t("workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
validateTrigger="onSubmit"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
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 { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -289,6 +289,10 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
name={formName}
|
name={formName}
|
||||||
onValuesChange={handleFormChange}
|
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]}>
|
<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 placeholder={t("workflow_node.deploy.form.local_format.placeholder")} onSelect={handleFormatSelect}>
|
||||||
<Select.Option key={FORMAT_PEM} value={FORMAT_PEM}>
|
<Select.Option key={FORMAT_PEM} value={FORMAT_PEM}>
|
||||||
@ -377,7 +381,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -407,7 +411,7 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<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")} />
|
<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>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
@ -282,7 +282,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
|
@ -160,7 +160,7 @@ const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: stri
|
|||||||
{...formProps}
|
{...formProps}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
title={t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title")}
|
title={t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title")}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
validateTrigger="onSubmit"
|
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")} />
|
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<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>
|
<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>
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
<div className="max-w-full grow truncate">
|
<div className="max-w-full grow truncate">
|
||||||
|
@ -285,7 +285,7 @@ const SharedNodeConfigDrawer = ({
|
|||||||
<Drawer
|
<Drawer
|
||||||
afterOpenChange={setOpen}
|
afterOpenChange={setOpen}
|
||||||
closable={!pending}
|
closable={!pending}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
extra={
|
extra={
|
||||||
<SharedNodeMenu
|
<SharedNodeMenu
|
||||||
node={node}
|
node={node}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"access.form.provider.label": "Provider",
|
"access.form.provider.label": "Provider",
|
||||||
"access.form.provider.placeholder": "Please select a 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.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.label": "Certificate authority",
|
||||||
"access.form.certificate_authority.placeholder": "Please select a certificate authority",
|
"access.form.certificate_authority.placeholder": "Please select a certificate authority",
|
||||||
"access.form.notification_channel.label": "Notification channel",
|
"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.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.label": "Deployment",
|
||||||
"workflow_node.deploy.search.provider.placeholder": "Search deploy target ...",
|
|
||||||
"workflow_node.deploy.form.provider.label": "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.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.label": "Hosting provider authorization",
|
||||||
"workflow_node.deploy.form.provider_access.placeholder": "Please select an authorization of Hosting provider",
|
"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.tooltip": "Used to invoke API during deployment.",
|
||||||
"workflow_node.deploy.form.provider_access.button": "Create",
|
"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.label": "Certificate",
|
||||||
"workflow_node.deploy.form.certificate.placeholder": "Please select 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.",
|
"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.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.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.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.label": "File format",
|
||||||
"workflow_node.deploy.form.local_format.placeholder": "Please select 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)",
|
"workflow_node.deploy.form.local_format.option.pem.label": "PEM (*.pem, *.crt, *.key)",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"access.form.provider.label": "提供商",
|
"access.form.provider.label": "提供商",
|
||||||
"access.form.provider.placeholder": "请选择提供商",
|
"access.form.provider.placeholder": "请选择提供商",
|
||||||
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
||||||
|
"access.form.provider.search.placeholder": "搜索提供商……",
|
||||||
"access.form.certificate_authority.label": "证书颁发机构",
|
"access.form.certificate_authority.label": "证书颁发机构",
|
||||||
"access.form.certificate_authority.placeholder": "请选择证书颁发机构",
|
"access.form.certificate_authority.placeholder": "请选择证书颁发机构",
|
||||||
"access.form.notification_channel.label": "通知渠道",
|
"access.form.notification_channel.label": "通知渠道",
|
||||||
|
@ -97,14 +97,13 @@
|
|||||||
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
|
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
|
||||||
|
|
||||||
"workflow_node.deploy.label": "部署证书",
|
"workflow_node.deploy.label": "部署证书",
|
||||||
"workflow_node.deploy.search.provider.placeholder": "搜索部署目标……",
|
|
||||||
"workflow_node.deploy.form.provider.label": "部署目标",
|
"workflow_node.deploy.form.provider.label": "部署目标",
|
||||||
"workflow_node.deploy.form.provider.placeholder": "请选择部署目标",
|
"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.label": "主机提供商授权",
|
||||||
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
|
"workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权",
|
||||||
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书时调用相关 API,注意与申请阶段所需的 DNS 提供商相区分。",
|
"workflow_node.deploy.form.provider_access.tooltip": "用于部署证书时调用相关 API,注意与申请阶段所需的 DNS 提供商相区分。",
|
||||||
"workflow_node.deploy.form.provider_access.button": "新建",
|
"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.label": "待部署证书",
|
||||||
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
|
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
|
||||||
"workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。",
|
"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.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.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.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.label": "文件格式",
|
||||||
"workflow_node.deploy.form.local_format.placeholder": "请选择文件格式",
|
"workflow_node.deploy.form.local_format.placeholder": "请选择文件格式",
|
||||||
"workflow_node.deploy.form.local_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)",
|
"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
|
<Drawer
|
||||||
closable={false}
|
closable={false}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={siderOpen}
|
open={siderOpen}
|
||||||
placement="left"
|
placement="left"
|
||||||
styles={{
|
styles={{
|
||||||
|
@ -374,7 +374,7 @@ const WorkflowBaseInfoModal = ({ trigger }: { trigger?: React.ReactNode }) => {
|
|||||||
disabled={formPending}
|
disabled={formPending}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
okText={t("common.button.save")}
|
okText={t("common.button.save")}
|
||||||
title={t(`workflow.detail.baseinfo.modal.title`)}
|
title={t(`workflow.detail.baseinfo.modal.title`)}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
|
@ -165,7 +165,7 @@ const WorkflowNew = () => {
|
|||||||
disabled={formPending}
|
disabled={formPending}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
form={formInst}
|
form={formInst}
|
||||||
modalProps={{ destroyOnClose: true }}
|
modalProps={{ destroyOnHidden: true }}
|
||||||
okText={t("common.button.submit")}
|
okText={t("common.button.submit")}
|
||||||
open={formModalOpen}
|
open={formModalOpen}
|
||||||
title={t(`workflow.new.modal.title`)}
|
title={t(`workflow.new.modal.title`)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user