From 949660bc013f793d43a8287e6efac2395d352ed6 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 2 Apr 2025 11:02:09 +0800 Subject: [PATCH] feat(ui): add AccessEditDrawer component --- ui/src/components/access/AccessEditDrawer.tsx | 118 ++++++++++++++++++ ui/src/i18n/locales/en/nls.access.json | 10 +- ui/src/i18n/locales/zh/nls.access.json | 10 +- ui/src/pages/accesses/AccessList.tsx | 10 +- 4 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 ui/src/components/access/AccessEditDrawer.tsx diff --git a/ui/src/components/access/AccessEditDrawer.tsx b/ui/src/components/access/AccessEditDrawer.tsx new file mode 100644 index 00000000..a7d1d47e --- /dev/null +++ b/ui/src/components/access/AccessEditDrawer.tsx @@ -0,0 +1,118 @@ +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useControllableValue } from "ahooks"; +import { Button, Drawer, Space, notification } from "antd"; + +import { type AccessModel } from "@/domain/access"; +import { useTriggerElement, useZustandShallowSelector } from "@/hooks"; +import { useAccessesStore } from "@/stores/access"; +import { getErrMsg } from "@/utils/error"; + +import AccessForm, { type AccessFormInstance, type AccessFormProps } from "./AccessForm"; + +export type AccessEditDrawerProps = { + data?: AccessFormProps["initialValues"]; + loading?: boolean; + open?: boolean; + range?: AccessFormProps["range"]; + scene: AccessFormProps["scene"]; + trigger?: React.ReactNode; + onOpenChange?: (open: boolean) => void; + afterSubmit?: (record: AccessModel) => void; +}; + +const AccessEditDrawer = ({ data, loading, trigger, scene, range, afterSubmit, ...props }: AccessEditDrawerProps) => { + const { t } = useTranslation(); + + const [notificationApi, NotificationContextHolder] = notification.useNotification(); + + const { createAccess, updateAccess } = useAccessesStore(useZustandShallowSelector(["createAccess", "updateAccess"])); + + const [open, setOpen] = useControllableValue(props, { + valuePropName: "open", + defaultValuePropName: "defaultOpen", + trigger: "onOpenChange", + }); + + const triggerEl = useTriggerElement(trigger, { onClick: () => setOpen(true) }); + + const formRef = useRef(null); + const [formPending, setFormPending] = useState(false); + + const handleOkClick = async () => { + setFormPending(true); + try { + await formRef.current!.validateFields(); + } catch (err) { + setFormPending(false); + throw err; + } + + try { + let values: AccessModel = formRef.current!.getFieldsValue(); + + if (scene === "add") { + if (data?.id) { + throw "Invalid props: `data`"; + } + + values = await createAccess(values); + } else if (scene === "edit") { + if (!data?.id) { + throw "Invalid props: `data`"; + } + + values = await updateAccess({ ...data, ...values }); + } else { + throw "Invalid props: `preset`"; + } + + afterSubmit?.(values); + setOpen(false); + } catch (err) { + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + + throw err; + } finally { + setFormPending(false); + } + }; + + const handleCancelClick = () => { + if (formPending) return; + + setOpen(false); + }; + + return ( + <> + {NotificationContextHolder} + + {triggerEl} + + + + + + } + loading={loading} + maskClosable={!formPending} + open={open} + title={t(`access.action.${scene}`)} + width={720} + onClose={() => setOpen(false)} + > + + + + ); +}; + +export default AccessEditDrawer; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 1e7ceaa2..344c2c1a 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -48,10 +48,10 @@ "access.form.acmehttpreq_mode.label": "Mode", "access.form.acmehttpreq_mode.placeholder": "Please select mode", "access.form.acmehttpreq_mode.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", - "access.form.acmehttpreq_username.label": "HTTP Basic Auth username", + "access.form.acmehttpreq_username.label": "HTTP Basic Auth username (Optional)", "access.form.acmehttpreq_username.placeholder": "Please enter HTTP Basic Auth username", "access.form.acmehttpreq_username.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", - "access.form.acmehttpreq_password.label": "HTTP Basic Auth password", + "access.form.acmehttpreq_password.label": "HTTP Basic Auth password (Optional)", "access.form.acmehttpreq_password.placeholder": "Please enter HTTP Basic Auth password", "access.form.acmehttpreq_password.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", "access.form.aliyun_access_key_id.label": "Aliyun AccessKeyId", @@ -251,14 +251,14 @@ "access.form.ssh_port.placeholder": "Please enter server port", "access.form.ssh_username.label": "Username", "access.form.ssh_username.placeholder": "Please enter username", - "access.form.ssh_password.label": "Password", + "access.form.ssh_password.label": "Password (Optional)", "access.form.ssh_password.placeholder": "Please enter password", "access.form.ssh_password.tooltip": "Required when using password to connect to SSH.", - "access.form.ssh_key.label": "SSH key", + "access.form.ssh_key.label": "SSH key (Optional)", "access.form.ssh_key.placeholder": "Please enter SSH key", "access.form.ssh_key.upload": "Choose file ...", "access.form.ssh_key.tooltip": "Required when using key to connect to SSH.", - "access.form.ssh_key_passphrase.label": "SSH key passphrase", + "access.form.ssh_key_passphrase.label": "SSH key passphrase (Optional)", "access.form.ssh_key_passphrase.placeholder": "Please enter SSH key passphrase", "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.", "access.form.sslcom_eab_kid.label": "ACME EAB KID", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 29d1b2f6..d356b94a 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -48,10 +48,10 @@ "access.form.acmehttpreq_mode.label": "模式", "access.form.acmehttpreq_mode.placeholder": "请选择模式", "access.form.acmehttpreq_mode.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", - "access.form.acmehttpreq_username.label": "HTTP 基本认证用户名", + "access.form.acmehttpreq_username.label": "HTTP 基本认证用户名(可选)", "access.form.acmehttpreq_username.placeholder": "请输入 HTTP 基本认证用户名", "access.form.acmehttpreq_username.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", - "access.form.acmehttpreq_password.label": "HTTP 基本认证密码", + "access.form.acmehttpreq_password.label": "HTTP 基本认证密码(可选)", "access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码", "access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", "access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId", @@ -245,14 +245,14 @@ "access.form.ssh_port.placeholder": "请输入服务器端口", "access.form.ssh_username.label": "用户名", "access.form.ssh_username.placeholder": "请输入用户名", - "access.form.ssh_password.label": "密码", + "access.form.ssh_password.label": "密码(可选)", "access.form.ssh_password.placeholder": "请输入密码", "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一,如果同时填写优先使用 SSH 密钥登录。", - "access.form.ssh_key.label": "SSH 密钥", + "access.form.ssh_key.label": "SSH 密钥(可选)", "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件", "access.form.ssh_key.upload": "选择文件", "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一,如果同时填写优先使用 SSH 密钥登录。", - "access.form.ssh_key_passphrase.label": "SSH 密钥口令", + "access.form.ssh_key_passphrase.label": "SSH 密钥口令(可选)", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", "access.form.sslcom_eab_kid.label": "ACME EAB KID", diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index 1ceaa3f6..0dd56aac 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -14,14 +14,14 @@ import { Avatar, Button, Card, Empty, Flex, Input, Modal, Space, Table, type Tab import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; -import AccessEditModal, { type AccessEditModalProps } from "@/components/access/AccessEditModal"; +import AccessEditDrawer, { type AccessEditDrawerProps } from "@/components/access/AccessEditDrawer"; import { type AccessModel } from "@/domain/access"; import { ACCESS_USAGES, accessProvidersMap } from "@/domain/provider"; import { useZustandShallowSelector } from "@/hooks"; import { useAccessesStore } from "@/stores/access"; import { getErrMsg } from "@/utils/error"; -type AccessRanges = AccessEditModalProps["range"]; +type AccessRanges = AccessEditDrawerProps["range"]; const AccessList = () => { const [searchParams] = useSearchParams(); @@ -85,7 +85,7 @@ const AccessList = () => { width: 120, render: (_, record) => ( - { } /> - {