feat(ui): new AccessEditForm using antd

This commit is contained in:
Fu Diwei
2024-12-17 17:11:11 +08:00
parent 047b3bc079
commit c27818b3b0
66 changed files with 2104 additions and 238 deletions

View File

@@ -0,0 +1,193 @@
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import {
ACCESS_PROVIDER_TYPES,
type AccessModel,
type ACMEHttpReqAccessConfig,
type AliyunAccessConfig,
type AWSAccessConfig,
type BaiduCloudAccessConfig,
type BytePlusAccessConfig,
type CloudflareAccessConfig,
type DogeCloudAccessConfig,
type GoDaddyAccessConfig,
type HuaweiCloudAccessConfig,
type KubernetesAccessConfig,
type LocalAccessConfig,
type NameSiloAccessConfig,
type PowerDNSAccessConfig,
type QiniuAccessConfig,
type SSHAccessConfig,
type TencentCloudAccessConfig,
type VolcEngineAccessConfig,
type WebhookAccessConfig,
} from "@/domain/access";
import AccessTypeSelect from "./AccessTypeSelect";
import AccessEditFormACMEHttpReqConfig from "./AccessEditFormACMEHttpReqConfig";
import AccessEditFormAliyunConfig from "./AccessEditFormAliyunConfig";
import AccessEditFormAWSConfig from "./AccessEditFormAWSConfig";
import AccessEditFormBaiduCloudConfig from "./AccessEditFormBaiduCloudConfig";
import AccessEditFormBytePlusConfig from "./AccessEditFormBytePlusConfig";
import AccessEditFormCloudflareConfig from "./AccessEditFormCloudflareConfig";
import AccessEditFormDogeCloudConfig from "./AccessEditFormDogeCloudConfig";
import AccessEditFormGoDaddyConfig from "./AccessEditFormGoDaddyConfig";
import AccessEditFormHuaweiCloudConfig from "./AccessEditFormHuaweiCloudConfig";
import AccessEditFormKubernetesConfig from "./AccessEditFormKubernetesConfig";
import AccessEditFormLocalConfig from "./AccessEditFormLocalConfig";
import AccessEditFormNameSiloConfig from "./AccessEditFormNameSiloConfig";
import AccessEditFormPowerDNSConfig from "./AccessEditFormPowerDNSConfig";
import AccessEditFormQiniuConfig from "./AccessEditFormQiniuConfig";
import AccessEditFormSSHConfig from "./AccessEditFormSSHConfig";
import AccessEditFormTencentCloudConfig from "./AccessEditFormTencentCloudConfig";
import AccessEditFormVolcEngineConfig from "./AccessEditFormVolcEngineConfig";
import AccessEditFormWebhookConfig from "./AccessEditFormWebhookConfig";
type AccessEditFormModelType = Partial<Omit<AccessModel, "id" | "created" | "updated" | "deleted">>;
export type AccessEditFormProps = {
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
loading?: boolean;
mode: "add" | "edit";
model?: AccessEditFormModelType;
onModelChange?: (model: AccessEditFormModelType) => void;
};
export type AccessEditFormInstance = {
getFieldsValue: () => AccessEditFormModelType;
resetFields: () => void;
validateFields: () => Promise<AccessEditFormModelType>;
};
const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>(({ className, style, disabled, loading, mode, model, onModelChange }, ref) => {
const { t } = useTranslation();
const formSchema = z.object({
name: z
.string()
.trim()
.min(1, t("access.form.name.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: z.nativeEnum(ACCESS_PROVIDER_TYPES, { message: t("access.form.type.placeholder") }),
config: z.any(),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? {});
useEffect(() => {
setInitialValues(model ?? {});
}, [model]);
const [configType, setConfigType] = useState(model?.configType);
useEffect(() => {
setConfigType(model?.configType);
}, [model?.configType]);
const [configFormInst] = Form.useForm();
const configForm = useMemo(() => {
/*
注意:如果追加新的子组件,请保持以 ASCII 排序。
NOTICE: If you add new child component, please keep ASCII order.
*/
switch (configType) {
case ACCESS_PROVIDER_TYPES.ACMEHTTPREQ:
return <AccessEditFormACMEHttpReqConfig form={configFormInst} model={model?.config as ACMEHttpReqAccessConfig} />;
case ACCESS_PROVIDER_TYPES.ALIYUN:
return <AccessEditFormAliyunConfig form={configFormInst} model={model?.config as AliyunAccessConfig} />;
case ACCESS_PROVIDER_TYPES.AWS:
return <AccessEditFormAWSConfig form={configFormInst} model={model?.config as AWSAccessConfig} />;
case ACCESS_PROVIDER_TYPES.BAIDUCLOUD:
return <AccessEditFormBaiduCloudConfig form={configFormInst} model={model?.config as BaiduCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.BYTEPLUS:
return <AccessEditFormBytePlusConfig form={configFormInst} model={model?.config as BytePlusAccessConfig} />;
case ACCESS_PROVIDER_TYPES.CLOUDFLARE:
return <AccessEditFormCloudflareConfig form={configFormInst} model={model?.config as CloudflareAccessConfig} />;
case ACCESS_PROVIDER_TYPES.DOGECLOUD:
return <AccessEditFormDogeCloudConfig form={configFormInst} model={model?.config as DogeCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.GODADDY:
return <AccessEditFormGoDaddyConfig form={configFormInst} model={model?.config as GoDaddyAccessConfig} />;
case ACCESS_PROVIDER_TYPES.HUAWEICLOUD:
return <AccessEditFormHuaweiCloudConfig form={configFormInst} model={model?.config as HuaweiCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.KUBERNETES:
return <AccessEditFormKubernetesConfig form={configFormInst} model={model?.config as KubernetesAccessConfig} />;
case ACCESS_PROVIDER_TYPES.LOCAL:
return <AccessEditFormLocalConfig form={configFormInst} model={model?.config as LocalAccessConfig} />;
case ACCESS_PROVIDER_TYPES.NAMESILO:
return <AccessEditFormNameSiloConfig form={configFormInst} model={model?.config as NameSiloAccessConfig} />;
case ACCESS_PROVIDER_TYPES.POWERDNS:
return <AccessEditFormPowerDNSConfig form={configFormInst} model={model?.config as PowerDNSAccessConfig} />;
case ACCESS_PROVIDER_TYPES.QINIU:
return <AccessEditFormQiniuConfig form={configFormInst} model={model?.config as QiniuAccessConfig} />;
case ACCESS_PROVIDER_TYPES.SSH:
return <AccessEditFormSSHConfig form={configFormInst} model={model?.config as SSHAccessConfig} />;
case ACCESS_PROVIDER_TYPES.TENCENTCLOUD:
return <AccessEditFormTencentCloudConfig form={configFormInst} model={model?.config as TencentCloudAccessConfig} />;
case ACCESS_PROVIDER_TYPES.VOLCENGINE:
return <AccessEditFormVolcEngineConfig form={configFormInst} model={model?.config as VolcEngineAccessConfig} />;
case ACCESS_PROVIDER_TYPES.WEBHOOK:
return <AccessEditFormWebhookConfig form={configFormInst} model={model?.config as WebhookAccessConfig} />;
}
}, [model, configType, configFormInst]);
const handleFormProviderChange = (name: string) => {
if (name === "configForm") {
form.setFieldValue("config", configFormInst.getFieldsValue());
onModelChange?.(form.getFieldsValue());
}
};
const handleFormChange = (_: unknown, fields: AccessEditFormModelType) => {
if (fields.configType !== configType) {
setConfigType(fields.configType);
}
onModelChange?.(fields);
};
useImperativeHandle(ref, () => ({
getFieldsValue: () => {
return form.getFieldsValue();
},
resetFields: () => {
return form.resetFields();
},
validateFields: () => {
const t1 = form.validateFields();
const t2 = configFormInst.validateFields();
return Promise.all([t1, t2]).then(() => t1);
},
}));
return (
<Form.Provider onFormChange={handleFormProviderChange}>
<div className={className} style={style}>
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item name="name" label={t("access.form.name.label")} rules={[formRule]}>
<Input placeholder={t("access.form.name.placeholder")} />
</Form.Item>
<Form.Item
name="configType"
label={t("access.form.type.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.type.tooltip") }}></span>}
>
<AccessTypeSelect disabled={mode === "edit"} placeholder={t("access.form.type.placeholder")} showSearch={!disabled} />
</Form.Item>
<Form.Item name="config" hidden />
</Form>
{configForm}
</div>
</Form.Provider>
);
});
export default AccessEditForm;

View File

@@ -0,0 +1,103 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, Select, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type ACMEHttpReqAccessConfig } from "@/domain/access";
type AccessEditFormACMEHttpReqConfigModelType = Partial<ACMEHttpReqAccessConfig>;
export type AccessEditFormACMEHttpReqConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormACMEHttpReqConfigModelType;
onModelChange?: (model: AccessEditFormACMEHttpReqConfigModelType) => void;
};
const initModel = () => {
return {
endpoint: "https://example.com/api/",
mode: "",
} as AccessEditFormACMEHttpReqConfigModelType;
};
const AccessEditFormACMEHttpReqConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormACMEHttpReqConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
endpoint: z.string().url(t("common.errmsg.url_invalid")),
mode: z.string().min(0, t("access.form.acmehttpreq_mode.placeholder")).nullish(),
username: z
.string()
.trim()
.min(0, t("access.form.acmehttpreq_username.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.nullish(),
password: z
.string()
.trim()
.min(0, t("access.form.acmehttpreq_password.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormACMEHttpReqConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="endpoint"
label={t("access.form.acmehttpreq_endpoint.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_endpoint.tooltip") }}></span>}
>
<Input placeholder={t("access.form.acmehttpreq_endpoint.placeholder")} />
</Form.Item>
<Form.Item
name="mode"
label={t("access.form.acmehttpreq_mode.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_mode.tooltip") }}></span>}
>
<Select
options={[
{ value: "", label: "(default)" },
{ value: "RAW", label: "RAW" },
]}
placeholder={t("access.form.acmehttpreq_mode.placeholder")}
/>
</Form.Item>
<Form.Item
name="username"
label={t("access.form.acmehttpreq_username.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_username.tooltip") }}></span>}
>
<Input placeholder={t("access.form.acmehttpreq_username.placeholder")} />
</Form.Item>
<Form.Item
name="password"
label={t("access.form.acmehttpreq_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.acmehttpreq_password.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.acmehttpreq_password.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormACMEHttpReqConfig;

View File

@@ -0,0 +1,106 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AWSAccessConfig } from "@/domain/access";
type AccessEditFormAWSConfigModelType = Partial<AWSAccessConfig>;
export type AccessEditFormAWSConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormAWSConfigModelType;
onModelChange?: (model: AccessEditFormAWSConfigModelType) => void;
};
const initModel = () => {
return {
region: "us-east-1",
} as AccessEditFormAWSConfigModelType;
};
const AccessEditFormAWSConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormAWSConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.aws_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.aws_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
region: z
.string()
.trim()
.min(0, t("access.form.aws_region.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
hostedZoneId: z
.string()
.trim()
.min(0, t("access.form.aws_hosted_zone_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormAWSConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.aws_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.aws_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.aws_secret_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="region"
label={t("access.form.aws_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_region.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_region.placeholder")} />
</Form.Item>
<Form.Item
name="hostedZoneId"
label={t("access.form.aws_hosted_zone_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aws_hosted_zone_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aws_hosted_zone_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormAWSConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AliyunAccessConfig } from "@/domain/access";
type AccessEditFormAliyunConfigModelType = Partial<AliyunAccessConfig>;
export type AccessEditFormAliyunConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormAliyunConfigModelType;
onModelChange?: (model: AccessEditFormAliyunConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormAliyunConfigModelType;
};
const AccessEditFormAliyunConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormAliyunConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.aliyun_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessKeySecret: z
.string()
.trim()
.min(1, t("access.form.aliyun_access_key_secret.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormAliyunConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.aliyun_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aliyun_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.aliyun_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="accessKeySecret"
label={t("access.form.aliyun_access_key_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.aliyun_access_key_secret.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.aliyun_access_key_secret.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormAliyunConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type BaiduCloudAccessConfig } from "@/domain/access";
type AccessEditFormBaiduCloudConfigModelType = Partial<BaiduCloudAccessConfig>;
export type AccessEditFormBaiduCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormBaiduCloudConfigModelType;
onModelChange?: (model: AccessEditFormBaiduCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormBaiduCloudConfigModelType;
};
const AccessEditFormBaiduCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormBaiduCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.baiducloud_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.baiducloud_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormBaiduCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.baiducloud_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.baiducloud_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.baiducloud_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.baiducloud_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.baiducloud_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.baiducloud_secret_access_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormBaiduCloudConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type BytePlusAccessConfig } from "@/domain/access";
type AccessEditFormBytePlusConfigModelType = Partial<BytePlusAccessConfig>;
export type AccessEditFormBytePlusConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormBytePlusConfigModelType;
onModelChange?: (model: AccessEditFormBytePlusConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormBytePlusConfigModelType;
};
const AccessEditFormBytePlusConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormBytePlusConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.byteplus_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.byteplus_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormBytePlusConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.byteplus_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.byteplus_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.byteplus_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.byteplus_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.byteplus_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.byteplus_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormBytePlusConfig;

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type CloudflareAccessConfig } from "@/domain/access";
type AccessEditFormCloudflareConfigModelType = Partial<CloudflareAccessConfig>;
export type AccessEditFormCloudflareConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormCloudflareConfigModelType;
onModelChange?: (model: AccessEditFormCloudflareConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormCloudflareConfigModelType;
};
const AccessEditFormCloudflareConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormCloudflareConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
dnsApiToken: z
.string()
.trim()
.min(1, t("access.form.cloudflare_dns_api_token.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormCloudflareConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="dnsApiToken"
label={t("access.form.cloudflare_dns_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.cloudflare_dns_api_token.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.cloudflare_dns_api_token.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormCloudflareConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type DogeCloudAccessConfig } from "@/domain/access";
type AccessEditFormDogeCloudConfigModelType = Partial<DogeCloudAccessConfig>;
export type AccessEditFormDogeCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormDogeCloudConfigModelType;
onModelChange?: (model: AccessEditFormDogeCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormDogeCloudConfigModelType;
};
const AccessEditFormDogeCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormDogeCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.dogecloud_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.dogecloud_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormDogeCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.dogecloud_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dogecloud_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.dogecloud_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.dogecloud_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dogecloud_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.dogecloud_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormDogeCloudConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type GoDaddyAccessConfig } from "@/domain/access";
type AccessEditFormGoDaddyConfigModelType = Partial<GoDaddyAccessConfig>;
export type AccessEditFormGoDaddyConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormGoDaddyConfigModelType;
onModelChange?: (model: AccessEditFormGoDaddyConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormGoDaddyConfigModelType;
};
const AccessEditFormGoDaddyConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormGoDaddyConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z
.string()
.trim()
.min(1, t("access.form.godaddy_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
apiSecret: z
.string()
.trim()
.min(1, t("access.form.godaddy_api_secret.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormGoDaddyConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiKey"
label={t("access.form.godaddy_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.godaddy_api_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.godaddy_api_key.placeholder")} />
</Form.Item>
<Form.Item
name="apiSecret"
label={t("access.form.godaddy_api_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.godaddy_api_secret.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.godaddy_api_secret.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormGoDaddyConfig;

View File

@@ -0,0 +1,90 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type HuaweiCloudAccessConfig } from "@/domain/access";
type AccessEditFormHuaweiCloudConfigModelType = Partial<HuaweiCloudAccessConfig>;
export type AccessEditFormHuaweiCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormHuaweiCloudConfigModelType;
onModelChange?: (model: AccessEditFormHuaweiCloudConfigModelType) => void;
};
const initModel = () => {
return {
region: "cn-north-1",
} as AccessEditFormHuaweiCloudConfigModelType;
};
const AccessEditFormHuaweiCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormHuaweiCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.huaweicloud_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.huaweicloud_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
// TODO: 该字段仅用于申请证书,后续迁移到工作流表单中
region: z
.string()
.trim()
.min(0, t("access.form.huaweicloud_region.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormHuaweiCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.huaweicloud_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.huaweicloud_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.huaweicloud_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.huaweicloud_secret_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="region"
label={t("access.form.huaweicloud_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.huaweicloud_region.tooltip") }}></span>}
>
<Input placeholder={t("access.form.huaweicloud_region.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormHuaweiCloudConfig;

View File

@@ -0,0 +1,79 @@
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next";
import { Button, Form, Input, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { type KubernetesAccessConfig } from "@/domain/access";
import { readFileContent } from "@/utils/file";
type AccessEditFormKubernetesConfigModelType = Partial<KubernetesAccessConfig>;
export type AccessEditFormKubernetesConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormKubernetesConfigModelType;
onModelChange?: (model: AccessEditFormKubernetesConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormKubernetesConfigModelType;
};
const AccessEditFormKubernetesConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormKubernetesConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
kubeConfig: z
.string()
.trim()
.min(0, t("access.form.k8s_kubeconfig.placeholder"))
.max(20480, t("common.errmsg.string_max", { max: 20480 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
setKubeFileList(model?.kubeConfig?.trim() ? [{ uid: "-1", name: "kubeconfig", status: "done" }] : []);
}, [model]);
const [kubeFileList, setKubeFileList] = useState<UploadFile[]>([]);
const handleFormChange = (_: unknown, fields: AccessEditFormKubernetesConfigModelType) => {
onModelChange?.(fields);
};
const handleUploadChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
form.setFieldValue("kubeConfig", (await readFileContent(file.originFileObj ?? (file as unknown as File))).trim());
setKubeFileList([file]);
} else {
form.setFieldValue("kubeConfig", "");
setKubeFileList([]);
}
flushSync(() => onModelChange?.(form.getFieldsValue()));
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="kubeConfig"
label={t("access.form.k8s_kubeconfig.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
>
<Input.TextArea hidden placeholder={t("access.form.k8s_kubeconfig.placeholder")} value={form.getFieldValue("kubeConfig")} />
<Upload beforeUpload={() => false} fileList={kubeFileList} maxCount={1} onChange={handleUploadChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.k8s_kubeconfig.upload")}</Button>
</Upload>
</Form.Item>
</Form>
);
};
export default AccessEditFormKubernetesConfig;

View File

@@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
import { Form, type FormInstance } from "antd";
import { type LocalAccessConfig } from "@/domain/access";
type AccessEditFormLocalConfigModelType = Partial<LocalAccessConfig>;
export type AccessEditFormLocalConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormLocalConfigModelType;
onModelChange?: (model: AccessEditFormLocalConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormLocalConfigModelType;
};
const AccessEditFormLocalConfig = ({ form, disabled, loading, model }: AccessEditFormLocalConfigProps) => {
const [initialValues, setInitialValues] = useState(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
return <Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm"></Form>;
};
export default AccessEditFormLocalConfig;

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type NameSiloAccessConfig } from "@/domain/access";
type AccessEditFormNameSiloConfigModelType = Partial<NameSiloAccessConfig>;
export type AccessEditFormNameSiloConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormNameSiloConfigModelType;
onModelChange?: (model: AccessEditFormNameSiloConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormNameSiloConfigModelType;
};
const AccessEditFormNameSiloConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormNameSiloConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiKey: z
.string()
.trim()
.min(1, t("access.form.namesilo_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormNameSiloConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiKey"
label={t("access.form.namesilo_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.namesilo_api_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.namesilo_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormNameSiloConfig;

View File

@@ -0,0 +1,68 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type PowerDNSAccessConfig } from "@/domain/access";
type AccessEditFormPowerDNSConfigModelType = Partial<PowerDNSAccessConfig>;
export type AccessEditFormPowerDNSConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormPowerDNSConfigModelType;
onModelChange?: (model: AccessEditFormPowerDNSConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormPowerDNSConfigModelType;
};
const AccessEditFormPowerDNSConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormPowerDNSConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
apiKey: z
.string()
.trim()
.min(1, t("access.form.powerdns_api_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormPowerDNSConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="apiUrl"
label={t("access.form.powerdns_api_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.powerdns_api_url.tooltip") }}></span>}
>
<Input placeholder={t("access.form.powerdns_api_url.placeholder")} />
</Form.Item>
<Form.Item
name="apiKey"
label={t("access.form.powerdns_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.powerdns_api_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.powerdns_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormPowerDNSConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type QiniuAccessConfig } from "@/domain/access";
type AccessEditFormQiniuConfigModelType = Partial<QiniuAccessConfig>;
export type AccessEditFormQiniuConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormQiniuConfigModelType;
onModelChange?: (model: AccessEditFormQiniuConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormQiniuConfigModelType;
};
const AccessEditFormQiniuConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormQiniuConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKey: z
.string()
.trim()
.min(1, t("access.form.qiniu_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.qiniu_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormQiniuConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKey"
label={t("access.form.qiniu_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_access_key.tooltip") }}></span>}
>
<Input placeholder={t("access.form.qiniu_access_key.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.qiniu_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.qiniu_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.qiniu_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormQiniuConfig;

View File

@@ -0,0 +1,162 @@
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next";
import { Button, Form, Input, InputNumber, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { type SSHAccessConfig } from "@/domain/access";
import { readFileContent } from "@/utils/file";
type AccessEditFormSSHConfigModelType = Partial<SSHAccessConfig>;
export type AccessEditFormSSHConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormSSHConfigModelType;
onModelChange?: (model: AccessEditFormSSHConfigModelType) => void;
};
const initModel = () => {
return {
host: "127.0.0.1",
port: 22,
username: "root",
} as AccessEditFormSSHConfigModelType;
};
const AccessEditFormSSHConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormSSHConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
host: z.string().refine(
(str) => {
const reIpv4 =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const reIpv6 =
/^([\da-fA-F]{1,4}:){6}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|::([\dafAF]1,4:)0,4((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|::([\dafAF]1,4:)0,4((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:):([\da-fA-F]{1,4}:){0,3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|([\dafAF]1,4:)2:([\dafAF]1,4:)0,2((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|([\dafAF]1,4:)2:([\dafAF]1,4:)0,2((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:){3}:([\da-fA-F]{1,4}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|([\dafAF]1,4:)4:((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|([\dafAF]1,4:)4:((25[05]|2[04]\d|[01]?\d\d?)\.)3(25[05]|2[04]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|:((:[\dafAF]1,4)1,6|:)|:((:[\dafAF]1,4)1,6|:)|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?|([\dafAF]1,4:)6:|([\dafAF]1,4:)6:/;
const reDomain = /^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
return reIpv4.test(str) || reIpv6.test(str) || reDomain.test(str);
},
{ message: t("common.errmsg.host_invalid") }
),
port: z
.number()
.int()
.gte(1, t("common.errmsg.port_invalid"))
.lte(65535, t("common.errmsg.port_invalid"))
.transform((v) => +v),
username: z
.string()
.min(1, "access.form.ssh_username.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
password: z
.string()
.min(0, "access.form.ssh_password.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 }))
.nullish(),
key: z
.string()
.min(0, "access.form.ssh_key.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish(),
keyPassphrase: z
.string()
.min(0, "access.form.ssh_key_passphrase.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
.nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
setKeyFileList(model?.key?.trim() ? [{ uid: "-1", name: "sshkey", status: "done" }] : []);
}, [model]);
const [keyFileList, setKeyFileList] = useState<UploadFile[]>([]);
const handleFormChange = (_: unknown, fields: AccessEditFormSSHConfigModelType) => {
onModelChange?.(fields);
};
const handleUploadChange: UploadProps["onChange"] = async ({ file }) => {
if (file && file.status !== "removed") {
form.setFieldValue("kubeConfig", (await readFileContent(file.originFileObj ?? (file as unknown as File))).trim());
setKeyFileList([file]);
} else {
form.setFieldValue("kubeConfig", "");
setKeyFileList([]);
}
flushSync(() => onModelChange?.(form.getFieldsValue()));
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<div className="flex space-x-2">
<div className="w-2/3">
<Form.Item name="host" label={t("access.form.ssh_host.label")} rules={[formRule]}>
<Input placeholder={t("access.form.ssh_host.placeholder")} />
</Form.Item>
</div>
<div className="w-1/3">
<Form.Item name="port" label={t("access.form.ssh_port.label")} rules={[formRule]}>
<InputNumber className="w-full" placeholder={t("access.form.ssh_port.placeholder")} min={1} max={65535} />
</Form.Item>
</div>
</div>
<div className="flex space-x-2">
<div className="w-1/2">
<Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}>
<Input placeholder={t("access.form.ssh_username.placeholder")} />
</Form.Item>
</div>
<div className="w-1/2">
<Form.Item
name="password"
label={t("access.form.ssh_password.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.ssh_password.placeholder")} />
</Form.Item>
</div>
</div>
<div className="flex space-x-2">
<div className="w-1/2">
<Form.Item
name="key"
label={t("access.form.ssh_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}
>
<Input.TextArea hidden placeholder={t("access.form.ssh_key.placeholder")} value={form.getFieldValue("key")} />
<Upload beforeUpload={() => false} fileList={keyFileList} maxCount={1} onChange={handleUploadChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.ssh_key.upload")}</Button>
</Upload>
</Form.Item>
</div>
<div className="w-1/2">
<Form.Item
name="keyPassphrase"
label={t("access.form.ssh_key_passphrase.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key_passphrase.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
</Form.Item>
</div>
</div>
</Form>
);
};
export default AccessEditFormSSHConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type TencentCloudAccessConfig } from "@/domain/access";
type AccessEditFormTencentCloudConfigModelType = Partial<TencentCloudAccessConfig>;
export type AccessEditFormTencentCloudConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormTencentCloudConfigModelType;
onModelChange?: (model: AccessEditFormTencentCloudConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormTencentCloudConfigModelType;
};
const AccessEditFormTencentCloudConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormTencentCloudConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
secretId: z
.string()
.trim()
.min(1, t("access.form.tencentcloud_secret_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.trim()
.min(1, t("access.form.tencentcloud_secret_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormTencentCloudConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="secretId"
label={t("access.form.tencentcloud_secret_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.tencentcloud_secret_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.tencentcloud_secret_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretKey"
label={t("access.form.tencentcloud_secret_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.tencentcloud_secret_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.tencentcloud_secret_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormTencentCloudConfig;

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type VolcEngineAccessConfig } from "@/domain/access";
type AccessEditFormVolcEngineConfigModelType = Partial<VolcEngineAccessConfig>;
export type AccessEditFormVolcEngineConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormVolcEngineConfigModelType;
onModelChange?: (model: AccessEditFormVolcEngineConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormVolcEngineConfigModelType;
};
const AccessEditFormVolcEngineConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormVolcEngineConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
accessKeyId: z
.string()
.trim()
.min(1, t("access.form.volcengine_access_key_id.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.trim()
.min(1, t("access.form.volcengine_secret_access_key.placeholder"))
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormVolcEngineConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item
name="accessKeyId"
label={t("access.form.volcengine_access_key_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.volcengine_access_key_id.tooltip") }}></span>}
>
<Input placeholder={t("access.form.volcengine_access_key_id.placeholder")} />
</Form.Item>
<Form.Item
name="secretAccessKey"
label={t("access.form.volcengine_secret_access_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.volcengine_secret_access_key.tooltip") }}></span>}
>
<Input.Password placeholder={t("access.form.volcengine_secret_access_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormVolcEngineConfig;

View File

@@ -0,0 +1,49 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, type FormInstance } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type WebhookAccessConfig } from "@/domain/access";
type AccessEditFormWebhookConfigModelType = Partial<WebhookAccessConfig>;
export type AccessEditFormWebhookConfigProps = {
form: FormInstance;
disabled?: boolean;
loading?: boolean;
model?: AccessEditFormWebhookConfigModelType;
onModelChange?: (model: AccessEditFormWebhookConfigModelType) => void;
};
const initModel = () => {
return {} as AccessEditFormWebhookConfigModelType;
};
const AccessEditFormWebhookConfig = ({ form, disabled, loading, model, onModelChange }: AccessEditFormWebhookConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
url: z.string().url(t("common.errmsg.url_invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormWebhookConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name="configForm" onValuesChange={handleFormChange}>
<Form.Item name="url" label={t("access.form.webhook_url.label")} rules={[formRule]}>
<Input placeholder={t("access.form.webhook_url.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormWebhookConfig;

View File

@@ -0,0 +1,108 @@
import { cloneElement, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useControllableValue } from "ahooks";
import { Modal, notification } from "antd";
import { type AccessModel } from "@/domain/access";
import { useAccessStore } from "@/stores/access";
import AccessEditForm, { type AccessEditFormInstance } from "./AccessEditForm";
export type AccessEditModalProps = {
data?: Partial<AccessModel>;
loading?: boolean;
mode: "add" | "edit" | "copy";
open?: boolean;
trigger?: React.ReactElement;
onOpenChange?: (open: boolean) => void;
};
const AccessEditModal = ({ data, loading, mode, trigger, ...props }: AccessEditModalProps) => {
const { t } = useTranslation();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const { createAccess, updateAccess } = useAccessStore();
const [open, setOpen] = useControllableValue<boolean>(props, {
valuePropName: "open",
defaultValuePropName: "defaultOpen",
trigger: "onOpenChange",
});
const triggerEl = useMemo(() => {
if (!trigger) {
return null;
}
return cloneElement(trigger, {
...trigger.props,
onClick: () => {
setOpen(true);
trigger.props?.onClick?.();
},
});
}, [trigger, setOpen]);
const formRef = useRef<AccessEditFormInstance>(null);
const [formPending, setFormPending] = useState(false);
const handleClickOk = async () => {
setFormPending(true);
try {
await formRef.current!.validateFields();
} catch (err) {
setFormPending(false);
return Promise.reject();
}
try {
if (mode === "add") {
await createAccess(formRef.current!.getFieldsValue() as AccessModel);
} else {
await updateAccess({ ...data, ...formRef.current!.getFieldsValue() } as AccessModel);
}
setOpen(false);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
throw err;
} finally {
setFormPending(false);
}
};
const handleClickCancel = () => {
if (formPending) return Promise.reject();
setOpen(false);
};
return (
<>
{NotificationContextHolder}
{triggerEl}
<Modal
afterClose={() => setOpen(false)}
cancelButtonProps={{ disabled: formPending }}
closable
confirmLoading={formPending}
destroyOnClose
loading={loading}
okText={mode === "edit" ? t("common.button.save") : t("common.button.submit")}
open={open}
title={t(`access.action.${mode}`)}
onOk={handleClickOk}
onCancel={handleClickCancel}
>
<div className="pt-4 pb-2">
<AccessEditForm ref={formRef} mode={mode === "add" ? "add" : "edit"} model={data} />
</div>
</Modal>
</>
);
};
export default AccessEditModal;

View File

@@ -15,17 +15,44 @@ const AccessTypeSelect = memo((props: AccessTypeSelectProps) => {
label: t(item.name),
}));
const renderOption = (key: string) => {
const provider = accessProvidersMap.get(key);
return (
<div className="flex items-center justify-between gap-4 max-w-full overflow-hidden">
<Space className="flex-grow max-w-full truncate" size={4}>
<Avatar src={provider?.icon} size="small" />
<Typography.Text className="leading-loose" ellipsis>
{t(provider?.name ?? "")}
</Typography.Text>
</Space>
<div>
{provider?.usage === "apply" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
</>
)}
{provider?.usage === "deploy" && (
<>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
{provider?.usage === "all" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
</div>
</div>
);
};
return (
<Select
{...props}
labelRender={({ label, value }) => {
if (label) {
return (
<Space className="max-w-full truncate" size={4}>
<Avatar src={accessProvidersMap.get(String(value))?.icon} size="small" />
{label}
</Space>
);
return renderOption(value as string);
}
return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
@@ -33,32 +60,7 @@ const AccessTypeSelect = memo((props: AccessTypeSelectProps) => {
options={options}
optionFilterProp={undefined}
optionLabelProp={undefined}
optionRender={(option) => (
<div className="flex items-center justify-between gap-4 max-w-full overflow-hidden">
<Space className="flex-grow max-w-full truncate" size={4}>
<Avatar src={accessProvidersMap.get(option.data.value)?.icon} size="small" />
<Typography.Text ellipsis>{t(accessProvidersMap.get(option.data.value)?.name ?? "")}</Typography.Text>
</Space>
<div>
{accessProvidersMap.get(option.data.value)?.usage === "apply" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
</>
)}
{accessProvidersMap.get(option.data.value)?.usage === "deploy" && (
<>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
{accessProvidersMap.get(option.data.value)?.usage === "all" && (
<>
<Tag color="orange">{t("access.props.provider.usage.dns")}</Tag>
<Tag color="blue">{t("access.props.provider.usage.host")}</Tag>
</>
)}
</div>
</div>
)}
optionRender={(option) => renderOption(option.data.value)}
/>
);
});