From 220d98a66851b8108b43bce19f86722924f17f24 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 12 Dec 2024 09:48:50 +0800 Subject: [PATCH 01/39] feat(ui): show more details in CertificateDetail --- .../certificate/CertificateDetail.tsx | 5 ++ ui/src/i18n/locales/en/nls.certificate.json | 2 +- ui/src/i18n/locales/zh/nls.certificate.json | 2 +- ui/src/pages/ConsoleLayout.tsx | 77 +++++++------------ ui/src/pages/certificates/CertificateList.tsx | 2 +- 5 files changed, 34 insertions(+), 54 deletions(-) diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index 02b7a91e..05b492d3 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next"; import { Button, Dropdown, Form, Input, message, Space, Tooltip } from "antd"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { ChevronDown as ChevronDownIcon, Clipboard as ClipboardIcon, ThumbsUp as ThumbsUpIcon } from "lucide-react"; +import dayjs from "dayjs"; import { type CertificateModel } from "@/domain/certificate"; import { saveFiles2Zip } from "@/utils/file"; @@ -36,6 +37,10 @@ const CertificateDetail = ({ data }: CertificateDetailProps) => { {MessageContextHolder}
+ {data.san} + + {dayjs(data.expireAt).format("YYYY-MM-DD HH:mm:ss")} +
diff --git a/ui/src/i18n/locales/en/nls.certificate.json b/ui/src/i18n/locales/en/nls.certificate.json index 88ebed31..d1146930 100644 --- a/ui/src/i18n/locales/en/nls.certificate.json +++ b/ui/src/i18n/locales/en/nls.certificate.json @@ -6,7 +6,7 @@ "certificate.action.view": "View Certificate", "certificate.action.download": "Download Certificate", - "certificate.props.domain": "Name", + "certificate.props.san": "Name", "certificate.props.expiry": "Expiry", "certificate.props.expiry.left_days": "{{left}} / {{total}} days left", "certificate.props.expiry.expired": "Expired", diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json index 538e24bf..1c4d3d24 100644 --- a/ui/src/i18n/locales/zh/nls.certificate.json +++ b/ui/src/i18n/locales/zh/nls.certificate.json @@ -6,7 +6,7 @@ "certificate.action.view": "查看证书", "certificate.action.download": "下载证书", - "certificate.props.domain": "名称", + "certificate.props.san": "名称", "certificate.props.expiry": "有效期限", "certificate.props.expiry.left_days": "{{left}} / {{total}} 天", "certificate.props.expiry.expired": "已到期", diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx index de053fa8..c21ab1bf 100644 --- a/ui/src/pages/ConsoleLayout.tsx +++ b/ui/src/pages/ConsoleLayout.tsx @@ -120,43 +120,21 @@ const SiderMenu = React.memo(({ onSelect }: { onSelect?: (key: string) => void } const MENU_KEY_CERTIFICATES = "/certificates"; const MENU_KEY_ACCESSES = "/accesses"; const menuItems: Required["items"] = [ - { - key: MENU_KEY_HOME, - icon: , - label: t("dashboard.page.title"), + [MENU_KEY_HOME, , t("dashboard.page.title")], + [MENU_KEY_WORKFLOWS, , t("workflow.page.title")], + [MENU_KEY_CERTIFICATES, , t("certificate.page.title")], + [MENU_KEY_ACCESSES, , t("access.page.title")], + ].map(([key, icon, label]) => { + return { + key: key as string, + icon: icon, + label: label, onClick: () => { - navigate(MENU_KEY_HOME); - onSelect?.(MENU_KEY_HOME); + navigate(key as string); + onSelect?.(key as string); }, - }, - { - key: MENU_KEY_WORKFLOWS, - icon: , - label: t("workflow.page.title"), - onClick: () => { - navigate(MENU_KEY_WORKFLOWS); - onSelect?.(MENU_KEY_WORKFLOWS); - }, - }, - { - key: MENU_KEY_CERTIFICATES, - icon: , - label: t("certificate.page.title"), - onClick: () => { - navigate(MENU_KEY_CERTIFICATES); - onSelect?.(MENU_KEY_CERTIFICATES); - }, - }, - { - key: MENU_KEY_ACCESSES, - icon: , - label: t("access.page.title"), - onClick: () => { - navigate(MENU_KEY_ACCESSES); - onSelect?.(MENU_KEY_ACCESSES); - }, - }, - ]; + }; + }); const [menuSelectedKey, setMenuSelectedKey] = useState(); const getActiveMenuItem = () => { @@ -207,22 +185,19 @@ const ThemeToggleButton = React.memo(({ size }: { size?: ButtonProps["size"] }) const { theme, setThemeMode } = useTheme(); const items: Required["items"] = [ - { - key: "light", - label: <>{t("common.theme.light")}, - onClick: () => setThemeMode("light"), - }, - { - key: "dark", - label: <>{t("common.theme.dark")}, - onClick: () => setThemeMode("dark"), - }, - { - key: "system", - label: <>{t("common.theme.system")}, - onClick: () => setThemeMode("system"), - }, - ]; + ["light", t("common.theme.light")], + ["dark", t("common.theme.dark")], + ["system", t("common.theme.system")], + ].map(([key, label]) => { + return { + key: key as string, + label: label, + onClick: () => { + setThemeMode(key as Parameters[0]); + window.location.reload(); + }, + }; + }); return ( diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index ae7525d5..aa744fff 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -33,7 +33,7 @@ const CertificateList = () => { }, { key: "name", - title: t("certificate.props.domain"), + title: t("certificate.props.san"), render: (_, record) => {record.san}, }, { From b5739c663d704a2918136a1d3e5a1b937d0fb288 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 12 Dec 2024 16:49:12 +0800 Subject: [PATCH 02/39] feat(ui): new AccessProviderSelect component using antd --- ui/package-lock.json | 361 ------------------ ui/package.json | 1 - .../access/AccessProviderSelect.tsx | 48 +++ .../components/certimate/AccessEditDialog.tsx | 10 +- .../components/certimate/AccessTypeSelect.tsx | 80 ---- ui/src/components/ui/command.tsx | 105 ----- ui/src/domain/access.ts | 43 +-- ui/src/i18n/locales/en/nls.access.json | 1 - ui/src/i18n/locales/zh/nls.access.json | 1 - ui/src/pages/accesses/AccessList.tsx | 2 +- 10 files changed, 74 insertions(+), 578 deletions(-) create mode 100644 ui/src/components/access/AccessProviderSelect.tsx delete mode 100644 ui/src/components/certimate/AccessTypeSelect.tsx delete mode 100644 ui/src/components/ui/command.tsx diff --git a/ui/package-lock.json b/ui/package-lock.json index 2ba9c9c4..f4fd3895 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -31,7 +31,6 @@ "antd-zod": "^6.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "cmdk": "^1.0.0", "cron-parser": "^4.9.0", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", @@ -4015,366 +4014,6 @@ "node": ">=6" } }, - "node_modules/cmdk": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.0.0.tgz", - "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", - "dependencies": { - "@radix-ui/react-dialog": "1.0.5", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/cmdk/node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", - "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", - "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", - "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/cmdk/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", diff --git a/ui/package.json b/ui/package.json index 986fdd6a..a08f888e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -33,7 +33,6 @@ "antd-zod": "^6.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "cmdk": "^1.0.0", "cron-parser": "^4.9.0", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", diff --git a/ui/src/components/access/AccessProviderSelect.tsx b/ui/src/components/access/AccessProviderSelect.tsx new file mode 100644 index 00000000..a7186ea9 --- /dev/null +++ b/ui/src/components/access/AccessProviderSelect.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Avatar, Select, Space, Typography, type SelectProps } from "antd"; + +import { accessProvidersMap } from "@/domain/access"; + +export type AccessProviderSelectProps = Omit & { + className?: string; +}; + +const AccessProviderSelect = React.memo((props: AccessProviderSelectProps) => { + const { t } = useTranslation(); + + const options = Array.from(accessProvidersMap.values()).map((item) => ({ + key: item.type, + value: item.type, + label: t(item.name), + })); + + return ( + { - if (label) { - return ( - - - {label} - - ); - } - - return {props.placeholder}; - }} - options={options} - optionFilterProp={undefined} - optionLabelProp={undefined} - optionRender={(option) => ( - - - {t(accessProvidersMap.get(option.data.value)?.name ?? "")} - - )} - /> - ); -}); - -export default AccessProviderSelect; diff --git a/ui/src/components/access/AccessTypeSelect.tsx b/ui/src/components/access/AccessTypeSelect.tsx new file mode 100644 index 00000000..711d1b22 --- /dev/null +++ b/ui/src/components/access/AccessTypeSelect.tsx @@ -0,0 +1,66 @@ +import { memo } from "react"; +import { useTranslation } from "react-i18next"; +import { Avatar, Select, Space, Tag, Typography, type SelectProps } from "antd"; + +import { accessProvidersMap } from "@/domain/access"; + +export type AccessTypeSelectProps = Omit; + +const AccessTypeSelect = memo((props: AccessTypeSelectProps) => { + const { t } = useTranslation(); + + const options = Array.from(accessProvidersMap.values()).map((item) => ({ + key: item.type, + value: item.type, + label: t(item.name), + })); + + return ( + + @@ -128,7 +128,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => { name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -143,7 +143,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => { name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -158,9 +158,9 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => { name="accessKeyId" render={({ field }) => ( - {t("access.authorization.form.access_key_id.label")} + {t("access.form.access_key_id.label")} - + @@ -173,9 +173,9 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => { name="accessSecretId" render={({ field }) => ( - {t("access.authorization.form.access_key_secret.label")} + {t("access.form.access_key_secret.label")} - + diff --git a/ui/src/components/certimate/AccessAwsForm.tsx b/ui/src/components/certimate/AccessAwsForm.tsx index 28f3c69d..4669bb12 100644 --- a/ui/src/components/certimate/AccessAwsForm.tsx +++ b/ui/src/components/certimate/AccessAwsForm.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { PbErrorData } from "@/domain/base"; -import { AccessModel, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access"; +import { AccessModel, accessProvidersMap, accessTypeFormSchema, type AWSAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -27,34 +27,34 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, region: z .string() - .min(1, "access.authorization.form.region.placeholder") + .min(1, "access.form.region.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), accessKeyId: z .string() - .min(1, "access.authorization.form.access_key_id.placeholder") + .min(1, "access.form.access_key_id.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), secretAccessKey: z .string() - .min(1, "access.authorization.form.secret_access_key.placeholder") + .min(1, "access.form.secret_access_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), hostedZoneId: z .string() - .min(0, "access.authorization.form.aws_hosted_zone_id.placeholder") + .min(0, "access.form.aws_hosted_zone_id.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: AwsConfig = { + let config: AWSAccessConfig = { region: "cn-north-1", accessKeyId: "", secretAccessKey: "", hostedZoneId: "", }; - if (data) config = data.config as AwsConfig; + if (data) config = data.config as AWSAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -127,9 +127,9 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -142,7 +142,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -157,7 +157,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -172,9 +172,9 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="region" render={({ field }) => ( - {t("access.authorization.form.region.label")} + {t("access.form.region.label")} - + @@ -187,9 +187,9 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="accessKeyId" render={({ field }) => ( - {t("access.authorization.form.access_key_id.label")} + {t("access.form.access_key_id.label")} - + @@ -202,9 +202,9 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="secretAccessKey" render={({ field }) => ( - {t("access.authorization.form.secret_access_key.label")} + {t("access.form.secret_access_key.label")} - + @@ -217,9 +217,9 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { name="hostedZoneId" render={({ field }) => ( - {t("access.authorization.form.aws_hosted_zone_id.label")} + {t("access.form.aws_hosted_zone_id.label")} - + diff --git a/ui/src/components/certimate/AccessBaiduCloudForm.tsx b/ui/src/components/certimate/AccessBaiduCloudForm.tsx index 033d34e4..2bd1bb59 100644 --- a/ui/src/components/certimate/AccessBaiduCloudForm.tsx +++ b/ui/src/components/certimate/AccessBaiduCloudForm.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BaiduCloudConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BaiduCloudAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -27,24 +27,24 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, accessKeyId: z .string() - .min(1, "access.authorization.form.access_key_id.placeholder") + .min(1, "access.form.access_key_id.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), secretAccessKey: z .string() - .min(1, "access.authorization.form.secret_access_key.placeholder") + .min(1, "access.form.secret_access_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: BaiduCloudConfig = { + let config: BaiduCloudAccessConfig = { accessKeyId: "", secretAccessKey: "", }; - if (data) config = data.config as BaiduCloudConfig; + if (data) config = data.config as BaiduCloudAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -113,9 +113,9 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -128,7 +128,7 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -143,7 +143,7 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -158,9 +158,9 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp name="accessKeyId" render={({ field }) => ( - {t("access.authorization.form.access_key_id.label")} + {t("access.form.access_key_id.label")} - + @@ -173,9 +173,9 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp name="secretAccessKey" render={({ field }) => ( - {t("access.authorization.form.secret_access_key.label")} + {t("access.form.secret_access_key.label")} - + diff --git a/ui/src/components/certimate/AccessByteplusForm.tsx b/ui/src/components/certimate/AccessByteplusForm.tsx index f7fc213b..d069383b 100644 --- a/ui/src/components/certimate/AccessByteplusForm.tsx +++ b/ui/src/components/certimate/AccessByteplusForm.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type ByteplusConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BytePlusAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,24 +25,24 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, accessKey: z .string() - .min(1, "access.authorization.form.access_key.placeholder") + .min(1, "access.form.access_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), secretKey: z .string() - .min(1, "access.authorization.form.secret_key.placeholder") + .min(1, "access.form.secret_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: ByteplusConfig = { + let config: BytePlusAccessConfig = { accessKey: "", secretKey: "", }; - if (data) config = data.config as ByteplusConfig; + if (data) config = data.config as BytePlusAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -110,9 +110,9 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -125,7 +125,7 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -140,7 +140,7 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -155,9 +155,9 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = name="accessKey" render={({ field }) => ( - {t("access.authorization.form.access_key.label")} + {t("access.form.access_key.label")} - + @@ -170,9 +170,9 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) = name="secretKey" render={({ field }) => ( - {t("access.authorization.form.secret_key.label")} + {t("access.form.secret_key.label")} - + diff --git a/ui/src/components/certimate/AccessCloudflareForm.tsx b/ui/src/components/certimate/AccessCloudflareForm.tsx index 0a347223..874ca81d 100644 --- a/ui/src/components/certimate/AccessCloudflareForm.tsx +++ b/ui/src/components/certimate/AccessCloudflareForm.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type CloudflareConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type CloudflareAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,19 +25,19 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, dnsApiToken: z .string() - .min(1, "access.authorization.form.cloud_dns_api_token.placeholder") + .min(1, "access.form.cloud_dns_api_token.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: CloudflareConfig = { + let config: CloudflareAccessConfig = { dnsApiToken: "", }; - if (data) config = data.config as CloudflareConfig; + if (data) config = data.config as CloudflareAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -101,9 +101,9 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -116,7 +116,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -131,7 +131,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -146,9 +146,9 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp name="dnsApiToken" render={({ field }) => ( - {t("access.authorization.form.cloud_dns_api_token.label")} + {t("access.form.cloud_dns_api_token.label")} - + diff --git a/ui/src/components/certimate/AccessDogeCloudForm.tsx b/ui/src/components/certimate/AccessDogeCloudForm.tsx index 13283ee1..aa963503 100644 --- a/ui/src/components/certimate/AccessDogeCloudForm.tsx +++ b/ui/src/components/certimate/AccessDogeCloudForm.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type DogeCloudConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type DogeCloudAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,18 +25,18 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, - accessKey: z.string().min(1, "access.authorization.form.access_key.placeholder").max(64), - secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64), + accessKey: z.string().min(1, "access.form.access_key.placeholder").max(64), + secretKey: z.string().min(1, "access.form.secret_key.placeholder").max(64), }); - let config: DogeCloudConfig = { + let config: DogeCloudAccessConfig = { accessKey: "", secretKey: "", }; - if (data) config = data.config as DogeCloudConfig; + if (data) config = data.config as DogeCloudAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -104,9 +104,9 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -119,7 +119,7 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -134,7 +134,7 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -149,9 +149,9 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) name="accessKey" render={({ field }) => ( - {t("access.authorization.form.access_key.label")} + {t("access.form.access_key.label")} - + @@ -164,9 +164,9 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) name="secretKey" render={({ field }) => ( - {t("access.authorization.form.secret_key.label")} + {t("access.form.secret_key.label")} - + diff --git a/ui/src/components/certimate/AccessEditDialog.tsx b/ui/src/components/certimate/AccessEditDialog.tsx index 36de6fce..d777dbcb 100644 --- a/ui/src/components/certimate/AccessEditDialog.tsx +++ b/ui/src/components/certimate/AccessEditDialog.tsx @@ -5,7 +5,6 @@ import { cn } from "@/components/ui/utils"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; -import AccessEditForm from "@/components/access/AccessEditForm"; import AccessTypeSelect from "@/components/access/AccessTypeSelect"; import AccessAliyunForm from "./AccessAliyunForm"; import AccessTencentForm from "./AccessTencentForm"; @@ -61,7 +60,7 @@ const AccessEditDialog = ({ trigger, op, data, className, outConfigType }: Acces /> ); break; - case "tencent": + case "tencentcloud": childComponent = ( ); break; - case "pdns": + case "powerdns": childComponent = ( ); break; - case "httpreq": + case "acmehttpreq": childComponent = ( -
- + { diff --git a/ui/src/components/certimate/AccessGodaddyForm.tsx b/ui/src/components/certimate/AccessGodaddyForm.tsx index 1c8f40a5..a56c98b8 100644 --- a/ui/src/components/certimate/AccessGodaddyForm.tsx +++ b/ui/src/components/certimate/AccessGodaddyForm.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type GodaddyConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type GoDaddyAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,24 +25,24 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, apiKey: z .string() - .min(1, "access.authorization.form.godaddy_api_key.placeholder") + .min(1, "access.form.godaddy_api_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), apiSecret: z .string() - .min(1, "access.authorization.form.godaddy_api_secret.placeholder") + .min(1, "access.form.godaddy_api_secret.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: GodaddyConfig = { + let config: GoDaddyAccessConfig = { apiKey: "", apiSecret: "", }; - if (data) config = data.config as GodaddyConfig; + if (data) config = data.config as GoDaddyAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -108,9 +108,9 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -123,7 +123,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -138,7 +138,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -153,9 +153,9 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => name="apiKey" render={({ field }) => ( - {t("access.authorization.form.godaddy_api_key.label")} + {t("access.form.godaddy_api_key.label")} - + @@ -168,9 +168,9 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => name="apiSecret" render={({ field }) => ( - {t("access.authorization.form.godaddy_api_secret.label")} + {t("access.form.godaddy_api_secret.label")} - + diff --git a/ui/src/components/certimate/AccessHttpreqForm.tsx b/ui/src/components/certimate/AccessHttpreqForm.tsx index 38abf92f..ac8c7fac 100644 --- a/ui/src/components/certimate/AccessHttpreqForm.tsx +++ b/ui/src/components/certimate/AccessHttpreqForm.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HttpreqConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type ACMEHttpReqAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,35 +25,35 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, endpoint: z.string().url("common.errmsg.url_invalid"), mode: z.enum(["RAW", ""]), username: z .string() - .min(1, "access.authorization.form.access_key_secret.placeholder") + .min(1, "access.form.access_key_secret.placeholder") .max(128, t("common.errmsg.string_max", { max: 128 })), password: z .string() - .min(1, "access.authorization.form.access_key_secret.placeholder") + .min(1, "access.form.access_key_secret.placeholder") .max(128, t("common.errmsg.string_max", { max: 128 })), }); - let config: HttpreqConfig = { + let config: ACMEHttpReqAccessConfig = { endpoint: "", mode: "", username: "", password: "", }; - if (data) config = data.config as HttpreqConfig; + if (data) config = data.config as ACMEHttpReqAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { id: data?.id, name: data?.name || "", - configType: "httpreq", + configType: "acmehttpreq", endpoint: config.endpoint, mode: config.mode === "RAW" ? "RAW" : "", username: config.username, @@ -119,9 +119,9 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -134,7 +134,7 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -149,7 +149,7 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -164,9 +164,9 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="endpoint" render={({ field }) => ( - {t("access.authorization.form.httpreq_endpoint.label")} + {t("access.form.httpreq_endpoint.label")} - + @@ -179,9 +179,9 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="mode" render={({ field }) => ( - {t("access.authorization.form.httpreq_mode.label")} + {t("access.form.httpreq_mode.label")} - + @@ -194,9 +194,9 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="username" render={({ field }) => ( - {t("access.authorization.form.username.label")} + {t("access.form.username.label")} - + @@ -209,9 +209,9 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => name="password" render={({ field }) => ( - {t("access.authorization.form.password.label")} + {t("access.form.password.label")} - + diff --git a/ui/src/components/certimate/AccessHuaweicloudForm.tsx b/ui/src/components/certimate/AccessHuaweicloudForm.tsx index 16c7c746..00167d75 100644 --- a/ui/src/components/certimate/AccessHuaweicloudForm.tsx +++ b/ui/src/components/certimate/AccessHuaweicloudForm.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HuaweiCloudConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HuaweiCloudAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -25,29 +25,29 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, region: z .string() - .min(1, "access.authorization.form.region.placeholder") + .min(1, "access.form.region.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), accessKeyId: z .string() - .min(1, "access.authorization.form.access_key_id.placeholder") + .min(1, "access.form.access_key_id.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), secretAccessKey: z .string() - .min(1, "access.authorization.form.secret_access_key.placeholder") + .min(1, "access.form.secret_access_key.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), }); - let config: HuaweiCloudConfig = { + let config: HuaweiCloudAccessConfig = { region: "cn-north-1", accessKeyId: "", secretAccessKey: "", }; - if (data) config = data.config as HuaweiCloudConfig; + if (data) config = data.config as HuaweiCloudAccessConfig; const form = useForm>({ resolver: zodResolver(formSchema), @@ -117,9 +117,9 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -132,7 +132,7 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="id" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -147,7 +147,7 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="configType" render={({ field }) => ( - {t("access.authorization.form.config.label")} + {t("access.form.config.label")} @@ -162,9 +162,9 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="region" render={({ field }) => ( - {t("access.authorization.form.region.label")} + {t("access.form.region.label")} - + @@ -177,9 +177,9 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="accessKeyId" render={({ field }) => ( - {t("access.authorization.form.access_key_id.label")} + {t("access.form.access_key_id.label")} - + @@ -192,9 +192,9 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr name="secretAccessKey" render={({ field }) => ( - {t("access.authorization.form.secret_access_key.label")} + {t("access.form.secret_access_key.label")} - + diff --git a/ui/src/components/certimate/AccessKubernetesForm.tsx b/ui/src/components/certimate/AccessKubernetesForm.tsx index 55ccc204..14db9997 100644 --- a/ui/src/components/certimate/AccessKubernetesForm.tsx +++ b/ui/src/components/certimate/AccessKubernetesForm.tsx @@ -10,7 +10,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from " import { Input } from "@/components/ui/input"; import { readFileContent } from "@/utils/file"; import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type KubernetesConfig } from "@/domain/access"; +import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type KubernetesAccessConfig } from "@/domain/access"; import { save } from "@/repository/access"; import { useAccessStore } from "@/stores/access"; @@ -32,17 +32,17 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp id: z.string().optional(), name: z .string() - .min(1, "access.authorization.form.name.placeholder") + .min(1, "access.form.name.placeholder") .max(64, t("common.errmsg.string_max", { max: 64 })), configType: accessTypeFormSchema, kubeConfig: z .string() - .min(0, "access.authorization.form.k8s_kubeconfig.placeholder") + .min(0, "access.form.k8s_kubeconfig.placeholder") .max(20480, t("common.errmsg.string_max", { max: 20480 })), kubeConfigFile: z.any().optional(), }); - let config: KubernetesConfig & { kubeConfigFile?: string } = { + let config: KubernetesAccessConfig & { kubeConfigFile?: string } = { kubeConfig: "", kubeConfigFile: "", }; @@ -126,9 +126,9 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp name="name" render={({ field }) => ( - {t("access.authorization.form.name.label")} + {t("access.form.name.label")} - + @@ -141,9 +141,9 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp name="kubeConfig" render={({ field }) => (
} op="add" - outConfigType="tencent" + outConfigType="tencentcloud" /> @@ -107,7 +107,7 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => { onValueChange={(value) => { form.setValue("access", value); }} - providerType="tencent-teo" + providerType="tencentcloud-eo" /> diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 763bc6f4..8544ac9f 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -1,153 +1,150 @@ import { z } from "zod"; import { type BaseModel } from "pocketbase"; -type AccessUsages = "apply" | "deploy" | "all"; - -type AccessProvider = { - type: string; - name: string; - icon: string; - usage: AccessUsages; -}; - -export const accessProvidersMap: Map = new Map( - [ - ["aliyun", "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all"], - ["tencent", "common.provider.tencent", "/imgs/providers/tencent.svg", "all"], - ["huaweicloud", "common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg", "all"], - ["baiducloud", "common.provider.baiducloud", "/imgs/providers/baiducloud.svg", "all"], - ["qiniu", "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy"], - ["dogecloud", "common.provider.dogecloud", "/imgs/providers/dogecloud.svg", "deploy"], - ["volcengine", "common.provider.volcengine", "/imgs/providers/volcengine.svg", "all"], - ["byteplus", "common.provider.byteplus", "/imgs/providers/byteplus.svg", "all"], - ["aws", "common.provider.aws", "/imgs/providers/aws.svg", "apply"], - ["cloudflare", "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply"], - ["namesilo", "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply"], - ["godaddy", "common.provider.godaddy", "/imgs/providers/godaddy.svg", "apply"], - ["pdns", "common.provider.pdns", "/imgs/providers/pdns.svg", "apply"], - ["httpreq", "common.provider.httpreq", "/imgs/providers/httpreq.svg", "apply"], - ["local", "common.provider.local", "/imgs/providers/local.svg", "deploy"], - ["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg", "deploy"], - ["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"], - ["k8s", "common.provider.kubernetes", "/imgs/providers/k8s.svg", "deploy"], - ].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessUsages }]) -); - -export const accessTypeFormSchema = z.union( - [ - z.literal("aliyun"), - z.literal("tencent"), - z.literal("huaweicloud"), - z.literal("baiducloud"), - z.literal("qiniu"), - z.literal("dogecloud"), - z.literal("aws"), - z.literal("cloudflare"), - z.literal("namesilo"), - z.literal("godaddy"), - z.literal("pdns"), - z.literal("httpreq"), - z.literal("local"), - z.literal("ssh"), - z.literal("webhook"), - z.literal("k8s"), - z.literal("volcengine"), - z.literal("byteplus"), - ], - { message: "access.authorization.form.type.placeholder" } -); +/* + 注意:如果追加新的常量值,请保持以 ASCII 排序。 + NOTICE: If you add new constant, please keep ASCII order. + */ +export const ACCESS_PROVIDER_TYPE_ACMEHTTPREQ = "acmehttpreq" as const; +export const ACCESS_PROVIDER_TYPE_ALIYUN = "aliyun" as const; +export const ACCESS_PROVIDER_TYPE_AWS = "aws" as const; +export const ACCESS_PROVIDER_TYPE_BAIDUCLOUD = "baiducloud" as const; +export const ACCESS_PROVIDER_TYPE_BYTEPLUS = "byteplus" as const; +export const ACCESS_PROVIDER_TYPE_CLOUDFLARE = "cloudflare" as const; +export const ACCESS_PROVIDER_TYPE_DOGECLOUD = "dogecloud" as const; +export const ACCESS_PROVIDER_TYPE_GODADDY = "godaddy" as const; +export const ACCESS_PROVIDER_TYPE_HUAWEICLOUD = "huaweicloud" as const; +export const ACCESS_PROVIDER_TYPE_KUBERNETES = "k8s" as const; +export const ACCESS_PROVIDER_TYPE_LOCAL = "local" as const; +export const ACCESS_PROVIDER_TYPE_NAMESILO = "namesilo" as const; +export const ACCESS_PROVIDER_TYPE_POWERDNS = "powerdns" as const; +export const ACCESS_PROVIDER_TYPE_QINIU = "qiniu" as const; +export const ACCESS_PROVIDER_TYPE_SSH = "ssh" as const; +export const ACCESS_PROVIDER_TYPE_TENCENTCLOUD = "tencentcloud" as const; +export const ACCESS_PROVIDER_TYPE_VOLCENGINE = "volcengine" as const; +export const ACCESS_PROVIDER_TYPE_WEBHOOK = "webhook" as const; +export const ACCESS_PROVIDER_TYPES = Object.freeze({ + ACMEHTTPREQ: ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, + ALIYUN: ACCESS_PROVIDER_TYPE_ALIYUN, + AWS: ACCESS_PROVIDER_TYPE_AWS, + BAIDUCLOUD: ACCESS_PROVIDER_TYPE_BAIDUCLOUD, + BYTEPLUS: ACCESS_PROVIDER_TYPE_BYTEPLUS, + CLOUDFLARE: ACCESS_PROVIDER_TYPE_CLOUDFLARE, + DOGECLOUD: ACCESS_PROVIDER_TYPE_DOGECLOUD, + GODADDY: ACCESS_PROVIDER_TYPE_GODADDY, + HUAWEICLOUD: ACCESS_PROVIDER_TYPE_HUAWEICLOUD, + KUBERNETES: ACCESS_PROVIDER_TYPE_KUBERNETES, + LOCAL: ACCESS_PROVIDER_TYPE_LOCAL, + NAMESILO: ACCESS_PROVIDER_TYPE_NAMESILO, + POWERDNS: ACCESS_PROVIDER_TYPE_POWERDNS, + QINIU: ACCESS_PROVIDER_TYPE_QINIU, + SSH: ACCESS_PROVIDER_TYPE_SSH, + TENCENTCLOUD: ACCESS_PROVIDER_TYPE_TENCENTCLOUD, + VOLCENGINE: ACCESS_PROVIDER_TYPE_VOLCENGINE, + WEBHOOK: ACCESS_PROVIDER_TYPE_WEBHOOK, +} as const); export interface AccessModel extends Omit { name: string; configType: string; usage: AccessUsages; - group?: string; - config: - | AliyunConfig - | TencentConfig - | HuaweiCloudConfig - | QiniuConfig - | DogeCloudConfig - | AwsConfig - | CloudflareConfig - | NamesiloConfig - | GodaddyConfig - | PdnsConfig - | HttpreqConfig - | LocalConfig - | SSHConfig - | WebhookConfig - | KubernetesConfig - | VolcengineConfig - | ByteplusConfig; + config: /* + 注意:如果追加新的类型,请保持以 ASCII 排序。 + NOTICE: If you add new type, please keep ASCII order. + */ + Record & + ( + | ACMEHttpReqAccessConfig + | AliyunAccessConfig + | AWSAccessConfig + | BaiduCloudAccessConfig + | BytePlusAccessConfig + | CloudflareAccessConfig + | DogeCloudAccessConfig + | GoDaddyAccessConfig + | HuaweiCloudAccessConfig + | KubernetesAccessConfig + | LocalAccessConfig + | NamesiloAccessConfig + | PowerDNSAccessConfig + | QiniuAccessConfig + | SSHAccessConfig + | TencentCloudAccessConfig + | VolcEngineAccessConfig + | WebhookAccessConfig + ); } -export type AliyunConfig = { +export type ACMEHttpReqAccessConfig = { + endpoint: string; + mode?: string; + username?: string; + password?: string; +}; + +export type AliyunAccessConfig = { accessKeyId: string; accessKeySecret: string; }; -export type TencentConfig = { - secretId: string; - secretKey: string; -}; - -export type HuaweiCloudConfig = { - region: string; - accessKeyId: string; - secretAccessKey: string; -}; - -export type BaiduCloudConfig = { - accessKeyId: string; - secretAccessKey: string; -}; - -export type QiniuConfig = { - accessKey: string; - secretKey: string; -}; - -export type DogeCloudConfig = { - accessKey: string; - secretKey: string; -}; - -export type AwsConfig = { - region: string; +export type AWSAccessConfig = { accessKeyId: string; secretAccessKey: string; + region?: string; hostedZoneId?: string; }; -export type CloudflareConfig = { +export type BaiduCloudAccessConfig = { + accessKeyId: string; + secretAccessKey: string; +}; + +export type BytePlusAccessConfig = { + accessKey: string; + secretKey: string; +}; + +export type CloudflareAccessConfig = { dnsApiToken: string; }; -export type NamesiloConfig = { - apiKey: string; +export type DogeCloudAccessConfig = { + accessKey: string; + secretKey: string; }; -export type GodaddyConfig = { +export type GoDaddyAccessConfig = { apiKey: string; apiSecret: string; }; -export type PdnsConfig = { +export type HuaweiCloudAccessConfig = { + region: string; + accessKeyId: string; + secretAccessKey: string; +}; + +export type KubernetesAccessConfig = { + kubeConfig: string; +}; + +export type LocalAccessConfig = never; + +export type NamesiloAccessConfig = { + apiKey: string; +}; + +export type PowerDNSAccessConfig = { apiUrl: string; apiKey: string; }; -export type HttpreqConfig = { - endpoint: string; - mode: string; - username: string; - password: string; +export type QiniuAccessConfig = { + accessKey: string; + secretKey: string; }; -export type LocalConfig = Record; - -export type SSHConfig = { +export type SSHAccessConfig = { host: string; port: string; username: string; @@ -157,20 +154,78 @@ export type SSHConfig = { keyPassphrase?: string; }; -export type WebhookConfig = { - url: string; +export type TencentCloudAccessConfig = { + secretId: string; + secretKey: string; }; -export type KubernetesConfig = { - kubeConfig: string; -}; - -export type VolcengineConfig = { +export type VolcEngineAccessConfig = { accessKeyId: string; secretAccessKey: string; }; -export type ByteplusConfig = { - accessKey: string; - secretKey: string; +export type WebhookAccessConfig = { + url: string; }; + +type AccessTypes = (typeof ACCESS_PROVIDER_TYPES)[keyof typeof ACCESS_PROVIDER_TYPES]; + +type AccessUsages = "apply" | "deploy" | "all"; + +type AccessProvider = { + type: AccessTypes; + name: string; + icon: string; + usage: AccessUsages; +}; + +export const accessProvidersMap: Map = new Map( + /* + 注意:与定义常量值时不同,此处的顺序决定显示在前端的顺序。 + NOTICE: The following order determines the order displayed at the frontend. + */ + [ + [ACCESS_PROVIDER_TYPE_ALIYUN, "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all"], + [ACCESS_PROVIDER_TYPE_TENCENTCLOUD, "common.provider.tencentcloud", "/imgs/providers/tencentcloud.svg", "all"], + [ACCESS_PROVIDER_TYPE_HUAWEICLOUD, "common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg", "all"], + [ACCESS_PROVIDER_TYPE_BAIDUCLOUD, "common.provider.baiducloud", "/imgs/providers/baiducloud.svg", "all"], + [ACCESS_PROVIDER_TYPE_QINIU, "common.provider.qiniu", "/imgs/providers/qiniu.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_DOGECLOUD, "common.provider.dogecloud", "/imgs/providers/dogecloud.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_VOLCENGINE, "common.provider.volcengine", "/imgs/providers/volcengine.svg", "all"], + [ACCESS_PROVIDER_TYPE_BYTEPLUS, "common.provider.byteplus", "/imgs/providers/byteplus.svg", "all"], + [ACCESS_PROVIDER_TYPE_AWS, "common.provider.aws", "/imgs/providers/aws.svg", "apply"], + [ACCESS_PROVIDER_TYPE_CLOUDFLARE, "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply"], + [ACCESS_PROVIDER_TYPE_NAMESILO, "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply"], + [ACCESS_PROVIDER_TYPE_GODADDY, "common.provider.godaddy", "/imgs/providers/godaddy.svg", "apply"], + [ACCESS_PROVIDER_TYPE_POWERDNS, "common.provider.powerdns", "/imgs/providers/powerdns.svg", "apply"], + [ACCESS_PROVIDER_TYPE_LOCAL, "common.provider.local", "/imgs/providers/local.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_SSH, "common.provider.ssh", "/imgs/providers/ssh.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"], + [ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"], + ].map(([type, name, icon, usage]) => [type as AccessTypes, { type: type as AccessTypes, name, icon, usage: usage as AccessUsages }]) +); + +export const accessTypeFormSchema = z.union( + [ + z.literal("aliyun"), + z.literal("tencentcloud"), + z.literal("huaweicloud"), + z.literal("baiducloud"), + z.literal("qiniu"), + z.literal("dogecloud"), + z.literal("aws"), + z.literal("cloudflare"), + z.literal("namesilo"), + z.literal("godaddy"), + z.literal("powerdns"), + z.literal("acmehttpreq"), + z.literal("local"), + z.literal("ssh"), + z.literal("webhook"), + z.literal("k8s"), + z.literal("volcengine"), + z.literal("byteplus"), + ], + { message: "access.form.type.placeholder" } +); diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index 00ae9fbe..62bd8ecc 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -70,11 +70,11 @@ export const deployTargetList: string[][] = [ ["aliyun-clb", "common.provider.aliyun.clb", "/imgs/providers/aliyun.svg"], ["aliyun-alb", "common.provider.aliyun.alb", "/imgs/providers/aliyun.svg"], ["aliyun-nlb", "common.provider.aliyun.nlb", "/imgs/providers/aliyun.svg"], - ["tencent-cdn", "common.provider.tencent.cdn", "/imgs/providers/tencent.svg"], - ["tencent-ecdn", "common.provider.tencent.ecdn", "/imgs/providers/tencent.svg"], - ["tencent-clb", "common.provider.tencent.clb", "/imgs/providers/tencent.svg"], - ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"], - ["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"], + ["tencentcloud-cdn", "common.provider.tencentcloud.cdn", "/imgs/providers/tencentcloud.svg"], + ["tencentcloud-ecdn", "common.provider.tencentcloud.ecdn", "/imgs/providers/tencentcloud.svg"], + ["tencentcloud-clb", "common.provider.tencentcloud.clb", "/imgs/providers/tencentcloud.svg"], + ["tencentcloud-cos", "common.provider.tencentcloud.cos", "/imgs/providers/tencentcloud.svg"], + ["tencentcloud-eo", "common.provider.tencentcloud.eo", "/imgs/providers/tencentcloud.svg"], ["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"], ["huaweicloud-elb", "common.provider.huaweicloud.elb", "/imgs/providers/huaweicloud.svg"], ["baiducloud-cdn", "common.provider.baiducloud.cdn", "/imgs/providers/baiducloud.svg"], @@ -83,7 +83,7 @@ export const deployTargetList: string[][] = [ ["local", "common.provider.local", "/imgs/providers/local.svg"], ["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"], ["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"], - ["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"], + ["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/kubernetes.svg"], ["volcengine-live", "common.provider.volcengine.live", "/imgs/providers/volcengine.svg"], ["volcengine-cdn", "common.provider.volcengine.cdn", "/imgs/providers/volcengine.svg"], ["byteplus-cdn", "common.provider.byteplus.cdn", "/imgs/providers/byteplus.svg"], diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 8fa38ae0..736eef07 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -16,67 +16,62 @@ "access.props.created_at": "Created At", "access.props.updated_at": "Updated At", - "access.authorization.form.name.label": "Name", - "access.authorization.form.name.placeholder": "Please enter authorization name", - "access.authorization.form.type.label": "Provider", - "access.authorization.form.type.placeholder": "Please select a provider", - "access.authorization.form.type.search.notfound": "Provider not found", - "access.authorization.form.type.list": "Authorization List", - "access.authorization.form.config.label": "Configuration Type", - "access.authorization.form.region.label": "Region", - "access.authorization.form.region.placeholder": "Please enter Region", - "access.authorization.form.access_key_id.label": "AccessKeyId", - "access.authorization.form.access_key_id.placeholder": "Please enter AccessKeyId", - "access.authorization.form.access_key_secret.label": "AccessKeySecret", - "access.authorization.form.access_key_secret.placeholder": "Please enter AccessKeySecret", - "access.authorization.form.access_key.label": "AccessKey", - "access.authorization.form.access_key.placeholder": "Please enter AccessKey", - "access.authorization.form.secret_id.label": "SecretId", - "access.authorization.form.secret_id.placeholder": "Please enter SecretId", - "access.authorization.form.secret_key.label": "SecretKey", - "access.authorization.form.secret_key.placeholder": "Please enter SecretKey", - "access.authorization.form.secret_access_key.label": "SecretAccessKey", - "access.authorization.form.secret_access_key.placeholder": "Please enter SecretAccessKey", - "access.authorization.form.aws_hosted_zone_id.label": "AWS Hosted Zone ID", - "access.authorization.form.aws_hosted_zone_id.placeholder": "Please enter AWS Hosted Zone ID", - "access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", - "access.authorization.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN", - "access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY", - "access.authorization.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY", - "access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET", - "access.authorization.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET", - "access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY", - "access.authorization.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY", - "access.authorization.form.pdns_api_url.label": "PDNS_API_URL", - "access.authorization.form.pdns_api_url.placeholder": "Please enter PDNS_API_URL", - "access.authorization.form.pdns_api_key.label": "PDNS_API_KEY", - "access.authorization.form.pdns_api_key.placeholder": "Please enter PDNS_API_KEY", - "access.authorization.form.httpreq_endpoint.label": "HTTPREQ_ENDPOINT", - "access.authorization.form.httpreq_endpoint.placeholder": "Please enter HTTPREQ_ENDPOINT", - "access.authorization.form.httpreq_mode.label": "HTTPREQ_MODE", - "access.authorization.form.httpreq_mode.placeholder": "Please enter HTTPREQ_MODE(RAW or '')", - "access.authorization.form.username.label": "Username", - "access.authorization.form.username.placeholder": "Please enter username", - "access.authorization.form.password.label": "Password", - "access.authorization.form.password.placeholder": "Please enter password", - "access.authorization.form.access_group.placeholder": "Please select a group", - "access.authorization.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)", - "access.authorization.form.ssh_host.label": "Server Host", - "access.authorization.form.ssh_host.placeholder": "Please enter Host", - "access.authorization.form.ssh_port.label": "SSH Port", - "access.authorization.form.ssh_port.placeholder": "Please enter Port", - "access.authorization.form.ssh_username.label": "Username", - "access.authorization.form.ssh_username.placeholder": "Please enter username", - "access.authorization.form.ssh_password.label": "Password (Log-in using password)", - "access.authorization.form.ssh_password.placeholder": "Please enter password", - "access.authorization.form.ssh_key.label": "Key (Log-in using private key)", - "access.authorization.form.ssh_key.placeholder": "Please enter Key", - "access.authorization.form.ssh_key_file.placeholder": "Please select file", - "access.authorization.form.ssh_key_passphrase.label": "Key Passphrase (Log-in using private key)", - "access.authorization.form.ssh_key_passphrase.placeholder": "Please enter Key Passphrase", - "access.authorization.form.webhook_url.label": "Webhook URL", - "access.authorization.form.webhook_url.placeholder": "Please enter Webhook URL", - "access.authorization.form.k8s_kubeconfig.label": "KubeConfig (Null will use pod's ServiceAccount)", - "access.authorization.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig", - "access.authorization.form.k8s_kubeconfig_file.placeholder": "Please select file (Null will use pod's ServiceAccount)" + "access.form.name.label": "Name", + "access.form.name.placeholder": "Please enter authorization name", + "access.form.type.label": "Provider", + "access.form.type.placeholder": "Please select a provider", + "access.form.region.label": "Region", + "access.form.region.placeholder": "Please enter Region", + "access.form.access_key_id.label": "AccessKeyId", + "access.form.access_key_id.placeholder": "Please enter AccessKeyId", + "access.form.access_key_secret.label": "AccessKeySecret", + "access.form.access_key_secret.placeholder": "Please enter AccessKeySecret", + "access.form.access_key.label": "AccessKey", + "access.form.access_key.placeholder": "Please enter AccessKey", + "access.form.secret_id.label": "SecretId", + "access.form.secret_id.placeholder": "Please enter SecretId", + "access.form.secret_key.label": "SecretKey", + "access.form.secret_key.placeholder": "Please enter SecretKey", + "access.form.secret_access_key.label": "SecretAccessKey", + "access.form.secret_access_key.placeholder": "Please enter SecretAccessKey", + "access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", + "access.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN", + "access.form.godaddy_api_key.label": "GO_DADDY_API_KEY", + "access.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY", + "access.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET", + "access.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET", + "access.form.namesilo_api_key.label": "NAMESILO_API_KEY", + "access.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY", + "access.form.pdns_api_url.label": "PDNS_API_URL", + "access.form.pdns_api_url.placeholder": "Please enter PDNS_API_URL", + "access.form.pdns_api_key.label": "PDNS_API_KEY", + "access.form.pdns_api_key.placeholder": "Please enter PDNS_API_KEY", + "access.form.httpreq_endpoint.label": "HTTPREQ_ENDPOINT", + "access.form.httpreq_endpoint.placeholder": "Please enter HTTPREQ_ENDPOINT", + "access.form.httpreq_mode.label": "HTTPREQ_MODE", + "access.form.httpreq_mode.placeholder": "Please enter HTTPREQ_MODE(RAW or '')", + "access.form.username.label": "Username", + "access.form.username.placeholder": "Please enter username", + "access.form.password.label": "Password", + "access.form.password.placeholder": "Please enter password", + "access.form.access_group.placeholder": "Please select a group", + "access.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)", + "access.form.ssh_host.label": "Server Host", + "access.form.ssh_host.placeholder": "Please enter Host", + "access.form.ssh_port.label": "SSH Port", + "access.form.ssh_port.placeholder": "Please enter Port", + "access.form.ssh_username.label": "Username", + "access.form.ssh_username.placeholder": "Please enter username", + "access.form.ssh_password.label": "Password (Log-in using password)", + "access.form.ssh_password.placeholder": "Please enter password", + "access.form.ssh_key.label": "Key (Log-in using private key)", + "access.form.ssh_key.placeholder": "Please enter Key", + "access.form.ssh_key_file.placeholder": "Please select file", + "access.form.ssh_key_passphrase.label": "Key Passphrase (Log-in using private key)", + "access.form.ssh_key_passphrase.placeholder": "Please enter Key Passphrase", + "access.form.webhook_url.label": "Webhook URL", + "access.form.webhook_url.placeholder": "Please enter Webhook URL", + "access.form.k8s_kubeconfig.label": "KubeConfig (Null will use pod's ServiceAccount)", + "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig", + "access.form.k8s_kubeconfig_file.placeholder": "Please select file (Null will use pod's ServiceAccount)" } diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 5095bab9..47d4f07b 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -51,27 +51,27 @@ "common.provider.aliyun.clb": "Alibaba Cloud - CLB", "common.provider.aliyun.alb": "Alibaba Cloud - ALB", "common.provider.aliyun.nlb": "Alibaba Cloud - NLB", - "common.provider.tencent": "Tencent Cloud", - "common.provider.tencent.cdn": "Tencent Cloud - CDN", - "common.provider.tencent.ecdn": "Tencent Cloud - ECDN", - "common.provider.tencent.clb": "Tencent Cloud - CLB", - "common.provider.tencent.cos": "Tencent Cloud - COS", - "common.provider.tencent.teo": "Tencent Cloud - EdgeOne", + "common.provider.tencentcloud": "Tencent Cloud", + "common.provider.tencentcloud.cdn": "Tencent Cloud - CDN", + "common.provider.tencentcloud.ecdn": "Tencent Cloud - ECDN", + "common.provider.tencentcloud.clb": "Tencent Cloud - CLB", + "common.provider.tencentcloud.cos": "Tencent Cloud - COS", + "common.provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", "common.provider.huaweicloud": "Huawei Cloud", "common.provider.huaweicloud.cdn": "Huawei Cloud - CDN", "common.provider.huaweicloud.elb": "Huawei Cloud - ELB", "common.provider.baiducloud": "Baidu Cloud", "common.provider.baiducloud.cdn": "Baidu Cloud - CDN", - "common.provider.qiniu": "Qiniu Cloud", - "common.provider.qiniu.cdn": "Qiniu Cloud - CDN", + "common.provider.qiniu": "Qiniu", + "common.provider.qiniu.cdn": "Qiniu - CDN", "common.provider.dogecloud": "Doge Cloud", "common.provider.dogecloud.cdn": "Doge Cloud - CDN", "common.provider.aws": "AWS", "common.provider.cloudflare": "Cloudflare", "common.provider.namesilo": "Namesilo", "common.provider.godaddy": "GoDaddy", - "common.provider.pdns": "PowerDNS", - "common.provider.httpreq": "Http Request", + "common.provider.powerdns": "PowerDNS", + "common.provider.acmehttpreq": "Http Request (ACME Proxy)", "common.provider.local": "Local Deployment", "common.provider.ssh": "SSH Deployment", "common.provider.webhook": "Webhook", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 068f80a1..d36b9785 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -10,73 +10,68 @@ "access.action.delete.confirm": "确定要删除此授权吗?", "access.props.name": "名称", - "access.props.provider": "服务商", - "access.props.provider.usage.dns": "DNS 服务商", - "access.props.provider.usage.host": "主机服务商", + "access.props.provider": "提供商", + "access.props.provider.usage.dns": "DNS 提供商", + "access.props.provider.usage.host": "主机提供商", "access.props.created_at": "创建时间", "access.props.updated_at": "更新时间", - "access.authorization.form.type.label": "服务商", - "access.authorization.form.type.placeholder": "请选择服务商", - "access.authorization.form.type.search.notfound": "未找到服务商", - "access.authorization.form.type.list": "服务商列表", - "access.authorization.form.name.label": "名称", - "access.authorization.form.name.placeholder": "请输入授权名称", - "access.authorization.form.config.label": "配置类型", - "access.authorization.form.region.label": "Region", - "access.authorization.form.region.placeholder": "请输入区域", - "access.authorization.form.access_key_id.label": "AccessKeyId", - "access.authorization.form.access_key_id.placeholder": "请输入 AccessKeyId", - "access.authorization.form.access_key_secret.label": "AccessKeySecret", - "access.authorization.form.access_key_secret.placeholder": "请输入 AccessKeySecret", - "access.authorization.form.access_key.label": "AccessKey", - "access.authorization.form.access_key.placeholder": "请输入 AccessKey", - "access.authorization.form.secret_id.label": "SecretId", - "access.authorization.form.secret_id.placeholder": "请输入 SecretId", - "access.authorization.form.secret_key.label": "SecretKey", - "access.authorization.form.secret_key.placeholder": "请输入 SecretKey", - "access.authorization.form.secret_access_key.label": "SecretAccessKey", - "access.authorization.form.secret_access_key.placeholder": "请输入 SecretAccessKey", - "access.authorization.form.aws_hosted_zone_id.label": "AWS 托管区域 ID", - "access.authorization.form.aws_hosted_zone_id.placeholder": "请输入 AWS Hosted Zone ID", - "access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", - "access.authorization.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN", - "access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY", - "access.authorization.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY", - "access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET", - "access.authorization.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET", - "access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY", - "access.authorization.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY", - "access.authorization.form.pdns_api_url.label": "PDNS_API_URL", - "access.authorization.form.pdns_api_url.placeholder": "请输入 PDNS_API_URL", - "access.authorization.form.pdns_api_key.label": "PDNS_API_KEY", - "access.authorization.form.pdns_api_key.placeholder": "请输入 PDNS_API_KEY", - "access.authorization.form.httpreq_endpoint.label": "HTTP 请求端点", - "access.authorization.form.httpreq_endpoint.placeholder": "请输入 请求端点", - "access.authorization.form.httpreq_mode.label": "模式", - "access.authorization.form.httpreq_mode.placeholder": "请输入模式( RAW or '')", - "access.authorization.form.username.label": "用户名", - "access.authorization.form.username.placeholder": "请输入用户名", - "access.authorization.form.password.label": "密码", - "access.authorization.form.password.placeholder": "请输入密码", - "access.authorization.form.access_group.placeholder": "请选择分组", - "access.authorization.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)", - "access.authorization.form.ssh_host.label": "服务器 Host", - "access.authorization.form.ssh_host.placeholder": "请输入 Host", - "access.authorization.form.ssh_port.label": "SSH 端口", - "access.authorization.form.ssh_port.placeholder": "请输入 Port", - "access.authorization.form.ssh_username.label": "用户名", - "access.authorization.form.ssh_username.placeholder": "请输入用户名", - "access.authorization.form.ssh_password.label": "密码(使用密码登录)", - "access.authorization.form.ssh_password.placeholder": "请输入密码", - "access.authorization.form.ssh_key.label": "Key(使用私钥登录)", - "access.authorization.form.ssh_key.placeholder": "请输入 Key", - "access.authorization.form.ssh_key_file.placeholder": "请选择文件", - "access.authorization.form.ssh_key_passphrase.label": "Key 口令(使用私钥登录)", - "access.authorization.form.ssh_key_passphrase.placeholder": "请输入 Key 口令", - "access.authorization.form.webhook_url.label": "Webhook URL", - "access.authorization.form.webhook_url.placeholder": "请输入 Webhook URL", - "access.authorization.form.k8s_kubeconfig.label": "KubeConfig(不选将使用Pod的ServiceAccount)", - "access.authorization.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig", - "access.authorization.form.k8s_kubeconfig_file.placeholder": "请选择文件" + "access.form.name.label": "名称", + "access.form.name.placeholder": "请输入授权名称", + "access.form.type.label": "提供商", + "access.form.type.placeholder": "请选择提供商", + "access.form.region.label": "Region", + "access.form.region.placeholder": "请输入区域", + "access.form.access_key_id.label": "AccessKeyId", + "access.form.access_key_id.placeholder": "请输入 AccessKeyId", + "access.form.access_key_secret.label": "AccessKeySecret", + "access.form.access_key_secret.placeholder": "请输入 AccessKeySecret", + "access.form.access_key.label": "AccessKey", + "access.form.access_key.placeholder": "请输入 AccessKey", + "access.form.secret_id.label": "SecretId", + "access.form.secret_id.placeholder": "请输入 SecretId", + "access.form.secret_key.label": "SecretKey", + "access.form.secret_key.placeholder": "请输入 SecretKey", + "access.form.secret_access_key.label": "SecretAccessKey", + "access.form.secret_access_key.placeholder": "请输入 SecretAccessKey", + "access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", + "access.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN", + "access.form.godaddy_api_key.label": "GO_DADDY_API_KEY", + "access.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY", + "access.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET", + "access.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET", + "access.form.namesilo_api_key.label": "NAMESILO_API_KEY", + "access.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY", + "access.form.pdns_api_url.label": "PDNS_API_URL", + "access.form.pdns_api_url.placeholder": "请输入 PDNS_API_URL", + "access.form.pdns_api_key.label": "PDNS_API_KEY", + "access.form.pdns_api_key.placeholder": "请输入 PDNS_API_KEY", + "access.form.httpreq_endpoint.label": "HTTP 请求端点", + "access.form.httpreq_endpoint.placeholder": "请输入 请求端点", + "access.form.httpreq_mode.label": "模式", + "access.form.httpreq_mode.placeholder": "请输入模式( RAW or '')", + "access.form.username.label": "用户名", + "access.form.username.placeholder": "请输入用户名", + "access.form.password.label": "密码", + "access.form.password.placeholder": "请输入密码", + "access.form.access_group.placeholder": "请选择分组", + "access.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)", + "access.form.ssh_host.label": "服务器 Host", + "access.form.ssh_host.placeholder": "请输入 Host", + "access.form.ssh_port.label": "SSH 端口", + "access.form.ssh_port.placeholder": "请输入 Port", + "access.form.ssh_username.label": "用户名", + "access.form.ssh_username.placeholder": "请输入用户名", + "access.form.ssh_password.label": "密码(使用密码登录)", + "access.form.ssh_password.placeholder": "请输入密码", + "access.form.ssh_key.label": "Key(使用私钥登录)", + "access.form.ssh_key.placeholder": "请输入 Key", + "access.form.ssh_key_file.placeholder": "请选择文件", + "access.form.ssh_key_passphrase.label": "Key 口令(使用私钥登录)", + "access.form.ssh_key_passphrase.placeholder": "请输入 Key 口令", + "access.form.webhook_url.label": "Webhook URL", + "access.form.webhook_url.placeholder": "请输入 Webhook URL", + "access.form.k8s_kubeconfig.label": "KubeConfig(不选将使用Pod的ServiceAccount)", + "access.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig", + "access.form.k8s_kubeconfig_file.placeholder": "请选择文件" } diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 22d41588..fd23a651 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -51,12 +51,12 @@ "common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "common.provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", "common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", - "common.provider.tencent": "腾讯云", - "common.provider.tencent.cos": "腾讯云 - 对象存储 COS", - "common.provider.tencent.cdn": "腾讯云 - 内容分发网络 CDN", - "common.provider.tencent.ecdn": "腾讯云 - 全站加速网络 ECDN", - "common.provider.tencent.clb": "腾讯云 - 负载均衡 CLB", - "common.provider.tencent.teo": "腾讯云 - 边缘安全加速平台 EdgeOne", + "common.provider.tencentcloud": "腾讯云", + "common.provider.tencentcloud.cos": "腾讯云 - 对象存储 COS", + "common.provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", + "common.provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", + "common.provider.tencentcloud.clb": "腾讯云 - 负载均衡 CLB", + "common.provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", "common.provider.huaweicloud": "华为云", "common.provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN", "common.provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB", @@ -70,8 +70,8 @@ "common.provider.cloudflare": "Cloudflare", "common.provider.namesilo": "Namesilo", "common.provider.godaddy": "GoDaddy", - "common.provider.pdns": "PowerDNS", - "common.provider.httpreq": "Http Request", + "common.provider.powerdns": "PowerDNS", + "common.provider.acmehttpreq": "Http Request (ACME Proxy)", "common.provider.local": "本地部署", "common.provider.ssh": "SSH 部署", "common.provider.webhook": "Webhook", From c27818b3b0d86eb606af9a5a52da13a544c41194 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 17 Dec 2024 17:11:11 +0800 Subject: [PATCH 05/39] feat(ui): new AccessEditForm using antd --- README.md | 9 +- README_EN.md | 6 +- internal/applicant/applicant.go | 4 +- .../huaweicloud-cdn/huaweicloud_cdn.go | 2 +- .../huaweicloud-elb/huaweicloud_elb.go | 2 +- .../huaweicloud-elb/huaweicloud_elb.go | 2 +- .../huaweicloud-scm/huaweicloud_scm.go | 2 +- ui/src/components/{certimate => }/Version.tsx | 0 ui/src/components/access/AccessEditForm.tsx | 193 ++++++++++++++++++ .../AccessEditFormACMEHttpReqConfig.tsx | 103 ++++++++++ .../access/AccessEditFormAWSConfig.tsx | 106 ++++++++++ .../access/AccessEditFormAliyunConfig.tsx | 72 +++++++ .../access/AccessEditFormBaiduCloudConfig.tsx | 72 +++++++ .../access/AccessEditFormBytePlusConfig.tsx | 72 +++++++ .../access/AccessEditFormCloudflareConfig.tsx | 58 ++++++ .../access/AccessEditFormDogeCloudConfig.tsx | 72 +++++++ .../access/AccessEditFormGoDaddyConfig.tsx | 72 +++++++ .../AccessEditFormHuaweiCloudConfig.tsx | 90 ++++++++ .../access/AccessEditFormKubernetesConfig.tsx | 79 +++++++ .../access/AccessEditFormLocalConfig.tsx | 29 +++ .../access/AccessEditFormNameSiloConfig.tsx | 58 ++++++ .../access/AccessEditFormPowerDNSConfig.tsx | 68 ++++++ .../access/AccessEditFormQiniuConfig.tsx | 72 +++++++ .../access/AccessEditFormSSHConfig.tsx | 162 +++++++++++++++ .../AccessEditFormTencentCloudConfig.tsx | 72 +++++++ .../access/AccessEditFormVolcEngineConfig.tsx | 72 +++++++ .../access/AccessEditFormWebhookConfig.tsx | 49 +++++ ui/src/components/access/AccessEditModal.tsx | 108 ++++++++++ ui/src/components/access/AccessTypeSelect.tsx | 66 +++--- .../certificate/CertificateDetail.tsx | 2 +- .../certificate/CertificateDetailDrawer.tsx | 16 +- ui/src/components/workflow/ApplyForm.tsx | 6 +- .../components/workflow/DeployToAliyunALB.tsx | 8 +- .../components/workflow/DeployToAliyunCDN.tsx | 8 +- .../components/workflow/DeployToAliyunCLB.tsx | 8 +- .../components/workflow/DeployToAliyunOss.tsx | 8 +- .../workflow/DeployToBaiduCloudCDN.tsx | 8 +- .../workflow/DeployToByteplusCDN.tsx | 8 +- .../workflow/DeployToDogeCloudCDN.tsx | 8 +- .../workflow/DeployToHuaweiCloudCDN.tsx | 8 +- .../workflow/DeployToHuaweiCloudELB.tsx | 8 +- .../workflow/DeployToKubernetesSecret.tsx | 8 +- ui/src/components/workflow/DeployToLocal.tsx | 8 +- .../components/workflow/DeployToQiniuCDN.tsx | 8 +- ui/src/components/workflow/DeployToSSH.tsx | 8 +- .../workflow/DeployToTencentCDN.tsx | 8 +- .../workflow/DeployToTencentCLB.tsx | 8 +- .../workflow/DeployToTencentCOS.tsx | 8 +- .../workflow/DeployToTencentTEO.tsx | 8 +- .../workflow/DeployToVolcengineCDN.tsx | 8 +- .../workflow/DeployToVolcengineLive.tsx | 8 +- .../components/workflow/DeployToWebhook.tsx | 8 +- ui/src/domain/access.ts | 17 +- ui/src/i18n/locales/en/nls.access.json | 144 ++++++++++--- ui/src/i18n/locales/en/nls.common.json | 7 +- ui/src/i18n/locales/zh/nls.access.json | 144 ++++++++++--- ui/src/i18n/locales/zh/nls.common.json | 3 +- ui/src/pages/AuthLayout.tsx | 2 +- ui/src/pages/ConsoleLayout.tsx | 2 +- ui/src/pages/accesses/AccessList.tsx | 30 ++- ui/src/pages/certificates/CertificateList.tsx | 1 + ui/src/pages/login/Login.tsx | 15 +- ui/src/pages/workflows/WorkflowList.tsx | 1 + ui/src/repository/access.ts | 4 +- ui/src/stores/access/index.ts | 14 +- ui/src/utils/file.ts | 2 +- 66 files changed, 2104 insertions(+), 238 deletions(-) rename ui/src/components/{certimate => }/Version.tsx (100%) create mode 100644 ui/src/components/access/AccessEditForm.tsx create mode 100644 ui/src/components/access/AccessEditFormACMEHttpReqConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormAWSConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormAliyunConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormBaiduCloudConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormBytePlusConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormCloudflareConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormDogeCloudConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormGoDaddyConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormHuaweiCloudConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormKubernetesConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormLocalConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormNameSiloConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormPowerDNSConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormQiniuConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormSSHConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormTencentCloudConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormVolcEngineConfig.tsx create mode 100644 ui/src/components/access/AccessEditFormWebhookConfig.tsx create mode 100644 ui/src/components/access/AccessEditModal.tsx diff --git a/README.md b/README.md index f03cd297..dbfcd1ee 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ > [!WARNING] > 当前分支为 `next`,是 v0.3.x 的开发分支,目前还没有稳定,请勿在生产环境中使用。 -> -> 如需访问 v0.2.x 源码,请切换至 `main` 分支。 +> +> 如需访问之前的版本,请切换至 `main` 分支。 # 🔒Certimate @@ -84,11 +84,11 @@ make local.run | 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN、ELB | | 七牛云 | | √ | 可部署到七牛云 CDN | | 多吉云 | | √ | 可部署到多吉云 CDN | -| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN | +| 火山引擎 | √ | √ | 可签发在火山引擎注册的域名;可部署到火山引擎 Live、CDN | | AWS | √ | | 可签发在 AWS Route53 托管的域名 | | CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 | | GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 | -| Namesilo | √ | | 可签发在 Namesilo 注册的域名 | +| NameSilo | √ | | 可签发在 NameSilo 注册的域名 | | PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 | | HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 | | 本地部署 | | √ | 可部署到本地服务器 | @@ -194,4 +194,3 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE. ## 十、Star 趋势图 [![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate) - diff --git a/README_EN.md b/README_EN.md index 15c61cf7..88e03baf 100644 --- a/README_EN.md +++ b/README_EN.md @@ -2,7 +2,7 @@ > [!WARNING] > The current branch is `next`, which is the development branch for v0.3.x. It is currently unstable and should not be used in production environments. -> +> > To access the previous versions, please switch to the `main` branch. # 🔒Certimate @@ -76,7 +76,7 @@ password:1234567890 ## List of Supported Providers | Provider | Registration | Deployment | Remarks | -| :-----------: | :----------: | :--------: |-------------------------------------------------------------------------------------------------------------| +| :-----------: | :----------: | :--------: | ----------------------------------------------------------------------------------------------------------- | | Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN,SLB | | Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud COS, CDN, ECDN, CLB, TEO | | Baidu Cloud | | √ | Supports deployment to Baidu Cloud CDN | @@ -87,7 +87,7 @@ password:1234567890 | AWS | √ | | Supports domains managed on AWS Route53 | | CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates | | GoDaddy | √ | | Supports domains registered on GoDaddy | -| Namesilo | √ | | Supports domains registered on Namesilo | +| NameSilo | √ | | Supports domains registered on NameSilo | | PowerDNS | √ | | Supports domains managed on PowerDNS | | HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request | | Local Deploy | | √ | Supports deployment to local servers | diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index 7fd00a84..33eb8779 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -39,7 +39,7 @@ const ( configTypeCloudflare = "cloudflare" configTypeGoDaddy = "godaddy" configTypeHuaweiCloud = "huaweicloud" - configTypeNamesilo = "namesilo" + configTypeNameSilo = "namesilo" configTypePowerDNS = "powerdns" configTypeTencentCloud = "tencentcloud" configTypeVolcEngine = "volcengine" @@ -219,7 +219,7 @@ func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) { return NewAws(option), nil case configTypeCloudflare: return NewCloudflare(option), nil - case configTypeNamesilo: + case configTypeNameSilo: return NewNamesilo(option), nil case configTypeGoDaddy: return NewGodaddy(option), nil diff --git a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go index 9c7131d1..b305156b 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-cdn/huaweicloud_cdn.go @@ -22,7 +22,7 @@ type HuaweiCloudCDNDeployerConfig struct { AccessKeyId string `json:"accessKeyId"` // 华为云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 华为云地域。 + // 华为云区域。 Region string `json:"region"` // 加速域名(不支持泛域名)。 Domain string `json:"domain"` diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go index 7cbc4f12..d3c142f0 100644 --- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go @@ -27,7 +27,7 @@ type HuaweiCloudELBDeployerConfig struct { AccessKeyId string `json:"accessKeyId"` // 华为云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 华为云地域。 + // 华为云区域。 Region string `json:"region"` // 部署资源类型。 ResourceType DeployResourceType `json:"resourceType"` diff --git a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go index 42c4747f..d8ea91c6 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-elb/huaweicloud_elb.go @@ -26,7 +26,7 @@ type HuaweiCloudELBUploaderConfig struct { AccessKeyId string `json:"accessKeyId"` // 华为云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 华为云地域。 + // 华为云区域。 Region string `json:"region"` } diff --git a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go index 1c219c79..5662f9b1 100644 --- a/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go +++ b/internal/pkg/core/uploader/providers/huaweicloud-scm/huaweicloud_scm.go @@ -22,7 +22,7 @@ type HuaweiCloudSCMUploaderConfig struct { AccessKeyId string `json:"accessKeyId"` // 华为云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 华为云地域。 + // 华为云区域。 Region string `json:"region"` } diff --git a/ui/src/components/certimate/Version.tsx b/ui/src/components/Version.tsx similarity index 100% rename from ui/src/components/certimate/Version.tsx rename to ui/src/components/Version.tsx diff --git a/ui/src/components/access/AccessEditForm.tsx b/ui/src/components/access/AccessEditForm.tsx new file mode 100644 index 00000000..049abd68 --- /dev/null +++ b/ui/src/components/access/AccessEditForm.tsx @@ -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>; + +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; +}; + +const AccessEditForm = forwardRef(({ 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>(); + + const [initialValues, setInitialValues] = useState>>(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 ; + case ACCESS_PROVIDER_TYPES.ALIYUN: + return ; + case ACCESS_PROVIDER_TYPES.AWS: + return ; + case ACCESS_PROVIDER_TYPES.BAIDUCLOUD: + return ; + case ACCESS_PROVIDER_TYPES.BYTEPLUS: + return ; + case ACCESS_PROVIDER_TYPES.CLOUDFLARE: + return ; + case ACCESS_PROVIDER_TYPES.DOGECLOUD: + return ; + case ACCESS_PROVIDER_TYPES.GODADDY: + return ; + case ACCESS_PROVIDER_TYPES.HUAWEICLOUD: + return ; + case ACCESS_PROVIDER_TYPES.KUBERNETES: + return ; + case ACCESS_PROVIDER_TYPES.LOCAL: + return ; + case ACCESS_PROVIDER_TYPES.NAMESILO: + return ; + case ACCESS_PROVIDER_TYPES.POWERDNS: + return ; + case ACCESS_PROVIDER_TYPES.QINIU: + return ; + case ACCESS_PROVIDER_TYPES.SSH: + return ; + case ACCESS_PROVIDER_TYPES.TENCENTCLOUD: + return ; + case ACCESS_PROVIDER_TYPES.VOLCENGINE: + return ; + case ACCESS_PROVIDER_TYPES.WEBHOOK: + return ; + } + }, [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 ( + +
+ + + + + + } + > + + + +
+
+ ); +}); + +export default AccessEditForm; diff --git a/ui/src/components/access/AccessEditFormACMEHttpReqConfig.tsx b/ui/src/components/access/AccessEditFormACMEHttpReqConfig.tsx new file mode 100644 index 00000000..6f88c6dd --- /dev/null +++ b/ui/src/components/access/AccessEditFormACMEHttpReqConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormACMEHttpReqConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormACMEHttpReqConfig; diff --git a/ui/src/components/access/AccessEditFormAWSConfig.tsx b/ui/src/components/access/AccessEditFormAWSConfig.tsx new file mode 100644 index 00000000..d153c734 --- /dev/null +++ b/ui/src/components/access/AccessEditFormAWSConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormAWSConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormAWSConfig; diff --git a/ui/src/components/access/AccessEditFormAliyunConfig.tsx b/ui/src/components/access/AccessEditFormAliyunConfig.tsx new file mode 100644 index 00000000..9d965d4c --- /dev/null +++ b/ui/src/components/access/AccessEditFormAliyunConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormAliyunConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormAliyunConfig; diff --git a/ui/src/components/access/AccessEditFormBaiduCloudConfig.tsx b/ui/src/components/access/AccessEditFormBaiduCloudConfig.tsx new file mode 100644 index 00000000..b66d55c5 --- /dev/null +++ b/ui/src/components/access/AccessEditFormBaiduCloudConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormBaiduCloudConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormBaiduCloudConfig; diff --git a/ui/src/components/access/AccessEditFormBytePlusConfig.tsx b/ui/src/components/access/AccessEditFormBytePlusConfig.tsx new file mode 100644 index 00000000..d55e17b7 --- /dev/null +++ b/ui/src/components/access/AccessEditFormBytePlusConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormBytePlusConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormBytePlusConfig; diff --git a/ui/src/components/access/AccessEditFormCloudflareConfig.tsx b/ui/src/components/access/AccessEditFormCloudflareConfig.tsx new file mode 100644 index 00000000..40a3167b --- /dev/null +++ b/ui/src/components/access/AccessEditFormCloudflareConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormCloudflareConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessEditFormCloudflareConfig; diff --git a/ui/src/components/access/AccessEditFormDogeCloudConfig.tsx b/ui/src/components/access/AccessEditFormDogeCloudConfig.tsx new file mode 100644 index 00000000..d8dac295 --- /dev/null +++ b/ui/src/components/access/AccessEditFormDogeCloudConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormDogeCloudConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormDogeCloudConfig; diff --git a/ui/src/components/access/AccessEditFormGoDaddyConfig.tsx b/ui/src/components/access/AccessEditFormGoDaddyConfig.tsx new file mode 100644 index 00000000..3c71b752 --- /dev/null +++ b/ui/src/components/access/AccessEditFormGoDaddyConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormGoDaddyConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormGoDaddyConfig; diff --git a/ui/src/components/access/AccessEditFormHuaweiCloudConfig.tsx b/ui/src/components/access/AccessEditFormHuaweiCloudConfig.tsx new file mode 100644 index 00000000..fc0e422d --- /dev/null +++ b/ui/src/components/access/AccessEditFormHuaweiCloudConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormHuaweiCloudConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormHuaweiCloudConfig; diff --git a/ui/src/components/access/AccessEditFormKubernetesConfig.tsx b/ui/src/components/access/AccessEditFormKubernetesConfig.tsx new file mode 100644 index 00000000..04adee75 --- /dev/null +++ b/ui/src/components/access/AccessEditFormKubernetesConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + setKubeFileList(model?.kubeConfig?.trim() ? [{ uid: "-1", name: "kubeconfig", status: "done" }] : []); + }, [model]); + + const [kubeFileList, setKubeFileList] = useState([]); + + 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 ( +
+ } + > + +
+ ); +}; + +export default AccessEditFormKubernetesConfig; diff --git a/ui/src/components/access/AccessEditFormLocalConfig.tsx b/ui/src/components/access/AccessEditFormLocalConfig.tsx new file mode 100644 index 00000000..918908a8 --- /dev/null +++ b/ui/src/components/access/AccessEditFormLocalConfig.tsx @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { Form, type FormInstance } from "antd"; + +import { type LocalAccessConfig } from "@/domain/access"; + +type AccessEditFormLocalConfigModelType = Partial; + +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
; +}; + +export default AccessEditFormLocalConfig; diff --git a/ui/src/components/access/AccessEditFormNameSiloConfig.tsx b/ui/src/components/access/AccessEditFormNameSiloConfig.tsx new file mode 100644 index 00000000..54b77870 --- /dev/null +++ b/ui/src/components/access/AccessEditFormNameSiloConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormNameSiloConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default AccessEditFormNameSiloConfig; diff --git a/ui/src/components/access/AccessEditFormPowerDNSConfig.tsx b/ui/src/components/access/AccessEditFormPowerDNSConfig.tsx new file mode 100644 index 00000000..5cbc52a2 --- /dev/null +++ b/ui/src/components/access/AccessEditFormPowerDNSConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormPowerDNSConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormPowerDNSConfig; diff --git a/ui/src/components/access/AccessEditFormQiniuConfig.tsx b/ui/src/components/access/AccessEditFormQiniuConfig.tsx new file mode 100644 index 00000000..9bb3f1c2 --- /dev/null +++ b/ui/src/components/access/AccessEditFormQiniuConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormQiniuConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormQiniuConfig; diff --git a/ui/src/components/access/AccessEditFormSSHConfig.tsx b/ui/src/components/access/AccessEditFormSSHConfig.tsx new file mode 100644 index 00000000..4d9c8d9b --- /dev/null +++ b/ui/src/components/access/AccessEditFormSSHConfig.tsx @@ -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; + +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?)|::([\da−fA−F]1,4:)0,4((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\d|[01]?\d\d?)|::([\da−fA−F]1,4:)0,4((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\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?)|([\da−fA−F]1,4:)2:([\da−fA−F]1,4:)0,2((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\d|[01]?\d\d?)|([\da−fA−F]1,4:)2:([\da−fA−F]1,4:)0,2((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\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?)|([\da−fA−F]1,4:)4:((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\d|[01]?\d\d?)|([\da−fA−F]1,4:)4:((25[0−5]|2[0−4]\d|[01]?\d\d?)\.)3(25[0−5]|2[0−4]\d|[01]?\d\d?)|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|:((:[\da−fA−F]1,4)1,6|:)|:((:[\da−fA−F]1,4)1,6|:)|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)|([\da−fA−F]1,4:)2((:[\da−fA−F]1,4)1,4|:)|([\da−fA−F]1,4:)2((:[\da−fA−F]1,4)1,4|:)|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)|([\da−fA−F]1,4:)4((:[\da−fA−F]1,4)1,2|:)|([\da−fA−F]1,4:)4((:[\da−fA−F]1,4)1,2|:)|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?|([\da−fA−F]1,4:)6:|([\da−fA−F]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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + setKeyFileList(model?.key?.trim() ? [{ uid: "-1", name: "sshkey", status: "done" }] : []); + }, [model]); + + const [keyFileList, setKeyFileList] = useState([]); + + 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 ( +
+
+
+ + + +
+ +
+ + + +
+
+ +
+
+ + + +
+ +
+ } + > + + +
+
+ +
+
+ } + > + +
+ +
+ } + > + + +
+
+
+ ); +}; + +export default AccessEditFormSSHConfig; diff --git a/ui/src/components/access/AccessEditFormTencentCloudConfig.tsx b/ui/src/components/access/AccessEditFormTencentCloudConfig.tsx new file mode 100644 index 00000000..b710635b --- /dev/null +++ b/ui/src/components/access/AccessEditFormTencentCloudConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormTencentCloudConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormTencentCloudConfig; diff --git a/ui/src/components/access/AccessEditFormVolcEngineConfig.tsx b/ui/src/components/access/AccessEditFormVolcEngineConfig.tsx new file mode 100644 index 00000000..173bed42 --- /dev/null +++ b/ui/src/components/access/AccessEditFormVolcEngineConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormVolcEngineConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessEditFormVolcEngineConfig; diff --git a/ui/src/components/access/AccessEditFormWebhookConfig.tsx b/ui/src/components/access/AccessEditFormWebhookConfig.tsx new file mode 100644 index 00000000..8b2649a5 --- /dev/null +++ b/ui/src/components/access/AccessEditFormWebhookConfig.tsx @@ -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; + +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>>(model ?? initModel()); + useEffect(() => { + setInitialValues(model ?? initModel()); + }, [model]); + + const handleFormChange = (_: unknown, fields: AccessEditFormWebhookConfigModelType) => { + onModelChange?.(fields); + }; + + return ( +
+ + + +
+ ); +}; + +export default AccessEditFormWebhookConfig; diff --git a/ui/src/components/access/AccessEditModal.tsx b/ui/src/components/access/AccessEditModal.tsx new file mode 100644 index 00000000..c9baec52 --- /dev/null +++ b/ui/src/components/access/AccessEditModal.tsx @@ -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; + 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(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(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} + + 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} + > +
+ +
+
+ + ); +}; + +export default AccessEditModal; diff --git a/ui/src/components/access/AccessTypeSelect.tsx b/ui/src/components/access/AccessTypeSelect.tsx index 711d1b22..bc52a709 100644 --- a/ui/src/components/access/AccessTypeSelect.tsx +++ b/ui/src/components/access/AccessTypeSelect.tsx @@ -15,17 +15,44 @@ const AccessTypeSelect = memo((props: AccessTypeSelectProps) => { label: t(item.name), })); + const renderOption = (key: string) => { + const provider = accessProvidersMap.get(key); + return ( +
+ + + + {t(provider?.name ?? "")} + + +
+ {provider?.usage === "apply" && ( + <> + {t("access.props.provider.usage.dns")} + + )} + {provider?.usage === "deploy" && ( + <> + {t("access.props.provider.usage.host")} + + )} + {provider?.usage === "all" && ( + <> + {t("access.props.provider.usage.dns")} + {t("access.props.provider.usage.host")} + + )} +
+
+ ); + }; + return ( @@ -55,7 +54,7 @@ const Login = () => { - diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 9196aad6..8559b73c 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -313,6 +313,7 @@ const WorkflowList = () => { current: page, pageSize: pageSize, total: tableTotal, + showSizeChanger: true, onChange: (page: number, pageSize: number) => { setPage(page); setPageSize(pageSize); diff --git a/ui/src/repository/access.ts b/ui/src/repository/access.ts index bc4ab8c8..2d114a45 100644 --- a/ui/src/repository/access.ts +++ b/ui/src/repository/access.ts @@ -12,7 +12,7 @@ export const list = async () => { }); }; -export const save = async (record: AccessModel) => { +export const save = async (record: Partial) => { if (record.id) { return await getPocketBase().collection(COLLECTION_NAME).update(record.id, record); } @@ -20,7 +20,7 @@ export const save = async (record: AccessModel) => { return await getPocketBase().collection(COLLECTION_NAME).create(record); }; -export const remove = async (record: AccessModel) => { +export const remove = async (record: Partial) => { record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") }; await getPocketBase().collection(COLLECTION_NAME).update(record.id, record); }; diff --git a/ui/src/stores/access/index.ts b/ui/src/stores/access/index.ts index c67167e7..510167c8 100644 --- a/ui/src/stores/access/index.ts +++ b/ui/src/stores/access/index.ts @@ -6,7 +6,7 @@ import { list as listAccess, save as saveAccess, remove as removeAccess } from " export interface AccessState { accesses: AccessModel[]; - createAccess: (access: AccessModel) => void; + createAccess: (access: Omit) => void; updateAccess: (access: AccessModel) => void; deleteAccess: (access: AccessModel) => void; fetchAccesses: () => Promise; @@ -17,22 +17,24 @@ export const useAccessStore = create((set) => { accesses: [], createAccess: async (access) => { - access = await saveAccess(access); + const record = await saveAccess(access); set( produce((state: AccessState) => { - state.accesses.unshift(access); + state.accesses.unshift(record); }) ); }, updateAccess: async (access) => { - access = await saveAccess(access); + const record = await saveAccess(access); set( produce((state: AccessState) => { - const index = state.accesses.findIndex((e) => e.id === access.id); - state.accesses[index] = access; + const index = state.accesses.findIndex((e) => e.id === record.id); + if (index !== -1) { + state.accesses[index] = record; + } }) ); }, diff --git a/ui/src/utils/file.ts b/ui/src/utils/file.ts index 41283994..00b7beef 100644 --- a/ui/src/utils/file.ts +++ b/ui/src/utils/file.ts @@ -14,7 +14,7 @@ export function readFileContent(file: File): Promise { reader.onerror = () => reject(reader.error); - reader.readAsText(file); + reader.readAsText(file, "utf-8"); }); } From 0fa6d2980b4cc29b2e3a374d4a907ac8dbe98635 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 17 Dec 2024 19:10:32 +0800 Subject: [PATCH 06/39] chore: remove unused code --- ui/package-lock.json | 161 -------- ui/package.json | 1 - .../components/certimate/AccessAliyunForm.tsx | 197 ---------- ui/src/components/certimate/AccessAwsForm.tsx | 241 ------------ .../certimate/AccessBaiduCloudForm.tsx | 197 ---------- .../certimate/AccessByteplusForm.tsx | 194 ---------- .../certimate/AccessCloudflareForm.tsx | 168 --------- .../certimate/AccessDogeCloudForm.tsx | 188 ---------- .../components/certimate/AccessEditDialog.tsx | 306 --------------- .../certimate/AccessGodaddyForm.tsx | 190 ---------- .../certimate/AccessHttpreqForm.tsx | 233 ------------ .../certimate/AccessHuaweicloudForm.tsx | 216 ----------- .../certimate/AccessKubernetesForm.tsx | 193 ---------- .../components/certimate/AccessLocalForm.tsx | 147 -------- .../certimate/AccessNamesiloForm.tsx | 168 --------- .../components/certimate/AccessPdnsForm.tsx | 192 ---------- .../components/certimate/AccessQiniuForm.tsx | 188 ---------- ui/src/components/certimate/AccessSSHForm.tsx | 348 ------------------ .../certimate/AccessTencentForm.tsx | 190 ---------- .../certimate/AccessVolcengineForm.tsx | 194 ---------- .../certimate/AccessWebhookForm.tsx | 165 --------- ui/src/components/ui/popover.tsx | 28 -- ui/src/components/workflow/AccessSelect.tsx | 6 +- ui/src/domain/access.ts | 27 +- ui/src/i18n/locales/en/nls.access.json | 30 +- ui/src/i18n/locales/zh/nls.access.json | 30 +- ui/src/repository/access.ts | 6 +- 27 files changed, 11 insertions(+), 4193 deletions(-) delete mode 100644 ui/src/components/certimate/AccessAliyunForm.tsx delete mode 100644 ui/src/components/certimate/AccessAwsForm.tsx delete mode 100644 ui/src/components/certimate/AccessBaiduCloudForm.tsx delete mode 100644 ui/src/components/certimate/AccessByteplusForm.tsx delete mode 100644 ui/src/components/certimate/AccessCloudflareForm.tsx delete mode 100644 ui/src/components/certimate/AccessDogeCloudForm.tsx delete mode 100644 ui/src/components/certimate/AccessEditDialog.tsx delete mode 100644 ui/src/components/certimate/AccessGodaddyForm.tsx delete mode 100644 ui/src/components/certimate/AccessHttpreqForm.tsx delete mode 100644 ui/src/components/certimate/AccessHuaweicloudForm.tsx delete mode 100644 ui/src/components/certimate/AccessKubernetesForm.tsx delete mode 100644 ui/src/components/certimate/AccessLocalForm.tsx delete mode 100644 ui/src/components/certimate/AccessNamesiloForm.tsx delete mode 100644 ui/src/components/certimate/AccessPdnsForm.tsx delete mode 100644 ui/src/components/certimate/AccessQiniuForm.tsx delete mode 100644 ui/src/components/certimate/AccessSSHForm.tsx delete mode 100644 ui/src/components/certimate/AccessTencentForm.tsx delete mode 100644 ui/src/components/certimate/AccessVolcengineForm.tsx delete mode 100644 ui/src/components/certimate/AccessWebhookForm.tsx delete mode 100644 ui/src/components/ui/popover.tsx diff --git a/ui/package-lock.json b/ui/package-lock.json index e2b71062..09c55d31 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,7 +15,6 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", @@ -2166,166 +2165,6 @@ } } }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", - "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.6", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", diff --git a/ui/package.json b/ui/package.json index a7963f08..9889164f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,7 +17,6 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", diff --git a/ui/src/components/certimate/AccessAliyunForm.tsx b/ui/src/components/certimate/AccessAliyunForm.tsx deleted file mode 100644 index ebae48a4..00000000 --- a/ui/src/components/certimate/AccessAliyunForm.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type AliyunAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessAliyunFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => { - const { t } = useTranslation(); - - const { createAccess, updateAccess } = useAccessStore(); - - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKeyId: z - .string() - .min(1, "access.form.access_key_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - accessSecretId: z - .string() - .min(1, "access.form.access_key_secret.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: AliyunAccessConfig = { - accessKeyId: "", - accessKeySecret: "", - }; - if (data) config = data.config as AliyunAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "aliyun", - accessKeyId: config.accessKeyId, - accessSecretId: config.accessKeySecret, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKeyId: data.accessKeyId, - accessKeySecret: data.accessSecretId, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_secret.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessAliyunForm; diff --git a/ui/src/components/certimate/AccessAwsForm.tsx b/ui/src/components/certimate/AccessAwsForm.tsx deleted file mode 100644 index 4669bb12..00000000 --- a/ui/src/components/certimate/AccessAwsForm.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { AccessModel, accessProvidersMap, accessTypeFormSchema, type AWSAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessAwsFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => { - const { t } = useTranslation(); - - const { createAccess, updateAccess } = useAccessStore(); - - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - region: z - .string() - .min(1, "access.form.region.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - accessKeyId: z - .string() - .min(1, "access.form.access_key_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretAccessKey: z - .string() - .min(1, "access.form.secret_access_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - hostedZoneId: z - .string() - .min(0, "access.form.aws_hosted_zone_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: AWSAccessConfig = { - region: "cn-north-1", - accessKeyId: "", - secretAccessKey: "", - hostedZoneId: "", - }; - if (data) config = data.config as AWSAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "aws", - region: config.region, - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - hostedZoneId: config.hostedZoneId, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - region: data.region, - accessKeyId: data.accessKeyId, - secretAccessKey: data.secretAccessKey, - hostedZoneId: data.hostedZoneId, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.region.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_access_key.label")} - - - - - - - )} - /> - - ( - - {t("access.form.aws_hosted_zone_id.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessAwsForm; diff --git a/ui/src/components/certimate/AccessBaiduCloudForm.tsx b/ui/src/components/certimate/AccessBaiduCloudForm.tsx deleted file mode 100644 index 2bd1bb59..00000000 --- a/ui/src/components/certimate/AccessBaiduCloudForm.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BaiduCloudAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessBaiduCloudFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProps) => { - const { t } = useTranslation(); - - const { createAccess, updateAccess } = useAccessStore(); - - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKeyId: z - .string() - .min(1, "access.form.access_key_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretAccessKey: z - .string() - .min(1, "access.form.secret_access_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: BaiduCloudAccessConfig = { - accessKeyId: "", - secretAccessKey: "", - }; - if (data) config = data.config as BaiduCloudAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "baiducloud", - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKeyId: data.accessKeyId, - secretAccessKey: data.secretAccessKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_access_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessBaiduCloudForm; diff --git a/ui/src/components/certimate/AccessByteplusForm.tsx b/ui/src/components/certimate/AccessByteplusForm.tsx deleted file mode 100644 index d069383b..00000000 --- a/ui/src/components/certimate/AccessByteplusForm.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BytePlusAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessByteplusFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKey: z - .string() - .min(1, "access.form.access_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretKey: z - .string() - .min(1, "access.form.secret_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: BytePlusAccessConfig = { - accessKey: "", - secretKey: "", - }; - if (data) config = data.config as BytePlusAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "byteplus", - accessKey: config.accessKey, - secretKey: config.secretKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKey: data.accessKey, - secretKey: data.secretKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessByteplusForm; diff --git a/ui/src/components/certimate/AccessCloudflareForm.tsx b/ui/src/components/certimate/AccessCloudflareForm.tsx deleted file mode 100644 index 874ca81d..00000000 --- a/ui/src/components/certimate/AccessCloudflareForm.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type CloudflareAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessCloudflareFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - dnsApiToken: z - .string() - .min(1, "access.form.cloud_dns_api_token.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: CloudflareAccessConfig = { - dnsApiToken: "", - }; - if (data) config = data.config as CloudflareAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "cloudflare", - dnsApiToken: config.dnsApiToken, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - dnsApiToken: data.dnsApiToken, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.cloud_dns_api_token.label")} - - - - - - - )} - /> - -
- -
- - - - ); -}; - -export default AccessCloudflareForm; diff --git a/ui/src/components/certimate/AccessDogeCloudForm.tsx b/ui/src/components/certimate/AccessDogeCloudForm.tsx deleted file mode 100644 index aa963503..00000000 --- a/ui/src/components/certimate/AccessDogeCloudForm.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type DogeCloudAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessDogeCloudFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKey: z.string().min(1, "access.form.access_key.placeholder").max(64), - secretKey: z.string().min(1, "access.form.secret_key.placeholder").max(64), - }); - - let config: DogeCloudAccessConfig = { - accessKey: "", - secretKey: "", - }; - if (data) config = data.config as DogeCloudAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "dogecloud", - accessKey: config.accessKey, - secretKey: config.secretKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKey: data.accessKey, - secretKey: data.secretKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessDogeCloudForm; diff --git a/ui/src/components/certimate/AccessEditDialog.tsx b/ui/src/components/certimate/AccessEditDialog.tsx deleted file mode 100644 index d777dbcb..00000000 --- a/ui/src/components/certimate/AccessEditDialog.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { cn } from "@/components/ui/utils"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Label } from "@/components/ui/label"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import AccessTypeSelect from "@/components/access/AccessTypeSelect"; -import AccessAliyunForm from "./AccessAliyunForm"; -import AccessTencentForm from "./AccessTencentForm"; -import AccessHuaweiCloudForm from "./AccessHuaweicloudForm"; -import AccessBaiduCloudForm from "./AccessBaiduCloudForm"; -import AccessQiniuForm from "./AccessQiniuForm"; -import AccessDogeCloudForm from "./AccessDogeCloudForm"; -import AccessAwsForm from "./AccessAwsForm"; -import AccessCloudflareForm from "./AccessCloudflareForm"; -import AccessNamesiloForm from "./AccessNamesiloForm"; -import AccessGodaddyForm from "./AccessGodaddyForm"; -import AccessPdnsForm from "./AccessPdnsForm"; -import AccessHttpreqForm from "./AccessHttpreqForm"; -import AccessLocalForm from "./AccessLocalForm"; -import AccessSSHForm from "./AccessSSHForm"; -import AccessWebhookForm from "./AccessWebhookForm"; -import AccessKubernetesForm from "./AccessKubernetesForm"; -import AccessVolcengineForm from "./AccessVolcengineForm"; -import AccessByteplusForm from "./AccessByteplusForm"; -import { AccessModel } from "@/domain/access"; - -type AccessEditProps = { - op: "add" | "edit" | "copy"; - className?: string; - trigger: React.ReactNode; - data?: AccessModel; - outConfigType?: string; -}; - -const AccessEditDialog = ({ trigger, op, data, className, outConfigType }: AccessEditProps) => { - const { t } = useTranslation(); - - const [open, setOpen] = useState(false); - - const [configType, setConfigType] = useState(data?.configType || ""); - - useEffect(() => { - if (outConfigType) { - setConfigType(outConfigType); - } - }, [outConfigType]); - - let childComponent = <> ; - switch (configType) { - case "aliyun": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "tencentcloud": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "huaweicloud": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "baiducloud": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "qiniu": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "dogecloud": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "aws": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "cloudflare": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "namesilo": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "godaddy": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "powerdns": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "acmehttpreq": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "local": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "ssh": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "webhook": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "k8s": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "volcengine": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - case "byteplus": - childComponent = ( - { - setOpen(false); - }} - /> - ); - break; - } - - return ( - { - if (openState) { - document.body.style.pointerEvents = "auto"; - } - setOpen(openState); - }} - open={open} - modal={false} - > - - {trigger} - - { - event.preventDefault(); - }} - > - - - { - { - ["add"]: t("access.action.add"), - ["edit"]: t("access.action.edit"), - ["copy"]: t("access.action.copy"), - }[op] - } - - - -
-
- - { - setConfigType(val); - }} - /> -
- -
{childComponent}
-
-
-
-
- ); -}; - -export default AccessEditDialog; diff --git a/ui/src/components/certimate/AccessGodaddyForm.tsx b/ui/src/components/certimate/AccessGodaddyForm.tsx deleted file mode 100644 index a56c98b8..00000000 --- a/ui/src/components/certimate/AccessGodaddyForm.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type GoDaddyAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessGodaddyFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - apiKey: z - .string() - .min(1, "access.form.godaddy_api_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - apiSecret: z - .string() - .min(1, "access.form.godaddy_api_secret.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: GoDaddyAccessConfig = { - apiKey: "", - apiSecret: "", - }; - if (data) config = data.config as GoDaddyAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "godaddy", - apiKey: config.apiKey, - apiSecret: config.apiSecret, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - apiKey: data.apiKey, - apiSecret: data.apiSecret, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.godaddy_api_key.label")} - - - - - - - )} - /> - - ( - - {t("access.form.godaddy_api_secret.label")} - - - - - - - )} - /> - -
- -
- - - - ); -}; - -export default AccessGodaddyForm; diff --git a/ui/src/components/certimate/AccessHttpreqForm.tsx b/ui/src/components/certimate/AccessHttpreqForm.tsx deleted file mode 100644 index ac8c7fac..00000000 --- a/ui/src/components/certimate/AccessHttpreqForm.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type ACMEHttpReqAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessHttpreqFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - endpoint: z.string().url("common.errmsg.url_invalid"), - mode: z.enum(["RAW", ""]), - username: z - .string() - .min(1, "access.form.access_key_secret.placeholder") - .max(128, t("common.errmsg.string_max", { max: 128 })), - password: z - .string() - .min(1, "access.form.access_key_secret.placeholder") - .max(128, t("common.errmsg.string_max", { max: 128 })), - }); - - let config: ACMEHttpReqAccessConfig = { - endpoint: "", - mode: "", - username: "", - password: "", - }; - if (data) config = data.config as ACMEHttpReqAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "acmehttpreq", - endpoint: config.endpoint, - mode: config.mode === "RAW" ? "RAW" : "", - username: config.username, - password: config.password, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - endpoint: data.endpoint, - mode: data.mode, - username: data.username, - password: data.password, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.httpreq_endpoint.label")} - - - - - - - )} - /> - - ( - - {t("access.form.httpreq_mode.label")} - - - - - - - )} - /> - - ( - - {t("access.form.username.label")} - - - - - - - )} - /> - - ( - - {t("access.form.password.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessHttpreqForm; diff --git a/ui/src/components/certimate/AccessHuaweicloudForm.tsx b/ui/src/components/certimate/AccessHuaweicloudForm.tsx deleted file mode 100644 index 00167d75..00000000 --- a/ui/src/components/certimate/AccessHuaweicloudForm.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HuaweiCloudAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessHuaweiCloudFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - region: z - .string() - .min(1, "access.form.region.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - accessKeyId: z - .string() - .min(1, "access.form.access_key_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretAccessKey: z - .string() - .min(1, "access.form.secret_access_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: HuaweiCloudAccessConfig = { - region: "cn-north-1", - accessKeyId: "", - secretAccessKey: "", - }; - if (data) config = data.config as HuaweiCloudAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "huaweicloud", - region: config.region, - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - region: data.region, - accessKeyId: data.accessKeyId, - secretAccessKey: data.secretAccessKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.region.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_access_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessHuaweiCloudForm; diff --git a/ui/src/components/certimate/AccessKubernetesForm.tsx b/ui/src/components/certimate/AccessKubernetesForm.tsx deleted file mode 100644 index 14db9997..00000000 --- a/ui/src/components/certimate/AccessKubernetesForm.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { readFileContent } from "@/utils/file"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type KubernetesAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessKubernetesFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - - const fileInputRef = useRef(null); - const [fileName, setFileName] = useState(""); - - const { t } = useTranslation(); - - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - kubeConfig: z - .string() - .min(0, "access.form.k8s_kubeconfig.placeholder") - .max(20480, t("common.errmsg.string_max", { max: 20480 })), - kubeConfigFile: z.any().optional(), - }); - - let config: KubernetesAccessConfig & { kubeConfigFile?: string } = { - kubeConfig: "", - kubeConfigFile: "", - }; - if (data) config = data.config as typeof config; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "k8s", - kubeConfig: config.kubeConfig, - kubeConfigFile: config.kubeConfigFile, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - kubeConfig: data.kubeConfig, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - } else { - createAccess(req); - } - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - const handleFileChange = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) return; - const savedFile = file; - setFileName(savedFile.name); - const content = await readFileContent(savedFile); - form.setValue("kubeConfig", content); - }; - - const handleSelectFileClick = () => { - fileInputRef.current?.click(); - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - )} - /> - - ( - - {t("access.form.k8s_kubeconfig.label")} - -
- - -
-
- - -
- )} - /> - - - -
- -
- - - - ); -}; - -export default AccessKubernetesForm; diff --git a/ui/src/components/certimate/AccessLocalForm.tsx b/ui/src/components/certimate/AccessLocalForm.tsx deleted file mode 100644 index 83a70f17..00000000 --- a/ui/src/components/certimate/AccessLocalForm.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessLocalFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessLocalForm = ({ data, op, onAfterReq }: AccessLocalFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "local", - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - - config: {}, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - } else { - createAccess(req); - } - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessLocalForm; diff --git a/ui/src/components/certimate/AccessNamesiloForm.tsx b/ui/src/components/certimate/AccessNamesiloForm.tsx deleted file mode 100644 index cf47dd70..00000000 --- a/ui/src/components/certimate/AccessNamesiloForm.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type NameSiloAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessNameSiloFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessNameSiloForm = ({ data, op, onAfterReq }: AccessNameSiloFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - apiKey: z - .string() - .min(1, "access.form.namesilo_api_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: NameSiloAccessConfig = { - apiKey: "", - }; - if (data) config = data.config as NameSiloAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "namesilo", - apiKey: config.apiKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - apiKey: data.apiKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.namesilo_api_key.label")} - - - - - - - )} - /> - -
- -
- - - - ); -}; - -export default AccessNameSiloForm; diff --git a/ui/src/components/certimate/AccessPdnsForm.tsx b/ui/src/components/certimate/AccessPdnsForm.tsx deleted file mode 100644 index e49c8271..00000000 --- a/ui/src/components/certimate/AccessPdnsForm.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type PowerDNSAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessPdnsFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessPdnsForm = ({ data, op, onAfterReq }: AccessPdnsFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - apiUrl: z.string().url("common.errmsg.url_invalid"), - apiKey: z - .string() - .min(1, "access.form.access_key_secret.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: PowerDNSAccessConfig = { - apiUrl: "", - apiKey: "", - }; - if (data) config = data.config as PowerDNSAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "powerdns", - apiUrl: config.apiUrl, - apiKey: config.apiKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - apiUrl: data.apiUrl, - apiKey: data.apiKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.pdns_api_url.label")} - - - - - - - )} - /> - - ( - - {t("access.form.pdns_api_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessPdnsForm; diff --git a/ui/src/components/certimate/AccessQiniuForm.tsx b/ui/src/components/certimate/AccessQiniuForm.tsx deleted file mode 100644 index 512cdb99..00000000 --- a/ui/src/components/certimate/AccessQiniuForm.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type QiniuAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessQiniuFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessQiniuForm = ({ data, op, onAfterReq }: AccessQiniuFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKey: z.string().min(1, "access.form.access_key.placeholder").max(64), - secretKey: z.string().min(1, "access.form.secret_key.placeholder").max(64), - }); - - let config: QiniuAccessConfig = { - accessKey: "", - secretKey: "", - }; - if (data) config = data.config as QiniuAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "qiniu", - accessKey: config.accessKey, - secretKey: config.secretKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKey: data.accessKey, - secretKey: data.secretKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessQiniuForm; diff --git a/ui/src/components/certimate/AccessSSHForm.tsx b/ui/src/components/certimate/AccessSSHForm.tsx deleted file mode 100644 index 3c1790ec..00000000 --- a/ui/src/components/certimate/AccessSSHForm.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import { useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { readFileContent } from "@/utils/file"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type SSHAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessSSHFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - - const fileInputRef = useRef(null); - - const [fileName, setFileName] = useState(""); - const { t } = useTranslation(); - - const domainReg = /^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/; - const ipReg = - /^(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 formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - host: z.string().refine( - (str) => { - return ipReg.test(str) || domainReg.test(str); - }, - { - message: "common.errmsg.host_invalid", - } - ), - group: z.string().optional(), - port: z - .string() - .min(1, "access.form.ssh_port.placeholder") - .max(5, t("common.errmsg.string_max", { max: 5 })), - 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 })), - key: z - .string() - .min(0, "access.form.ssh_key.placeholder") - .max(20480, t("common.errmsg.string_max", { max: 20480 })), - keyFile: z.any().optional(), - keyPassphrase: z - .string() - .min(0, "access.form.ssh_key_passphrase.placeholder") - .max(2048, t("common.errmsg.string_max", { max: 2048 })), - }); - - let config: SSHAccessConfig = { - host: "127.0.0.1", - port: "22", - username: "root", - password: "", - key: "", - keyFile: "", - keyPassphrase: "", - }; - if (data) config = data.config as SSHAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "ssh", - group: data?.group, - host: config.host, - port: config.port, - username: config.username, - password: config.password, - key: config.key, - keyFile: config.keyFile, - keyPassphrase: config.keyPassphrase, - }, - }); - - const onSubmit = async (data: z.infer) => { - let group = data.group; - if (group == "emptyId") group = ""; - - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - group: group, - config: { - host: data.host, - port: data.port, - username: data.username, - password: data.password, - key: data.key, - keyPassphrase: data.keyPassphrase, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - } else { - createAccess(req); - } - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - const handleFileChange = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) return; - const savedFile = file; - setFileName(savedFile.name); - const content = await readFileContent(savedFile); - form.setValue("key", content); - }; - - const handleSelectFileClick = () => { - fileInputRef.current?.click(); - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> -
- ( - - {t("access.form.ssh_host.label")} - - - - - - - )} - /> - - ( - - {t("access.form.ssh_port.label")} - - - - - - - )} - /> -
- - ( - - {t("access.form.ssh_username.label")} - - - - - - - )} - /> - - ( - - {t("access.form.ssh_password.label")} - - - - - - - )} - /> - - ( - - )} - /> - - ( - - {t("access.form.ssh_key.label")} - -
- - -
-
- - -
- )} - /> - - ( - - {t("access.form.ssh_key_passphrase.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessSSHForm; diff --git a/ui/src/components/certimate/AccessTencentForm.tsx b/ui/src/components/certimate/AccessTencentForm.tsx deleted file mode 100644 index 5eab43b4..00000000 --- a/ui/src/components/certimate/AccessTencentForm.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type TencentCloudAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessTencentFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessTencentForm = ({ data, op, onAfterReq }: AccessTencentFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - secretId: z - .string() - .min(1, "access.form.secret_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretKey: z - .string() - .min(1, "access.form.secret_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: TencentCloudAccessConfig = { - secretId: "", - secretKey: "", - }; - if (data) config = data.config as TencentCloudAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "tencentcloud", - secretId: config.secretId, - secretKey: config.secretKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - secretId: data.secretId, - secretKey: data.secretKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_key.label")} - - - - - - - )} - /> - -
- -
- - - - ); -}; - -export default AccessTencentForm; diff --git a/ui/src/components/certimate/AccessVolcengineForm.tsx b/ui/src/components/certimate/AccessVolcengineForm.tsx deleted file mode 100644 index f9c8894e..00000000 --- a/ui/src/components/certimate/AccessVolcengineForm.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Input } from "@/components/ui/input"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Button } from "@/components/ui/button"; -import { PbErrorData } from "@/domain/base"; -import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type VolcEngineAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessVolcengineFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - accessKeyId: z - .string() - .min(1, "access.form.access_key_id.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - secretAccessKey: z - .string() - .min(1, "access.form.secret_access_key.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - }); - - let config: VolcEngineAccessConfig = { - accessKeyId: "", - secretAccessKey: "", - }; - if (data) config = data.config as VolcEngineAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "volcengine", - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - accessKeyId: data.accessKeyId, - secretAccessKey: data.secretAccessKey, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - - return; - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.access_key_id.label")} - - - - - - - )} - /> - - ( - - {t("access.form.secret_access_key.label")} - - - - - - - )} - /> - - - -
- -
- - - - ); -}; - -export default AccessVolcengineForm; diff --git a/ui/src/components/certimate/AccessWebhookForm.tsx b/ui/src/components/certimate/AccessWebhookForm.tsx deleted file mode 100644 index d7a79f59..00000000 --- a/ui/src/components/certimate/AccessWebhookForm.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import z from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ClientResponseError } from "pocketbase"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { PbErrorData } from "@/domain/base"; -import { AccessModel, accessProvidersMap, accessTypeFormSchema, WebhookAccessConfig } from "@/domain/access"; -import { save } from "@/repository/access"; -import { useAccessStore } from "@/stores/access"; - -type AccessWebhookFormProps = { - op: "add" | "edit" | "copy"; - data?: AccessModel; - onAfterReq: () => void; -}; - -const AccessWebhookForm = ({ data, op, onAfterReq }: AccessWebhookFormProps) => { - const { createAccess, updateAccess } = useAccessStore(); - const { t } = useTranslation(); - const formSchema = z.object({ - id: z.string().optional(), - name: z - .string() - .min(1, "access.form.name.placeholder") - .max(64, t("common.errmsg.string_max", { max: 64 })), - configType: accessTypeFormSchema, - url: z.string().url("common.errmsg.url_invalid"), - }); - - let config: WebhookAccessConfig = { - url: "", - }; - if (data) config = data.config as WebhookAccessConfig; - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - id: data?.id, - name: data?.name || "", - configType: "webhook", - url: config.url, - }, - }); - - const onSubmit = async (data: z.infer) => { - const req: AccessModel = { - id: data.id as string, - name: data.name, - configType: data.configType, - usage: accessProvidersMap.get(data.configType)!.usage, - config: { - url: data.url, - }, - }; - - try { - req.id = op == "copy" ? "" : req.id; - const rs = await save(req); - - onAfterReq(); - - req.id = rs.id; - req.created = rs.created; - req.updated = rs.updated; - if (data.id && op == "edit") { - updateAccess(req); - return; - } - createAccess(req); - } catch (e) { - const err = e as ClientResponseError; - - Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => { - form.setError(key as keyof z.infer, { - type: "manual", - message: value.message, - }); - }); - } - }; - - return ( - <> -
- { - e.stopPropagation(); - form.handleSubmit(onSubmit)(e); - }} - className="space-y-8" - > - ( - - {t("access.form.name.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.config.label")} - - - - - - - )} - /> - - ( - - {t("access.form.webhook_url.label")} - - - - - - - )} - /> - -
- -
- - - - ); -}; - -export default AccessWebhookForm; diff --git a/ui/src/components/ui/popover.tsx b/ui/src/components/ui/popover.tsx deleted file mode 100644 index 3f188b41..00000000 --- a/ui/src/components/ui/popover.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from "react"; -import * as PopoverPrimitive from "@radix-ui/react-popover"; - -import { cn } from "./utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverContent = React.forwardRef, React.ComponentPropsWithoutRef>( - ({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - - ) -); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverTrigger, PopoverContent }; diff --git a/ui/src/components/workflow/AccessSelect.tsx b/ui/src/components/workflow/AccessSelect.tsx index 8ced0156..827c82ad 100644 --- a/ui/src/components/workflow/AccessSelect.tsx +++ b/ui/src/components/workflow/AccessSelect.tsx @@ -13,7 +13,11 @@ type AccessSelectProps = { const AccessSelect = ({ value, onValueChange, providerType }: AccessSelectProps) => { const [localValue, setLocalValue] = React.useState(""); const { t } = useTranslation(); - const { accesses } = useAccessStore(); + const { accesses, fetchAccesses } = useAccessStore(); + + useEffect(() => { + fetchAccesses(); + }, []); useEffect(() => { setLocalValue(value); diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index fab3cf61..94864129 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -1,4 +1,3 @@ -import { z } from "zod"; import { type BaseModel } from "pocketbase"; /* @@ -44,7 +43,7 @@ export const ACCESS_PROVIDER_TYPES = Object.freeze({ WEBHOOK: ACCESS_PROVIDER_TYPE_WEBHOOK, } as const); -export interface AccessModel extends Omit { +export interface AccessModel extends BaseModel { name: string; configType: string; usage: AccessUsages; @@ -202,27 +201,3 @@ export const accessProvidersMap: Map = n [ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"], ].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessUsages }]) ); - -export const accessTypeFormSchema = z.union( - [ - z.literal("aliyun"), - z.literal("tencentcloud"), - z.literal("huaweicloud"), - z.literal("baiducloud"), - z.literal("qiniu"), - z.literal("dogecloud"), - z.literal("aws"), - z.literal("cloudflare"), - z.literal("namesilo"), - z.literal("godaddy"), - z.literal("powerdns"), - z.literal("acmehttpreq"), - z.literal("local"), - z.literal("ssh"), - z.literal("webhook"), - z.literal("k8s"), - z.literal("volcengine"), - z.literal("byteplus"), - ], - { message: "access.form.type.placeholder" } -); diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index a43ec3ac..573e98c2 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -135,33 +135,5 @@ "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.placeholder": "Please enter SSH Key passphrase", - "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH.", - "access.form.region.label": "Region", - "access.form.region.placeholder": "Please enter Region", - "access.form.access_key_id.label": "AccessKeyId", - "access.form.access_key_id.placeholder": "Please enter AccessKeyId", - "access.form.access_key_secret.label": "AccessKeySecret", - "access.form.access_key_secret.placeholder": "Please enter AccessKeySecret", - "access.form.access_key.label": "AccessKey", - "access.form.access_key.placeholder": "Please enter AccessKey", - "access.form.secret_id.label": "SecretId", - "access.form.secret_id.placeholder": "Please enter SecretId", - "access.form.secret_key.label": "SecretKey", - "access.form.secret_key.placeholder": "Please enter SecretKey", - "access.form.secret_access_key.label": "SecretAccessKey", - "access.form.secret_access_key.placeholder": "Please enter SecretAccessKey", - "access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", - "access.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN", - "access.form.pdns_api_url.label": "PDNS_API_URL", - "access.form.pdns_api_url.placeholder": "Please enter PDNS_API_URL", - "access.form.pdns_api_key.label": "PDNS_API_KEY", - "access.form.pdns_api_key.placeholder": "Please enter PDNS_API_KEY", - "access.form.httpreq_endpoint.label": "HTTPREQ_ENDPOINT", - "access.form.httpreq_endpoint.placeholder": "Please enter HTTPREQ_ENDPOINT", - "access.form.httpreq_mode.label": "HTTPREQ_MODE", - "access.form.httpreq_mode.placeholder": "Please enter HTTPREQ_MODE(RAW or '')", - "access.form.username.label": "Username", - "access.form.username.placeholder": "Please enter username", - "access.form.password.label": "Password", - "access.form.password.placeholder": "Please enter password" + "access.form.ssh_key_passphrase.tooltip": "Optional when using key to connect to SSH." } diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index bf4969e1..843e0e9d 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -135,33 +135,5 @@ "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 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.region.label": "Region", - "access.form.region.placeholder": "请输入区域", - "access.form.access_key_id.label": "AccessKeyId", - "access.form.access_key_id.placeholder": "请输入 AccessKeyId", - "access.form.access_key_secret.label": "AccessKeySecret", - "access.form.access_key_secret.placeholder": "请输入 AccessKeySecret", - "access.form.access_key.label": "AccessKey", - "access.form.access_key.placeholder": "请输入 AccessKey", - "access.form.secret_id.label": "SecretId", - "access.form.secret_id.placeholder": "请输入 SecretId", - "access.form.secret_key.label": "SecretKey", - "access.form.secret_key.placeholder": "请输入 SecretKey", - "access.form.secret_access_key.label": "SecretAccessKey", - "access.form.secret_access_key.placeholder": "请输入 SecretAccessKey", - "access.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN", - "access.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN", - "access.form.pdns_api_url.label": "PDNS_API_URL", - "access.form.pdns_api_url.placeholder": "请输入 PDNS_API_URL", - "access.form.pdns_api_key.label": "PDNS_API_KEY", - "access.form.pdns_api_key.placeholder": "请输入 PDNS_API_KEY", - "access.form.httpreq_endpoint.label": "HTTP 请求端点", - "access.form.httpreq_endpoint.placeholder": "请输入 请求端点", - "access.form.httpreq_mode.label": "模式", - "access.form.httpreq_mode.placeholder": "请输入模式( RAW or '')", - "access.form.username.label": "用户名", - "access.form.username.placeholder": "请输入用户名", - "access.form.password.label": "密码", - "access.form.password.placeholder": "请输入密码" + "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。" } diff --git a/ui/src/repository/access.ts b/ui/src/repository/access.ts index 2d114a45..6f0f7d1c 100644 --- a/ui/src/repository/access.ts +++ b/ui/src/repository/access.ts @@ -12,7 +12,7 @@ export const list = async () => { }); }; -export const save = async (record: Partial) => { +export const save = async (record: AccessModel | Omit) => { if (record.id) { return await getPocketBase().collection(COLLECTION_NAME).update(record.id, record); } @@ -20,7 +20,7 @@ export const save = async (record: Partial) => { return await getPocketBase().collection(COLLECTION_NAME).create(record); }; -export const remove = async (record: Partial) => { +export const remove = async (record: AccessModel) => { record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") }; - await getPocketBase().collection(COLLECTION_NAME).update(record.id, record); + await getPocketBase().collection(COLLECTION_NAME).update(record.id!, record); }; From 1cad816b173b1e2833b3912ea0cfff990ca3903f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 17 Dec 2024 19:15:05 +0800 Subject: [PATCH 07/39] fix: render error when notify template is empty --- ui/src/components/notify/NotifyTemplate.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/notify/NotifyTemplate.tsx b/ui/src/components/notify/NotifyTemplate.tsx index 1ca6bd63..03d3a433 100644 --- a/ui/src/components/notify/NotifyTemplate.tsx +++ b/ui/src/components/notify/NotifyTemplate.tsx @@ -28,7 +28,7 @@ const NotifyTemplate = () => { }, []); const handleTitleChange = (val: string) => { - const template = templates[0]; + const template = templates?.[0] ?? {}; setTemplates([ { @@ -39,7 +39,7 @@ const NotifyTemplate = () => { }; const handleContentChange = (val: string) => { - const template = templates[0]; + const template = templates?.[0] ?? {}; setTemplates([ { @@ -71,7 +71,7 @@ const NotifyTemplate = () => { return (
{ handleTitleChange(e.target.value); }} @@ -81,7 +81,7 @@ const NotifyTemplate = () => { -
{t("settings.notification.template.variables.tips.content")}
-
- -
-
- ); -}; - -export default NotifyTemplate; diff --git a/ui/src/components/notify/ServerChan.tsx b/ui/src/components/notify/ServerChan.tsx deleted file mode 100644 index cb2ff1a6..00000000 --- a/ui/src/components/notify/ServerChan.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { useToast } from "@/components/ui/use-toast"; -import { getErrMsg } from "@/utils/error"; -import { isValidURL } from "@/utils/url"; -import { NotifyChannels, NotifyChannelServerChan } from "@/domain/settings"; -import { save } from "@/repository/settings"; -import { useNotifyContext } from "@/providers/notify"; -import { notifyTest } from "@/api/notify"; -import Show from "@/components/Show"; - -type ServerChanSetting = { - id: string; - name: string; - data: NotifyChannelServerChan; -}; - -const ServerChan = () => { - const { config, setChannels } = useNotifyContext(); - const { t } = useTranslation(); - const [changed, setChanged] = useState(false); - - const [serverchan, setServerChan] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - url: "", - enabled: false, - }, - }); - - const [originServerChan, setOriginServerChan] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - url: "", - enabled: false, - }, - }); - - useEffect(() => { - setChanged(false); - }, [config]); - - useEffect(() => { - const data = getDetailServerChan(); - setOriginServerChan({ - id: config.id ?? "", - name: "serverchan", - data, - }); - }, [config]); - - useEffect(() => { - const data = getDetailServerChan(); - setServerChan({ - id: config.id ?? "", - name: "serverchan", - data, - }); - }, [config]); - - const { toast } = useToast(); - - const checkChanged = (data: NotifyChannelServerChan) => { - if (data.url !== originServerChan.data.url) { - setChanged(true); - } else { - setChanged(false); - } - }; - - const getDetailServerChan = () => { - const df: NotifyChannelServerChan = { - url: "", - enabled: false, - }; - if (!config.content) { - return df; - } - const chanels = config.content as NotifyChannels; - if (!chanels.serverchan) { - return df; - } - - return chanels.serverchan as NotifyChannelServerChan; - }; - - const handleSaveClick = async () => { - try { - serverchan.data.url = serverchan.data.url.trim(); - if (!isValidURL(serverchan.data.url)) { - toast({ - title: t("common.text.operation_failed"), - description: t("common.errmsg.url_invalid"), - variant: "destructive", - }); - return; - } - - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - serverchan: { - ...serverchan.data, - }, - }, - }); - - setChannels(resp); - toast({ - title: t("common.text.operation_succeeded"), - description: t("settings.notification.config.saved.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - const [testing, setTesting] = useState(false); - const handlePushTestClick = async () => { - if (testing) return; - - try { - setTesting(true); - - await notifyTest("serverchan"); - - toast({ - title: t("settings.notification.push_test_message.succeeded.message"), - description: t("settings.notification.push_test_message.succeeded.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("settings.notification.push_test_message.failed.message"), - description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, - variant: "destructive", - }); - } finally { - setTesting(false); - } - }; - - const handleSwitchChange = async () => { - const newData = { - ...serverchan, - data: { - ...serverchan.data, - enabled: !serverchan.data.enabled, - }, - }; - setServerChan(newData); - - try { - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - serverchan: { - ...newData.data, - }, - }, - }); - - setChannels(resp); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - return ( -
-
- - { - const newData = { - ...serverchan, - data: { - ...serverchan.data, - url: e.target.value, - }, - }; - - checkChanged(newData.data); - setServerChan(newData); - }} - /> -
- -
-
- - -
- -
- - - - - - - -
-
-
- ); -}; - -export default ServerChan; diff --git a/ui/src/components/notify/Telegram.tsx b/ui/src/components/notify/Telegram.tsx deleted file mode 100644 index febf0905..00000000 --- a/ui/src/components/notify/Telegram.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { useToast } from "@/components/ui/use-toast"; -import { getErrMsg } from "@/utils/error"; -import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings"; -import { save } from "@/repository/settings"; -import { useNotifyContext } from "@/providers/notify"; -import { notifyTest } from "@/api/notify"; -import Show from "@/components/Show"; - -type TelegramSetting = { - id: string; - name: string; - data: NotifyChannelTelegram; -}; - -const Telegram = () => { - const { config, setChannels } = useNotifyContext(); - const { t } = useTranslation(); - - const [changed, setChanged] = useState(false); - - const [telegram, setTelegram] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - apiToken: "", - chatId: "", - enabled: false, - }, - }); - - const [originTelegram, setOriginTelegram] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - apiToken: "", - chatId: "", - enabled: false, - }, - }); - - useEffect(() => { - setChanged(false); - }, [config]); - - useEffect(() => { - const data = getDetailTelegram(); - setOriginTelegram({ - id: config.id ?? "", - name: "common.notifier.telegram", - data, - }); - }, [config]); - - useEffect(() => { - const data = getDetailTelegram(); - setTelegram({ - id: config.id ?? "", - name: "common.notifier.telegram", - data, - }); - }, [config]); - - const { toast } = useToast(); - - const checkChanged = (data: NotifyChannelTelegram) => { - if (data.apiToken !== originTelegram.data.apiToken || data.chatId !== originTelegram.data.chatId) { - setChanged(true); - } else { - setChanged(false); - } - }; - - const getDetailTelegram = () => { - const df: NotifyChannelTelegram = { - apiToken: "", - chatId: "", - enabled: false, - }; - if (!config.content) { - return df; - } - const chanels = config.content as NotifyChannels; - if (!chanels.telegram) { - return df; - } - - return chanels.telegram as NotifyChannelTelegram; - }; - - const handleSaveClick = async () => { - try { - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - telegram: { - ...telegram.data, - }, - }, - }); - - setChannels(resp); - toast({ - title: t("common.text.operation_succeeded"), - description: t("settings.notification.config.saved.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - const [testing, setTesting] = useState(false); - const handlePushTestClick = async () => { - if (testing) return; - - try { - setTesting(true); - - await notifyTest("telegram"); - - toast({ - title: t("settings.notification.push_test_message.succeeded.message"), - description: t("settings.notification.push_test_message.succeeded.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("settings.notification.push_test_message.failed.message"), - description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, - variant: "destructive", - }); - } finally { - setTesting(false); - } - }; - - const handleSwitchChange = async () => { - const newData = { - ...telegram, - data: { - ...telegram.data, - enabled: !telegram.data.enabled, - }, - }; - setTelegram(newData); - - try { - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - telegram: { - ...newData.data, - }, - }, - }); - - setChannels(resp); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - return ( -
-
- - { - const newData = { - ...telegram, - data: { - ...telegram.data, - apiToken: e.target.value, - }, - }; - - checkChanged(newData.data); - setTelegram(newData); - }} - /> -
- -
- - { - const newData = { - ...telegram, - data: { - ...telegram.data, - chatId: e.target.value, - }, - }; - - checkChanged(newData.data); - setTelegram(newData); - }} - /> -
- -
-
- - -
- -
- - - - - - - -
-
-
- ); -}; - -export default Telegram; diff --git a/ui/src/components/notify/Webhook.tsx b/ui/src/components/notify/Webhook.tsx deleted file mode 100644 index 13145f21..00000000 --- a/ui/src/components/notify/Webhook.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { useToast } from "@/components/ui/use-toast"; -import { getErrMsg } from "@/utils/error"; -import { isValidURL } from "@/utils/url"; -import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings"; -import { save } from "@/repository/settings"; -import { useNotifyContext } from "@/providers/notify"; -import { notifyTest } from "@/api/notify"; -import Show from "@/components/Show"; - -type WebhookSetting = { - id: string; - name: string; - data: NotifyChannelWebhook; -}; - -const Webhook = () => { - const { config, setChannels } = useNotifyContext(); - const { t } = useTranslation(); - const [changed, setChanged] = useState(false); - - const [webhook, setWebhook] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - url: "", - enabled: false, - }, - }); - - const [originWebhook, setOriginWebhook] = useState({ - id: config.id ?? "", - name: "notifyChannels", - data: { - url: "", - enabled: false, - }, - }); - - useEffect(() => { - setChanged(false); - }, [config]); - - useEffect(() => { - const data = getDetailWebhook(); - setOriginWebhook({ - id: config.id ?? "", - name: "webhook", - data, - }); - }, [config]); - - useEffect(() => { - const data = getDetailWebhook(); - setWebhook({ - id: config.id ?? "", - name: "webhook", - data, - }); - }, [config]); - - const { toast } = useToast(); - - const checkChanged = (data: NotifyChannelWebhook) => { - if (data.url !== originWebhook.data.url) { - setChanged(true); - } else { - setChanged(false); - } - }; - - const getDetailWebhook = () => { - const df: NotifyChannelWebhook = { - url: "", - enabled: false, - }; - if (!config.content) { - return df; - } - const chanels = config.content as NotifyChannels; - if (!chanels.webhook) { - return df; - } - - return chanels.webhook as NotifyChannelWebhook; - }; - - const handleSaveClick = async () => { - try { - webhook.data.url = webhook.data.url.trim(); - if (!isValidURL(webhook.data.url)) { - toast({ - title: t("common.text.operation_failed"), - description: t("common.errmsg.url_invalid"), - variant: "destructive", - }); - return; - } - - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - webhook: { - ...webhook.data, - }, - }, - }); - - setChannels(resp); - toast({ - title: t("common.text.operation_succeeded"), - description: t("settings.notification.config.saved.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - const [testing, setTesting] = useState(false); - const handlePushTestClick = async () => { - if (testing) return; - - try { - setTesting(true); - - await notifyTest("webhook"); - - toast({ - title: t("settings.notification.push_test_message.succeeded.message"), - description: t("settings.notification.push_test_message.succeeded.message"), - }); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("settings.notification.push_test_message.failed.message"), - description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`, - variant: "destructive", - }); - } finally { - setTesting(false); - } - }; - - const handleSwitchChange = async () => { - const newData = { - ...webhook, - data: { - ...webhook.data, - enabled: !webhook.data.enabled, - }, - }; - setWebhook(newData); - - try { - const resp = await save({ - ...config, - name: "notifyChannels", - content: { - ...config.content, - webhook: { - ...newData.data, - }, - }, - }); - - setChannels(resp); - } catch (e) { - const msg = getErrMsg(e); - - toast({ - title: t("common.text.operation_failed"), - description: `${t("settings.notification.config.failed.message")}: ${msg}`, - variant: "destructive", - }); - } - }; - - return ( -
-
- - { - const newData = { - ...webhook, - data: { - ...webhook.data, - url: e.target.value, - }, - }; - - checkChanged(newData.data); - setWebhook(newData); - }} - /> -
- -
-
- - -
- -
- - - - - - - -
-
-
- ); -}; - -export default Webhook; diff --git a/ui/src/components/workflow/DeployToLocal.tsx b/ui/src/components/workflow/DeployToLocal.tsx index 8b133e8f..ef9bfc77 100644 --- a/ui/src/components/workflow/DeployToLocal.tsx +++ b/ui/src/components/workflow/DeployToLocal.tsx @@ -41,13 +41,14 @@ const formSchema = z keyPath: z .string() .min(0, t("domain.deployment.form.file_key_path.placeholder")) - .max(255, t("common.errmsg.string_max", { max: 255 })), - pfxPassword: z.string().optional(), - jksAlias: z.string().optional(), - jksKeypass: z.string().optional(), - jksStorepass: z.string().optional(), - preCommand: z.string().optional(), - command: z.string().optional(), + .max(255, t("common.errmsg.string_max", { max: 255 })) + .nullish(), + pfxPassword: z.string().nullish(), + jksAlias: z.string().nullish(), + jksKeypass: z.string().nullish(), + jksStorepass: z.string().nullish(), + preCommand: z.string().nullish(), + command: z.string().nullish(), shell: z.union([z.literal("sh"), z.literal("cmd"), z.literal("powershell")], { message: t("domain.deployment.form.shell.placeholder"), }), diff --git a/ui/src/components/workflow/DeployToSSH.tsx b/ui/src/components/workflow/DeployToSSH.tsx index ee2c3279..968395cb 100644 --- a/ui/src/components/workflow/DeployToSSH.tsx +++ b/ui/src/components/workflow/DeployToSSH.tsx @@ -40,13 +40,14 @@ const formSchema = z keyPath: z .string() .min(0, t("domain.deployment.form.file_key_path.placeholder")) - .max(255, t("common.errmsg.string_max", { max: 255 })), - pfxPassword: z.string().optional(), - jksAlias: z.string().optional(), - jksKeypass: z.string().optional(), - jksStorepass: z.string().optional(), - preCommand: z.string().optional(), - command: z.string().optional(), + .max(255, t("common.errmsg.string_max", { max: 255 })) + .nullish(), + pfxPassword: z.string().nullish(), + jksAlias: z.string().nullish(), + jksKeypass: z.string().nullish(), + jksStorepass: z.string().nullish(), + preCommand: z.string().nullish(), + command: z.string().nullish(), }) .refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), { message: t("domain.deployment.form.file_key_path.placeholder"), diff --git a/ui/src/components/workflow/Node.tsx b/ui/src/components/workflow/Node.tsx index 4e5995a6..0496d8fa 100644 --- a/ui/src/components/workflow/Node.tsx +++ b/ui/src/components/workflow/Node.tsx @@ -9,7 +9,7 @@ import PanelBody from "./PanelBody"; import { useTranslation } from "react-i18next"; import Show from "../Show"; import { deployTargetsMap } from "@/domain/domain"; -import { channelLabelMap } from "@/domain/settings"; +import { notifyChannelsMap } from "@/domain/settings"; type NodeProps = { data: WorkflowNode; @@ -69,10 +69,10 @@ const Node = ({ data }: NodeProps) => { ); } case WorkflowNodeType.Notify: { - const channelLabel = channelLabelMap.get(data.config?.channel as string); + const channelLabel = notifyChannelsMap.get(data.config?.channel as string); return (
-
{t(channelLabel?.label ?? "")}
+
{t(channelLabel?.name ?? "")}
{(data.config?.title as string) ?? ""}
); diff --git a/ui/src/components/workflow/NotifyForm.tsx b/ui/src/components/workflow/NotifyForm.tsx index ea48cc3c..a632ddb2 100644 --- a/ui/src/components/workflow/NotifyForm.tsx +++ b/ui/src/components/workflow/NotifyForm.tsx @@ -10,9 +10,9 @@ import { useShallow } from "zustand/shallow"; import { usePanel } from "./PanelProvider"; import { useTranslation } from "react-i18next"; import { Button } from "../ui/button"; -import { useNotifyContext } from "@/providers/notify"; +import { useNotifyChannelStore } from "@/stores/notify"; import { useEffect, useState } from "react"; -import { NotifyChannels, channels as supportedChannels } from "@/domain/settings"; +import { notifyChannelsMap } from "@/domain/settings"; import { SelectValue } from "@radix-ui/react-select"; import { Textarea } from "../ui/textarea"; import { RefreshCw, Settings } from "lucide-react"; @@ -25,7 +25,7 @@ const selectState = (state: WorkflowState) => ({ updateNode: state.updateNode, }); type ChannelName = { - name: string; + key: string; label: string; }; @@ -34,28 +34,23 @@ const NotifyForm = ({ data }: NotifyFormProps) => { const { updateNode } = useWorkflowStore(useShallow(selectState)); const { hidePanel } = usePanel(); const { t } = useTranslation(); - const { config: notifyConfig, initChannels } = useNotifyContext(); + const { channels: supportedChannels, fetchChannels } = useNotifyChannelStore(); - const [chanels, setChanels] = useState([]); + const [channels, setChannels] = useState([]); useEffect(() => { - setChanels(getChannels()); - }, [notifyConfig]); + fetchChannels(); + }, [fetchChannels]); - const getChannels = () => { + useEffect(() => { const rs: ChannelName[] = []; - if (!notifyConfig.content) { - return rs; - } - - const chanels = notifyConfig.content as NotifyChannels; - for (const channel of supportedChannels) { - if (chanels[channel.name] && chanels[channel.name].enabled) { - rs.push(channel); + for (const channel of notifyChannelsMap.values()) { + if (supportedChannels[channel.type]?.enabled) { + rs.push({ key: channel.type, label: channel.name }); } } - return rs; - }; + setChannels(rs); + }, [supportedChannels]); const formSchema = z.object({ channel: z.string(), @@ -103,10 +98,10 @@ const NotifyForm = ({ data }: NotifyFormProps) => {
{t(`${i18nPrefix}.channel.label`)}
- initChannels()} /> + fetchChannels()} />
@@ -126,8 +121,8 @@ const NotifyForm = ({ data }: NotifyFormProps) => { - {chanels.map((item) => ( - + {channels.map((item) => ( +
{t(item.label)}
))} diff --git a/ui/src/components/workflow/WorkflowProvider.tsx b/ui/src/components/workflow/WorkflowProvider.tsx index 5e260348..1a44d266 100644 --- a/ui/src/components/workflow/WorkflowProvider.tsx +++ b/ui/src/components/workflow/WorkflowProvider.tsx @@ -1,14 +1,9 @@ import React from "react"; -import { NotifyProvider } from "@/providers/notify"; import { PanelProvider } from "./PanelProvider"; const WorkflowProvider = ({ children }: { children: React.ReactNode }) => { - return ( - - {children} - - ); + return {children}; }; export default WorkflowProvider; diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index c78ad13a..e6ee60bc 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -1,71 +1,64 @@ +export const SETTINGS_NAME_EMAILS = "emails" as const; +export const SETTINGS_NAME_NOTIFYTEMPLATES = "notifyTemplates" as const; +export const SETTINGS_NAME_NOTIFYCHANNELS = "notifyChannels" as const; +export const SETTINGS_NAME_SSLPROVIDER = "sslProvider" as const; +export const SETTINGS_NAMES = Object.freeze({ + EMAILS: SETTINGS_NAME_EMAILS, + NOTIFY_TEMPLATES: SETTINGS_NAME_NOTIFYTEMPLATES, + NOTIFY_CHANNELS: SETTINGS_NAME_NOTIFYCHANNELS, + SSL_PROVIDER: SETTINGS_NAME_SSLPROVIDER, +} as const); + export interface SettingsModel extends BaseModel { name: string; content: T; } +// #region Settings: Emails export type EmailsSettingsContent = { emails: string[]; }; +// #endregion -export type NotifyTemplates = { +// #region Settings: NotifyTemplates +export type NotifyTemplatesSettingsContent = { notifyTemplates: NotifyTemplate[]; }; export type NotifyTemplate = { - title: string; - content: string; + subject: string; + message: string; }; -export type NotifyChannels = { - [key: string]: NotifyChannel; +export const defaultNotifyTemplate: NotifyTemplate = { + subject: "您有 {COUNT} 张证书即将过期", + message: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!", +}; +// #endregion + +// #region Settings: NotifyChannels +export type NotifyChannelsSettingsContent = { + /* + 注意:如果追加新的类型,请保持以 ASCII 排序。 + NOTICE: If you add new type, please keep ASCII order. + */ + [key: string]: ({ enabled?: boolean } & Record) | undefined; + bark?: BarkNotifyChannelConfig; + dingtalk?: DingTalkNotifyChannelConfig; + email?: EmailNotifyChannelConfig; + lark?: LarkNotifyChannelConfig; + serverchan?: ServerChanNotifyChannelConfig; + telegram?: TelegramNotifyChannelConfig; + webhook?: WebhookNotifyChannelConfig; }; -export type NotifyChannel = - | NotifyChannelEmail - | NotifyChannelWebhook - | NotifyChannelDingTalk - | NotifyChannelLark - | NotifyChannelTelegram - | NotifyChannelServerChan - | NotifyChannelBark; - -type ChannelLabel = { - name: string; - label: string; +export type BarkNotifyChannelConfig = { + deviceKey: string; + serverUrl: string; + enabled?: boolean; }; -export const channels: ChannelLabel[] = [ - { - name: "dingtalk", - label: "common.notifier.dingtalk", - }, - { - name: "lark", - label: "common.notifier.lark", - }, - { - name: "telegram", - label: "common.notifier.telegram", - }, - { - name: "webhook", - label: "common.notifier.webhook", - }, - { - name: "serverchan", - label: "common.notifier.serverchan", - }, - { - name: "email", - label: "common.notifier.email", - }, - { - name: "bark", - label: "common.notifier.bark", - }, -]; -export const channelLabelMap: Map = new Map(channels.map((item) => [item.name, item])); -export type NotifyChannelEmail = { +export type EmailNotifyChannelConfig = { smtpHost: string; smtpPort: number; smtpTLS: boolean; @@ -73,47 +66,55 @@ export type NotifyChannelEmail = { password: string; senderAddress: string; receiverAddress: string; - enabled: boolean; + enabled?: boolean; }; -export type NotifyChannelWebhook = { - url: string; - enabled: boolean; -}; - -export type NotifyChannelDingTalk = { +export type DingTalkNotifyChannelConfig = { accessToken: string; secret: string; - enabled: boolean; + enabled?: boolean; }; -export type NotifyChannelLark = { +export type LarkNotifyChannelConfig = { webhookUrl: string; - enabled: boolean; + enabled?: boolean; }; -export type NotifyChannelTelegram = { +export type ServerChanNotifyChannelConfig = { + url: string; + enabled?: boolean; +}; + +export type TelegramNotifyChannelConfig = { apiToken: string; chatId: string; - enabled: boolean; + enabled?: boolean; }; -export type NotifyChannelServerChan = { +export type WebhookNotifyChannelConfig = { url: string; - enabled: boolean; + enabled?: boolean; }; -export type NotifyChannelBark = { - deviceKey: string; - serverUrl: string; - enabled: boolean; +export type NotifyChannel = { + type: string; + name: string; }; -export const defaultNotifyTemplate: NotifyTemplate = { - title: "您有 {COUNT} 张证书即将过期", - content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!", -}; +export const notifyChannelsMap: Map = new Map( + [ + ["email", "common.notifier.email"], + ["dingtalk", "common.notifier.dingtalk"], + ["lark", "common.notifier.lark"], + ["telegram", "common.notifier.telegram"], + ["serverchan", "common.notifier.serverchan"], + ["bark", "common.notifier.bark"], + ["webhook", "common.notifier.webhook"], + ].map(([type, name]) => [type, { type, name }]) +); +// #endregion +// #region Settings: SSLProvider export type SSLProvider = "letsencrypt" | "zerossl" | "gts"; export type SSLProviderSetting = { @@ -124,3 +125,4 @@ export type SSLProviderSetting = { }; }; }; +// #endregion diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index bb299557..3f02a3ae 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -16,47 +16,58 @@ "settings.password.form.password.errmsg.not_matched": "Passwords do not match", "settings.notification.tab": "Notification", - "settings.notification.template.label": "Template", - "settings.notification.template.saved.message": "Notification template saved successfully", - "settings.notification.template.variables.tips.title": "Optional variables ({COUNT}: number of expiring soon)", - "settings.notification.template.variables.tips.content": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)", - "settings.notification.config.enable": "Enable", - "settings.notification.config.saved.message": "Configuration saved successfully", - "settings.notification.config.failed.message": "Configuration save failed", - "settings.notification.push_test_message": "Send test notification", - "settings.notification.push_test_message.succeeded.message": "Send test notification successfully", - "settings.notification.push_test_message.failed.message": "Send test notification failed", - "settings.notification.email.smtp_host.label": "SMTP Host", - "settings.notification.email.smtp_host.placeholder": "Please enter SMTP host", - "settings.notification.email.smtp_port.label": "SMTP Port", - "settings.notification.email.smtp_port.placeholder": "Please enter SMTP port", - "settings.notification.email.smtp_tls.label": "Use TLS/SSL", - "settings.notification.email.username.label": "Username", - "settings.notification.email.username.placeholder": "please enter username", - "settings.notification.email.password.label": "Password", - "settings.notification.email.password.placeholder": "please enter password", - "settings.notification.email.sender_address.label": "Sender Email Address", - "settings.notification.email.sender_address.placeholder": "Please enter sender email address", - "settings.notification.email.receiver_address.label": "Receiver Email Address", - "settings.notification.email.receiver_address.placeholder": "Please enter receiver email address", - "settings.notification.webhook.url.label": "Webhook URL", - "settings.notification.webhook.url.placeholder": "Please enter Webhook URL", - "settings.notification.dingtalk.access_token.label": "AccessToken", - "settings.notification.dingtalk.access_token.placeholder": "Please enter access token", - "settings.notification.dingtalk.secret.label": "Secret", - "settings.notification.dingtalk.secret.placeholder": "Please enter secret", - "settings.notification.lark.webhook_url.label": "Webhook URL", - "settings.notification.lark.webhook_url.placeholder": "Please enter Webhook URL", - "settings.notification.telegram.api_token.label": "API Token", - "settings.notification.telegram.api_token.placeholder": "Please enter API token", - "settings.notification.telegram.chat_id.label": "Chat ID", - "settings.notification.telegram.chat_id.placeholder": "Please enter Telegram chat ID", - "settings.notification.serverchan.url.label": "Server URL", - "settings.notification.serverchan.url.placeholder": "Please enter server URL (e.g. https://sctapi.ftqq.com/*****.send)", - "settings.notification.bark.server_url.label": "Server URL", - "settings.notification.bark.server_url.placeholder": "Please enter server URL (e.g. https://your-bark-server.com. Leave it blank to use the bark default server)", - "settings.notification.bark.device_key.label": "Device Key", - "settings.notification.bark.device_key.placeholder": "Please enter device key", + "settings.notification.template.card.title": "Template", + "settings.notification.template.form.subject.label": "Subject", + "settings.notification.template.form.subject.placeholder": "Please enter notification subject", + "settings.notification.template.form.subject.tooltip": "Optional variables ({COUNT}: number of expiring soon)", + "settings.notification.template.form.message.label": "Message", + "settings.notification.template.form.message.placeholder": "Please enter notification message", + "settings.notification.template.form.message.tooltip": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)", + "settings.notification.channels.card.title": "Channels", + "settings.notification.channel.enabled.on": "On", + "settings.notification.channel.enabled.off": "Off", + "settings.notification.push_test.button": "Send Test Notification", + "settings.notification.push_test.tooltip": "Note: Please save settings before testing push.", + "settings.notification.push_test.pushed": "Sent", + "settings.notification.channel.form.bark_server_url.label": "Server URL", + "settings.notification.channel.form.bark_server_url.placeholder": "Please enter server URL", + "settings.notification.channel.form.bark_server_url.tooltip": "For more information, see
https://bark.day.app/

Leave blank to use the default Bark server.", + "settings.notification.channel.form.bark_device_key.label": "Device Key", + "settings.notification.channel.form.bark_device_key.placeholder": "Please enter device key", + "settings.notification.channel.form.bark_device_key.tooltip": "For more information, see https://bark.day.app/", + "settings.notification.channel.form.dingtalk_access_token.label": "Robot AccessToken", + "settings.notification.channel.form.dingtalk_access_token.placeholder": "Please enter Robot Access Token", + "settings.notification.channel.form.dingtalk_access_token.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_secret.label": "Robot Secret", + "settings.notification.channel.form.dingtalk_secret.placeholder": "Please enter Robot Secret", + "settings.notification.channel.form.dingtalk_secret.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.email_smtp_host.label": "SMTP Host", + "settings.notification.channel.form.email_smtp_host.placeholder": "Please enter SMTP host", + "settings.notification.channel.form.email_smtp_port.label": "SMTP Port", + "settings.notification.channel.form.email_smtp_port.placeholder": "Please enter SMTP port", + "settings.notification.channel.form.email_smtp_tls.label": "Use TLS/SSL", + "settings.notification.channel.form.email_username.label": "Username", + "settings.notification.channel.form.email_username.placeholder": "please enter username", + "settings.notification.channel.form.email_password.label": "Password", + "settings.notification.channel.form.email_password.placeholder": "please enter password", + "settings.notification.channel.form.email_sender_address.label": "Sender Email Address", + "settings.notification.channel.form.email_sender_address.placeholder": "Please enter sender email address", + "settings.notification.channel.form.email_receiver_address.label": "Receiver Email Address", + "settings.notification.channel.form.email_receiver_address.placeholder": "Please enter receiver email address", + "settings.notification.channel.form.lark_webhook_url.label": "Webhook URL", + "settings.notification.channel.form.lark_webhook_url.placeholder": "Please enter Webhook URL", + "settings.notification.channel.form.lark_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756", + "settings.notification.channel.form.serverchan_url.label": "Server URL", + "settings.notification.channel.form.serverchan_url.placeholder": "Please enter ServerChan server URL (e.g. https://sctapi.ftqq.com/*****.send)", + "settings.notification.channel.form.serverchan_url.tooltip": "For more information, see https://sct.ftqq.com/forward", + "settings.notification.channel.form.telegram_api_token.label": "Bot API Token", + "settings.notification.channel.form.telegram_api_token.placeholder": "Please enter bot API token", + "settings.notification.channel.form.telegram_api_token.tooltip": "For more information, see https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "settings.notification.channel.form.telegram_chat_id.label": "Chat ID", + "settings.notification.channel.form.telegram_chat_id.placeholder": "Please enter chat ID", + "settings.notification.channel.form.telegram_chat_id.tooltip": "For more information, see https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "settings.notification.channel.form.webhook_url.label": "Webhook URL", + "settings.notification.channel.form.webhook_url.placeholder": "Please enter Webhook URL", "settings.ca.tab": "Certificate Authority", "settings.ca.provider.errmsg.empty": "Please select a Certificate Authority", diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index e2d9f67f..3ae5f6dc 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -68,5 +68,5 @@ "workflow.node.notify.form.content.placeholder": "Please enter content", "workflow.node.notify.form.channel.label": "Channel", "workflow.node.notify.form.channel.placeholder": "Please select channel", - "workflow.node.notify.form.settingChannel.label": "Setting Channel" + "workflow.node.notify.form.settingChannel.label": "Configure Channels" } diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 02ab610a..ddbff40e 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -16,47 +16,58 @@ "settings.password.form.password.errmsg.not_matched": "两次密码不一致", "settings.notification.tab": "消息推送", - "settings.notification.template.label": "内容模板", - "settings.notification.template.saved.message": "通知模板保存成功", - "settings.notification.template.variables.tips.title": "可选的变量({COUNT}: 即将过期张数)", - "settings.notification.template.variables.tips.content": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)", - "settings.notification.config.enable": "是否启用", - "settings.notification.config.saved.message": "配置保存成功", - "settings.notification.config.failed.message": "配置保存失败", - "settings.notification.push_test_message": "推送测试消息", - "settings.notification.push_test_message.failed.message": "推送测试消息失败", - "settings.notification.push_test_message.succeeded.message": "推送测试消息成功", - "settings.notification.email.smtp_host.label": "SMTP 服务器地址", - "settings.notification.email.smtp_host.placeholder": "请输入 SMTP 服务器地址", - "settings.notification.email.smtp_port.label": "SMTP 服务器端口", - "settings.notification.email.smtp_port.placeholder": "请输入 SMTP 服务器端口", - "settings.notification.email.smtp_tls.label": "TLS/SSL 连接", - "settings.notification.email.username.label": "用户名", - "settings.notification.email.username.placeholder": "请输入用户名", - "settings.notification.email.password.label": "密码", - "settings.notification.email.password.placeholder": "请输入密码", - "settings.notification.email.sender_address.label": "发送邮箱地址", - "settings.notification.email.sender_address.placeholder": "请输入发送邮箱地址", - "settings.notification.email.receiver_address.label": "接收邮箱地址", - "settings.notification.email.receiver_address.placeholder": "请输入接收邮箱地址", - "settings.notification.webhook.url.label": "Webhook 回调地址", - "settings.notification.webhook.url.placeholder": "请输入 Webhook 回调地址", - "settings.notification.dingtalk.access_token.label": "AccessToken", - "settings.notification.dingtalk.access_token.placeholder": "请输入 AccessToken", - "settings.notification.dingtalk.secret.label": "签名密钥", - "settings.notification.dingtalk.secret.placeholder": "请输入签名密钥", - "settings.notification.lark.webhook_url.label": "Webhook URL", - "settings.notification.lark.webhook_url.placeholder": "请输入 Webhook URL", - "settings.notification.telegram.api_token.label": "API Token", - "settings.notification.telegram.api_token.placeholder": "请输入 API token", - "settings.notification.telegram.chat_id.label": "会话 ID", - "settings.notification.telegram.chat_id.placeholder": "请输入 Telegram 会话 ID", - "settings.notification.serverchan.url.label": "服务器 URL", - "settings.notification.serverchan.url.placeholder": "请输入服务器 URL(形如: https://sctapi.ftqq.com/*****.send)", - "settings.notification.bark.server_url.label": "服务器 URL", - "settings.notification.bark.server_url.placeholder": "请输入服务器 URL(形如: https://your-bark-server.com;留空则使用 Bark 默认服务器)", - "settings.notification.bark.device_key.label": "设备密钥", - "settings.notification.bark.device_key.placeholder": "请输入设备密钥", + "settings.notification.template.card.title": "通知模板", + "settings.notification.template.form.subject.label": "通知主题", + "settings.notification.template.form.subject.placeholder": "请输入通知主题", + "settings.notification.template.form.subject.tooltip": "可选的变量({COUNT}: 即将过期张数)", + "settings.notification.template.form.message.label": "通知内容", + "settings.notification.template.form.message.placeholder": "请输入通知内容", + "settings.notification.template.form.message.tooltip": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)", + "settings.notification.channels.card.title": "通知渠道", + "settings.notification.channel.enabled.on": "启用", + "settings.notification.channel.enabled.off": "未启用", + "settings.notification.push_test.button": "推送测试消息", + "settings.notification.push_test.tooltip": "提示:修改后请先保存设置再测试推送。", + "settings.notification.push_test.pushed": "已推送", + "settings.notification.channel.form.bark_server_url.label": "服务器地址", + "settings.notification.channel.form.bark_server_url.placeholder": "请输入服务器地址", + "settings.notification.channel.form.bark_server_url.tooltip": "这是什么?请参阅 https://bark.day.app/

为空时,将使用 Bark 默认服务器。", + "settings.notification.channel.form.bark_device_key.label": "设备密钥", + "settings.notification.channel.form.bark_device_key.placeholder": "请输入设备密钥", + "settings.notification.channel.form.bark_device_key.tooltip": "这是什么?请参阅 https://bark.day.app/", + "settings.notification.channel.form.dingtalk_access_token.label": "机器人 AccessToken", + "settings.notification.channel.form.dingtalk_access_token.placeholder": "请输入机器人 AccessToken", + "settings.notification.channel.form.dingtalk_access_token.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_secret.label": "机器人加签密钥", + "settings.notification.channel.form.dingtalk_secret.placeholder": "请输入机器人加签密钥", + "settings.notification.channel.form.dingtalk_secret.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.email_smtp_host.label": "SMTP 服务器地址", + "settings.notification.channel.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址", + "settings.notification.channel.form.email_smtp_port.label": "SMTP 服务器端口", + "settings.notification.channel.form.email_smtp_port.placeholder": "请输入 SMTP 服务器端口", + "settings.notification.channel.form.email_smtp_tls.label": "TLS/SSL 连接", + "settings.notification.channel.form.email_username.label": "用户名", + "settings.notification.channel.form.email_username.placeholder": "请输入用户名", + "settings.notification.channel.form.email_password.label": "密码", + "settings.notification.channel.form.email_password.placeholder": "请输入密码", + "settings.notification.channel.form.email_sender_address.label": "发送邮箱地址", + "settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址", + "settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址", + "settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址", + "settings.notification.channel.form.lark_webhook_url.label": "Webhook 地址", + "settings.notification.channel.form.lark_webhook_url.placeholder": "请输入 Webhook 地址", + "settings.notification.channel.form.lark_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756", + "settings.notification.channel.form.serverchan_url.label": "服务器地址", + "settings.notification.channel.form.serverchan_url.placeholder": "请输入服务器地址(形如: https://sctapi.ftqq.com/*****.send)", + "settings.notification.channel.form.serverchan_url.tooltip": "这是什么?请参阅 https://sct.ftqq.com/forward", + "settings.notification.channel.form.telegram_api_token.label": "机器人 API Token", + "settings.notification.channel.form.telegram_api_token.placeholder": "请输入机器人 API token", + "settings.notification.channel.form.telegram_api_token.tooltip": "这是什么?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "settings.notification.channel.form.telegram_chat_id.label": "会话 ID", + "settings.notification.channel.form.telegram_chat_id.placeholder": "请输入会话 ID", + "settings.notification.channel.form.telegram_chat_id.tooltip": "这是什么?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "settings.notification.channel.form.webhook_url.label": "Webhook 回调地址", + "settings.notification.channel.form.webhook_url.placeholder": "请输入 Webhook 回调地址", "settings.ca.tab": "证书颁发机构(CA)", "settings.ca.provider.errmsg.empty": "请选择证书分发机构", diff --git a/ui/src/pages/settings/Notification.tsx b/ui/src/pages/settings/Notification.tsx deleted file mode 100644 index 02dd727f..00000000 --- a/ui/src/pages/settings/Notification.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import DingTalk from "@/components/notify/DingTalk"; -import Lark from "@/components/notify/Lark"; -import NotifyTemplate from "@/components/notify/NotifyTemplate"; -import Telegram from "@/components/notify/Telegram"; -import Webhook from "@/components/notify/Webhook"; -import ServerChan from "@/components/notify/ServerChan"; -import Email from "@/components/notify/Email"; -import Bark from "@/components/notify/Bark"; -import { NotifyProvider } from "@/providers/notify"; - -const Notification = () => { - const { t } = useTranslation(); - - return ( - <> - -
- - - {t("settings.notification.template.label")} - - - - - -
- -
- - - {t("common.notifier.email")} - - - - - - - {t("common.notifier.webhook")} - - - - - - - {t("common.notifier.dingtalk")} - - - - - - - {t("common.notifier.lark")} - - - - - - - {t("common.notifier.telegram")} - - - - - - - {t("common.notifier.serverchan")} - - - - - - - {t("common.notifier.bark")} - - - - - -
-
- - ); -}; - -export default Notification; diff --git a/ui/src/pages/settings/SSLProvider.tsx b/ui/src/pages/settings/SSLProvider.tsx index 34477613..baafadaa 100644 --- a/ui/src/pages/settings/SSLProvider.tsx +++ b/ui/src/pages/settings/SSLProvider.tsx @@ -3,6 +3,7 @@ import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; +import { produce } from "immer"; import { cn } from "@/components/ui/utils"; import { Button } from "@/components/ui/button"; @@ -11,10 +12,9 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { useToast } from "@/components/ui/use-toast"; -import { getErrMsg } from "@/utils/error"; -import { SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings"; +import { SETTINGS_NAMES, SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings"; import { get, save } from "@/repository/settings"; -import { produce } from "immer"; +import { getErrMsg } from "@/utils/error"; type SSLProviderContext = { setting: SettingsModel; @@ -28,6 +28,16 @@ export const useSSLProviderContext = () => { return useContext(Context); }; +const getConfigStr = (content: SSLProviderSetting, kind: string, key: string) => { + if (!content.config) { + return ""; + } + if (!content.config[kind]) { + return ""; + } + return content.config[kind][key] ?? ""; +}; + const SSLProvider = () => { const { t } = useTranslation(); @@ -42,7 +52,7 @@ const SSLProvider = () => { useEffect(() => { const fetchData = async () => { - const setting = await get("ssl-provider"); + const setting = await get(SETTINGS_NAMES.SSL_PROVIDER); if (setting) { setConfig(setting); @@ -95,7 +105,7 @@ const SSLProvider = () => { return ( <> -
+
{ case "zerossl": return ; case "gts": - return ; + return ; default: return ; } @@ -160,16 +170,6 @@ const SSLProviderForm = ({ kind }: { kind: string }) => { ); }; -const getConfigStr = (content: SSLProviderSetting, kind: string, key: string) => { - if (!content.config) { - return ""; - } - if (!content.config[kind]) { - return ""; - } - return content.config[kind][key] ?? ""; -}; - const SSLProviderLetsEncryptForm = () => { const { t } = useTranslation(); @@ -227,6 +227,7 @@ const SSLProviderLetsEncryptForm = () => { ); }; + const SSLProviderZeroSSLForm = () => { const { t } = useTranslation(); @@ -334,7 +335,7 @@ const SSLProviderZeroSSLForm = () => { ); }; -const SSLProviderGtsForm = () => { +const SSLProviderGoogleTrustServicesForm = () => { const { t } = useTranslation(); const { setting, onSubmit } = useSSLProviderContext(); diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 94939a68..9b70a454 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -5,8 +5,6 @@ import { Card, Space } from "antd"; import { PageHeader } from "@ant-design/pro-components"; import { KeyRound as KeyRoundIcon, Megaphone as MegaphoneIcon, ShieldCheck as ShieldCheckIcon, UserRound as UserRoundIcon } from "lucide-react"; -import { Toaster } from "@/components/ui/toaster"; - const Settings = () => { const location = useLocation(); const navigate = useNavigate(); @@ -73,7 +71,6 @@ const Settings = () => { navigate(`/settings/${key}`); }} > - diff --git a/ui/src/pages/settings/SettingsAccount.tsx b/ui/src/pages/settings/SettingsAccount.tsx index f2b7fb53..3a2a8ce0 100644 --- a/ui/src/pages/settings/SettingsAccount.tsx +++ b/ui/src/pages/settings/SettingsAccount.tsx @@ -47,7 +47,7 @@ const SettingsAccount = () => { navigate("/login"); }, 500); } catch (err) { - notificationApi.error({ message: t("common.text.request_error"), description: <>{getErrMsg(err)} }); + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } finally { setFormPending(false); } @@ -58,7 +58,7 @@ const SettingsAccount = () => { {MessageContextHolder} {NotificationContextHolder} -
+
diff --git a/ui/src/pages/settings/SettingsNotification.tsx b/ui/src/pages/settings/SettingsNotification.tsx new file mode 100644 index 00000000..555af3a1 --- /dev/null +++ b/ui/src/pages/settings/SettingsNotification.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from "react-i18next"; +import { Card, Divider } from "antd"; + +import NotifyChannels from "@/components/notification/NotifyChannels"; +import NotifyTemplate from "@/components/notification/NotifyTemplate"; +import { useNotifyChannelStore } from "@/stores/notify"; + +const SettingsNotification = () => { + const { t } = useTranslation(); + + const { initialized } = useNotifyChannelStore(); + + return ( +
+ +
+ +
+
+ + + + + + +
+ ); +}; + +export default SettingsNotification; diff --git a/ui/src/pages/settings/SettingsPassword.tsx b/ui/src/pages/settings/SettingsPassword.tsx index 01dd3a56..39dfa84b 100644 --- a/ui/src/pages/settings/SettingsPassword.tsx +++ b/ui/src/pages/settings/SettingsPassword.tsx @@ -60,7 +60,7 @@ const SettingsPassword = () => { navigate("/login"); }, 500); } catch (err) { - notificationApi.error({ message: t("common.text.request_error"), description: <>{getErrMsg(err)} }); + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } finally { setFormPending(false); } @@ -71,7 +71,7 @@ const SettingsPassword = () => { {MessageContextHolder} {NotificationContextHolder} -
+
diff --git a/ui/src/providers/notify/index.tsx b/ui/src/providers/notify/index.tsx deleted file mode 100644 index f90b6b63..00000000 --- a/ui/src/providers/notify/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { ReactNode, useContext, createContext, useEffect, useReducer, useCallback } from "react"; - -import { NotifyChannel, NotifyChannels, SettingsModel } from "@/domain/settings"; -import { get } from "@/repository/settings"; -import { notifyReducer } from "./reducer"; - -export type NotifyContext = { - config: SettingsModel; - setChannel: (data: { channel: string; data: NotifyChannel }) => void; - setChannels: (data: SettingsModel) => void; - initChannels: () => void; -}; - -const Context = createContext({} as NotifyContext); - -export const useNotifyContext = () => useContext(Context); - -interface NotifyProviderProps { - children: ReactNode; -} - -export const NotifyProvider = ({ children }: NotifyProviderProps) => { - const [notify, dispatchNotify] = useReducer(notifyReducer, {} as SettingsModel); - - useEffect(() => { - featchData(); - }, []); - - const featchData = async () => { - const chanels = await get("notifyChannels"); - dispatchNotify({ - type: "SET_CHANNELS", - payload: chanels, - }); - }; - - const initChannels = useCallback(() => { - featchData(); - }, []); - - const setChannel = useCallback((data: { channel: string; data: NotifyChannel }) => { - dispatchNotify({ - type: "SET_CHANNEL", - payload: data, - }); - }, []); - - const setChannels = useCallback((setting: SettingsModel) => { - dispatchNotify({ - type: "SET_CHANNELS", - payload: setting, - }); - }, []); - - return ( - - {children} - - ); -}; diff --git a/ui/src/providers/notify/reducer.tsx b/ui/src/providers/notify/reducer.tsx deleted file mode 100644 index cea2092c..00000000 --- a/ui/src/providers/notify/reducer.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { NotifyChannel, NotifyChannels, SettingsModel } from "@/domain/settings"; - -type Action = - | { - type: "SET_CHANNEL"; - payload: { - channel: string; - data: NotifyChannel; - }; - } - | { - type: "SET_CHANNELS"; - payload: SettingsModel; - }; - -export const notifyReducer = (state: SettingsModel, action: Action) => { - switch (action.type) { - case "SET_CHANNEL": { - const channel = action.payload.channel; - return { - ...state, - content: { - ...state.content, - [channel]: action.payload.data, - }, - }; - } - case "SET_CHANNELS": { - return { ...action.payload }; - } - - default: - return state; - } -}; diff --git a/ui/src/repository/settings.ts b/ui/src/repository/settings.ts index 62772d2a..7bd2aec0 100644 --- a/ui/src/repository/settings.ts +++ b/ui/src/repository/settings.ts @@ -1,15 +1,23 @@ -import { type SettingsModel } from "@/domain/settings"; +import { ClientResponseError } from "pocketbase"; + +import { SETTINGS_NAMES, type SettingsModel } from "@/domain/settings"; import { getPocketBase } from "./pocketbase"; -export const get = async (name: string) => { +export const get = async (name: (typeof SETTINGS_NAMES)[keyof typeof SETTINGS_NAMES]) => { try { - const resp = await getPocketBase().collection("settings").getFirstListItem>(`name='${name}'`); + const resp = await getPocketBase().collection("settings").getFirstListItem>(`name='${name}'`, { + requestKey: null, + }); return resp; - } catch { - return { - name: name, - content: {} as T, - } as SettingsModel; + } catch (err) { + if (err instanceof ClientResponseError && err.status === 404) { + return { + name: name, + content: {} as T, + } as SettingsModel; + } + + throw err; } }; diff --git a/ui/src/router.tsx b/ui/src/router.tsx index 3dbcb36d..ef74a31d 100644 --- a/ui/src/router.tsx +++ b/ui/src/router.tsx @@ -11,8 +11,8 @@ import CertificateList from "./pages/certificates/CertificateList"; import Settings from "./pages/settings/Settings"; import SettingsAccount from "./pages/settings/SettingsAccount"; import SettingsPassword from "./pages/settings/SettingsPassword"; -import SettingsNotification from "./pages/settings/Notification"; -import SettingsSSLProvider from "./pages/settings/SSLProvider"; +import SettingsNotification from "./pages/settings/SettingsNotification"; +import SSLProvider from "./pages/settings/SSLProvider"; export const router = createHashRouter([ { @@ -57,7 +57,7 @@ export const router = createHashRouter([ }, { path: "/settings/ssl-provider", - element: , + element: , }, ], }, diff --git a/ui/src/stores/notify/index.ts b/ui/src/stores/notify/index.ts new file mode 100644 index 00000000..9a53b63c --- /dev/null +++ b/ui/src/stores/notify/index.ts @@ -0,0 +1,57 @@ +import { create } from "zustand"; +import { produce } from "immer"; + +import { SETTINGS_NAMES, type NotifyChannelsSettingsContent, type SettingsModel } from "@/domain/settings"; +import { get as getSettings, save as saveSettings } from "@/repository/settings"; + +export interface NotifyChannelState { + initialized: boolean; + channels: NotifyChannelsSettingsContent; + setChannel: (channel: keyof NotifyChannelsSettingsContent, config: NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent]) => void; + setChannels: (channels: NotifyChannelsSettingsContent) => void; + fetchChannels: () => Promise; +} + +export const useNotifyChannelStore = create((set, get) => { + let fetcher: Promise> | null = null; // 防止多次重复请求 + let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用 + + return { + initialized: false, + channels: {}, + + setChannel: async (channel, config) => { + settings ??= await getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS); + return get().setChannels({ + ...settings.content, + [channel]: { ...settings.content[channel], ...config }, + }); + }, + + setChannels: async (channels) => { + settings ??= await getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS); + settings = await saveSettings({ + ...settings, + content: channels, + }); + + set( + produce((state: NotifyChannelState) => { + state.channels = settings.content; + state.initialized = true; + }) + ); + }, + + fetchChannels: async () => { + fetcher ??= getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS); + + try { + settings = await fetcher; + set({ channels: settings.content ?? {}, initialized: true }); + } finally { + fetcher = null; + } + }, + }; +}); diff --git a/ui/src/utils/url.ts b/ui/src/utils/url.ts deleted file mode 100644 index a175f2d4..00000000 --- a/ui/src/utils/url.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function isValidURL(url: string): boolean { - try { - new URL(url); - return true; - } catch (error) { - return false; - } -} From 9e1e0dee1d58cfcdd59aaf0552ecc7e188e2675b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 20 Dec 2024 14:08:30 +0800 Subject: [PATCH 26/39] fix(ui): couldn't detect form changed in NotifyChannels --- .../notification/NotifyChannels.tsx | 97 ++++++++++--------- ui/src/i18n/locales/en/nls.settings.json | 1 - ui/src/i18n/locales/zh/nls.settings.json | 1 - 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/ui/src/components/notification/NotifyChannels.tsx b/ui/src/components/notification/NotifyChannels.tsx index b9a3a22a..25f4c0f3 100644 --- a/ui/src/components/notification/NotifyChannels.tsx +++ b/ui/src/components/notification/NotifyChannels.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useDeepCompareMemo } from "@ant-design/pro-components"; import { Button, Collapse, message, notification, Skeleton, Space, Switch, Tooltip, type CollapseProps } from "antd"; @@ -9,6 +9,55 @@ import { notifyChannelsMap } from "@/domain/settings"; import { useNotifyChannelStore } from "@/stores/notify"; import { getErrMsg } from "@/utils/error"; +type NotifyChannelProps = { + className?: string; + style?: React.CSSProperties; + channel: string; +}; + +const NotifyChannel = ({ className, style, channel }: NotifyChannelProps) => { + const { t } = useTranslation(); + + const [messageApi, MessageContextHolder] = message.useMessage(); + const [notificationApi, NotificationContextHolder] = notification.useNotification(); + + const { channels, setChannel } = useNotifyChannelStore(); + + const channelConfig = useDeepCompareMemo(() => channels[channel], [channels, channel]); + const [channelFormChanged, setChannelFormChanged] = useState(false); + const channelFormRef = useRef(null); + + const handleClickSubmit = async () => { + await channelFormRef.current!.validateFields(); + + try { + setChannel(channel, channelFormRef.current!.getFieldsValue()); + setChannelFormChanged(false); + + messageApi.success(t("common.text.operation_succeeded")); + } catch (err) { + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }; + + return ( +
+ {MessageContextHolder} + {NotificationContextHolder} + + setChannelFormChanged(true)} /> + + + + + {channelConfig != null ? : null} + +
+ ); +}; + type NotifyChannelsSemanticDOM = "collapse" | "form"; export type NotifyChannelsProps = { @@ -21,40 +70,18 @@ export type NotifyChannelsProps = { const NotifyChannels = ({ className, classNames, style, styles }: NotifyChannelsProps) => { const { t, i18n } = useTranslation(); - const [messageApi, MessageContextHolder] = message.useMessage(); - const [notificationApi, NotificationContextHolder] = notification.useNotification(); - const { initialized, channels, setChannel, fetchChannels } = useNotifyChannelStore(); useEffect(() => { fetchChannels(); }, [fetchChannels]); - const channelFormRefs = useRef>([]); const channelCollapseItems: CollapseProps["items"] = useDeepCompareMemo( () => - Array.from(notifyChannelsMap.values()).map((channel, index) => { + Array.from(notifyChannelsMap.values()).map((channel) => { return { key: `channel-${channel.type}`, label: <>{t(channel.name)}, - children: ( -
- (channelFormRefs.current[index] = ref)} model={channels[channel.type]} channel={channel.type} /> - - - - - {channels[channel.type] ? ( - - <> - - - - ) : null} - -
- ), + children: , extra: (
e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} onMouseUp={(e) => e.stopPropagation()}> { - const form = channelFormRefs.current[index]; - if (!form) { - return; - } - - await form.validateFields(); - - try { - setChannel(channel, form.getFieldsValue()); - - messageApi.success(t("common.text.operation_succeeded")); - } catch (err) { - notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }; - return (
- {MessageContextHolder} - {NotificationContextHolder} - {!initialized ? ( ) : ( diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 3f02a3ae..60e79051 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -27,7 +27,6 @@ "settings.notification.channel.enabled.on": "On", "settings.notification.channel.enabled.off": "Off", "settings.notification.push_test.button": "Send Test Notification", - "settings.notification.push_test.tooltip": "Note: Please save settings before testing push.", "settings.notification.push_test.pushed": "Sent", "settings.notification.channel.form.bark_server_url.label": "Server URL", "settings.notification.channel.form.bark_server_url.placeholder": "Please enter server URL", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index ddbff40e..276a90fa 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -27,7 +27,6 @@ "settings.notification.channel.enabled.on": "启用", "settings.notification.channel.enabled.off": "未启用", "settings.notification.push_test.button": "推送测试消息", - "settings.notification.push_test.tooltip": "提示:修改后请先保存设置再测试推送。", "settings.notification.push_test.pushed": "已推送", "settings.notification.channel.form.bark_server_url.label": "服务器地址", "settings.notification.channel.form.bark_server_url.placeholder": "请输入服务器地址", From a917d6c2c572e6c3bba7b985ec0c08ffccc998e0 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 20 Dec 2024 20:42:46 +0800 Subject: [PATCH 27/39] feat(ui): new SettingsSSLProvider using antd --- ui/public/imgs/{providers => acme}/google.svg | 2 +- .../imgs/{providers => acme}/letsencrypt.svg | 0 ui/public/imgs/acme/zerossl.svg | 1 + ui/public/imgs/providers/zerossl.svg | 1 - .../notification/NotifyChannels.tsx | 6 +- ui/src/domain/access.ts | 14 +- ui/src/domain/settings.ts | 38 +- ui/src/i18n/locales/en/nls.settings.json | 24 +- ui/src/i18n/locales/zh/nls.common.json | 2 +- ui/src/i18n/locales/zh/nls.settings.json | 24 +- ui/src/pages/settings/SSLProvider.tsx | 445 ------------------ ui/src/pages/settings/Settings.tsx | 2 +- ui/src/pages/settings/SettingsSSLProvider.tsx | 301 ++++++++++++ ui/src/repository/settings.ts | 6 +- ui/src/router.tsx | 4 +- ui/src/stores/notify/index.ts | 10 +- 16 files changed, 392 insertions(+), 488 deletions(-) rename ui/public/imgs/{providers => acme}/google.svg (99%) rename ui/public/imgs/{providers => acme}/letsencrypt.svg (100%) create mode 100644 ui/public/imgs/acme/zerossl.svg delete mode 100644 ui/public/imgs/providers/zerossl.svg delete mode 100644 ui/src/pages/settings/SSLProvider.tsx create mode 100644 ui/src/pages/settings/SettingsSSLProvider.tsx diff --git a/ui/public/imgs/providers/google.svg b/ui/public/imgs/acme/google.svg similarity index 99% rename from ui/public/imgs/providers/google.svg rename to ui/public/imgs/acme/google.svg index 120a7921..78f81c93 100644 --- a/ui/public/imgs/providers/google.svg +++ b/ui/public/imgs/acme/google.svg @@ -1 +1 @@ - + diff --git a/ui/public/imgs/providers/letsencrypt.svg b/ui/public/imgs/acme/letsencrypt.svg similarity index 100% rename from ui/public/imgs/providers/letsencrypt.svg rename to ui/public/imgs/acme/letsencrypt.svg diff --git a/ui/public/imgs/acme/zerossl.svg b/ui/public/imgs/acme/zerossl.svg new file mode 100644 index 00000000..e4c2ac63 --- /dev/null +++ b/ui/public/imgs/acme/zerossl.svg @@ -0,0 +1 @@ + diff --git a/ui/public/imgs/providers/zerossl.svg b/ui/public/imgs/providers/zerossl.svg deleted file mode 100644 index 8563aece..00000000 --- a/ui/public/imgs/providers/zerossl.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/src/components/notification/NotifyChannels.tsx b/ui/src/components/notification/NotifyChannels.tsx index 25f4c0f3..76d09817 100644 --- a/ui/src/components/notification/NotifyChannels.tsx +++ b/ui/src/components/notification/NotifyChannels.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useDeepCompareMemo } from "@ant-design/pro-components"; -import { Button, Collapse, message, notification, Skeleton, Space, Switch, Tooltip, type CollapseProps } from "antd"; +import { Button, Collapse, message, notification, Skeleton, Space, Switch, type CollapseProps } from "antd"; import NotifyChannelEditForm, { type NotifyChannelEditFormInstance } from "./NotifyChannelEditForm"; import NotifyTestButton from "./NotifyTestButton"; @@ -45,9 +45,9 @@ const NotifyChannel = ({ className, style, channel }: NotifyChannelProps) => { {MessageContextHolder} {NotificationContextHolder} - setChannelFormChanged(true)} /> + setChannelFormChanged(true)} /> - + diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 9419863b..8b5dc6b4 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -41,6 +41,8 @@ export const ACCESS_PROVIDER_TYPES = Object.freeze({ WEBHOOK: ACCESS_PROVIDER_TYPE_WEBHOOK, } as const); +export type AccessProviderTypes = (typeof ACCESS_PROVIDER_TYPES)[keyof typeof ACCESS_PROVIDER_TYPES]; + export const ACCESS_PROVIDER_USAGE_ALL = "all" as const; export const ACCESS_PROVIDER_USAGE_APPLY = "apply" as const; export const ACCESS_PROVIDER_USAGE_DEPLOY = "deploy" as const; @@ -50,11 +52,13 @@ export const ACCESS_PROVIDER_USAGES = Object.freeze({ DEPLOY: ACCESS_PROVIDER_USAGE_DEPLOY, } as const); +export type AccessProviderUsages = (typeof ACCESS_PROVIDER_USAGES)[keyof typeof ACCESS_PROVIDER_USAGES]; + // #region AccessModel export interface AccessModel extends BaseModel { name: string; configType: string; - usage: AccessUsages; + usage: AccessProviderUsages; config: /* 注意:如果追加新的类型,请保持以 ASCII 排序。 NOTICE: If you add new type, please keep ASCII order. @@ -136,7 +140,7 @@ export type KubernetesAccessConfig = { kubeConfig?: string; }; -export type LocalAccessConfig = never; +export type LocalAccessConfig = NonNullable; export type NameSiloAccessConfig = { apiKey: string; @@ -177,13 +181,11 @@ export type WebhookAccessConfig = { // #endregion // #region AccessProvider -export type AccessUsages = (typeof ACCESS_PROVIDER_USAGES)[keyof typeof ACCESS_PROVIDER_USAGES]; - export type AccessProvider = { type: string; name: string; icon: string; - usage: AccessUsages; + usage: AccessProviderUsages; }; export const accessProvidersMap: Map = new Map( @@ -210,6 +212,6 @@ export const accessProvidersMap: Map = n [ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"], [ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"], [ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"], - ].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessUsages }]) + ].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessProviderUsages }]) ); // #endregion diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index e6ee60bc..c280189f 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -9,7 +9,9 @@ export const SETTINGS_NAMES = Object.freeze({ SSL_PROVIDER: SETTINGS_NAME_SSLPROVIDER, } as const); -export interface SettingsModel extends BaseModel { +export type SettingsNames = (typeof SETTINGS_NAMES)[keyof typeof SETTINGS_NAMES]; + +export interface SettingsModel = NonNullable> extends BaseModel { name: string; content: T; } @@ -115,14 +117,36 @@ export const notifyChannelsMap: Map = new // #endregion // #region Settings: SSLProvider -export type SSLProvider = "letsencrypt" | "zerossl" | "gts"; +export const SSLPROVIDER_LETSENCRYPT = "letsencrypt" as const; +export const SSLPROVIDER_ZEROSSL = "zerossl" as const; +export const SSLPROVIDER_GOOGLETRUSTSERVICES = "gts" as const; +export const SSLPROVIDERS = Object.freeze({ + LETS_ENCRYPT: SSLPROVIDER_LETSENCRYPT, + ZERO_SSL: SSLPROVIDER_ZEROSSL, + GOOGLE_TRUST_SERVICES: SSLPROVIDER_GOOGLETRUSTSERVICES, +} as const); -export type SSLProviderSetting = { - provider: SSLProvider; +export type SSLProviders = (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS]; + +export type SSLProviderSettingsContent = { + provider: (typeof SSLPROVIDERS)[keyof typeof SSLPROVIDERS]; config: { - [key: string]: { - [key: string]: string; - }; + [key: string]: Record | undefined; + letsencrypt?: SSLProviderLetsEncryptConfig; + zerossl?: SSLProviderZeroSSLConfig; + gts?: SSLProviderGoogleTrustServicesConfig; }; }; + +export type SSLProviderLetsEncryptConfig = NonNullable; + +export type SSLProviderZeroSSLConfig = { + eabKid: string; + eabHmacKey: string; +}; + +export type SSLProviderGoogleTrustServicesConfig = { + eabKid: string; + eabHmacKey: string; +}; // #endregion diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 60e79051..742a2c31 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -36,10 +36,10 @@ "settings.notification.channel.form.bark_device_key.tooltip": "For more information, see https://bark.day.app/", "settings.notification.channel.form.dingtalk_access_token.label": "Robot AccessToken", "settings.notification.channel.form.dingtalk_access_token.placeholder": "Please enter Robot Access Token", - "settings.notification.channel.form.dingtalk_access_token.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_access_token.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot", "settings.notification.channel.form.dingtalk_secret.label": "Robot Secret", "settings.notification.channel.form.dingtalk_secret.placeholder": "Please enter Robot Secret", - "settings.notification.channel.form.dingtalk_secret.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_secret.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", "settings.notification.channel.form.email_smtp_host.label": "SMTP Host", "settings.notification.channel.form.email_smtp_host.placeholder": "Please enter SMTP host", "settings.notification.channel.form.email_smtp_port.label": "SMTP Port", @@ -68,9 +68,19 @@ "settings.notification.channel.form.webhook_url.label": "Webhook URL", "settings.notification.channel.form.webhook_url.placeholder": "Please enter Webhook URL", - "settings.ca.tab": "Certificate Authority", - "settings.ca.provider.errmsg.empty": "Please select a Certificate Authority", - "settings.ca.eab_kid.errmsg.empty": "Please enter EAB_KID", - "settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.", - "settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY" + "settings.sslprovider.tab": "Certificate Authority", + "settings.sslprovider.form.provider.label": "ACME Provider", + "settings.sslprovider.provider.errmsg.empty": "Please select a Certificate Authority", + "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", + "settings.sslprovider.form.zerossl_eab_kid.placeholder": "Please enter EAB KID", + "settings.sslprovider.form.zerossl_eab_kid.tooltip": "For more information, see https://zerossl.com/documentation/acme/", + "settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key", + "settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "Please enter EAB HMAC Key", + "settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "For more information, see https://zerossl.com/documentation/acme/", + "settings.sslprovider.form.gts_eab_kid.label": "EAB KID", + "settings.sslprovider.form.gts_eab_kid.placeholder": "Please enter EAB KID", + "settings.sslprovider.form.gts_eab_kid.tooltip": "For more information, see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", + "settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key", + "settings.sslprovider.form.gts_eab_hmac_key.placeholder": "Please enter EAB HMAC Key", + "settings.sslprovider.form.gts_eab_hmac_key.tooltip": "For more information, see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" } diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index edee22d5..e8636ee6 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -88,7 +88,7 @@ "common.notifier.dingtalk": "钉钉", "common.notifier.email": "电子邮件", "common.notifier.lark": "飞书", - "common.notifier.serverchan": "Server酱", + "common.notifier.serverchan": "Server 酱", "common.notifier.telegram": "Telegram", "common.notifier.webhook": "Webhook" } diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 276a90fa..9b33e0d2 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -36,10 +36,10 @@ "settings.notification.channel.form.bark_device_key.tooltip": "这是什么?请参阅 https://bark.day.app/", "settings.notification.channel.form.dingtalk_access_token.label": "机器人 AccessToken", "settings.notification.channel.form.dingtalk_access_token.placeholder": "请输入机器人 AccessToken", - "settings.notification.channel.form.dingtalk_access_token.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_access_token.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot", "settings.notification.channel.form.dingtalk_secret.label": "机器人加签密钥", "settings.notification.channel.form.dingtalk_secret.placeholder": "请输入机器人加签密钥", - "settings.notification.channel.form.dingtalk_secret.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/custom-bot-to-send-group-chat-messages", + "settings.notification.channel.form.dingtalk_secret.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", "settings.notification.channel.form.email_smtp_host.label": "SMTP 服务器地址", "settings.notification.channel.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址", "settings.notification.channel.form.email_smtp_port.label": "SMTP 服务器端口", @@ -68,9 +68,19 @@ "settings.notification.channel.form.webhook_url.label": "Webhook 回调地址", "settings.notification.channel.form.webhook_url.placeholder": "请输入 Webhook 回调地址", - "settings.ca.tab": "证书颁发机构(CA)", - "settings.ca.provider.errmsg.empty": "请选择证书分发机构", - "settings.ca.eab_kid.errmsg.empty": "请输入EAB_KID", - "settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY", - "settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY" + "settings.sslprovider.tab": "证书颁发机构(CA)", + "settings.sslprovider.form.provider.label": "ACME 提供商", + "settings.sslprovider.provider.errmsg.empty": "请选择证书分发机构", + "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", + "settings.sslprovider.form.zerossl_eab_kid.placeholder": "请输入 EAB KID", + "settings.sslprovider.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 https://zerossl.com/documentation/acme/", + "settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key", + "settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "请输入 EAB HMAC Key", + "settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 https://zerossl.com/documentation/acme/", + "settings.sslprovider.form.gts_eab_kid.label": "EAB KID", + "settings.sslprovider.form.gts_eab_kid.placeholder": "请输入 EAB KID", + "settings.sslprovider.form.gts_eab_kid.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", + "settings.sslprovider.form.gts_eab_hmac_key.label": "EAB HMAC Key", + "settings.sslprovider.form.gts_eab_hmac_key.placeholder": "请输入 EAB HMAC Key", + "settings.sslprovider.form.gts_eab_hmac_key.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" } diff --git a/ui/src/pages/settings/SSLProvider.tsx b/ui/src/pages/settings/SSLProvider.tsx deleted file mode 100644 index baafadaa..00000000 --- a/ui/src/pages/settings/SSLProvider.tsx +++ /dev/null @@ -1,445 +0,0 @@ -import { useContext, useEffect, useState, createContext } from "react"; -import { useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { produce } from "immer"; - -import { cn } from "@/components/ui/utils"; -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { useToast } from "@/components/ui/use-toast"; -import { SETTINGS_NAMES, SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings"; -import { get, save } from "@/repository/settings"; -import { getErrMsg } from "@/utils/error"; - -type SSLProviderContext = { - setting: SettingsModel; - onSubmit: (data: SettingsModel) => void; - setConfig: (config: SettingsModel) => void; -}; - -const Context = createContext({} as SSLProviderContext); - -export const useSSLProviderContext = () => { - return useContext(Context); -}; - -const getConfigStr = (content: SSLProviderSetting, kind: string, key: string) => { - if (!content.config) { - return ""; - } - if (!content.config[kind]) { - return ""; - } - return content.config[kind][key] ?? ""; -}; - -const SSLProvider = () => { - const { t } = useTranslation(); - - const [config, setConfig] = useState>({ - content: { - provider: "letsencrypt", - config: {}, - }, - } as SettingsModel); - - const { toast } = useToast(); - - useEffect(() => { - const fetchData = async () => { - const setting = await get(SETTINGS_NAMES.SSL_PROVIDER); - - if (setting) { - setConfig(setting); - } - }; - fetchData(); - }, []); - - const setProvider = (val: SSLProviderType) => { - const newData = produce(config, (draft) => { - if (draft.content) { - draft.content.provider = val; - } else { - draft.content = { - provider: val, - config: {}, - }; - } - }); - setConfig(newData); - }; - - const getOptionCls = (val: string) => { - if (config.content?.provider === val) { - return "border-primary dark:border-primary"; - } - - return ""; - }; - - const onSubmit = async (data: SettingsModel) => { - try { - console.log(data); - const resp = await save({ ...data }); - setConfig(resp); - toast({ - title: t("common.text.operation_succeeded"), - description: t("common.text.operation_succeeded"), - }); - } catch (e) { - const message = getErrMsg(e); - toast({ - title: t("common.text.operation_failed"), - description: message, - variant: "destructive", - }); - } - }; - - return ( - <> - -
- - { - setProvider(val as SSLProviderType); - }} - value={config.content?.provider} - > -
- - -
-
- - -
- -
- - -
-
- - -
-
- - ); -}; - -const SSLProviderForm = ({ kind }: { kind: string }) => { - const getForm = () => { - switch (kind) { - case "zerossl": - return ; - case "gts": - return ; - default: - return ; - } - }; - - return ( - <> -
{getForm()}
- - ); -}; - -const SSLProviderLetsEncryptForm = () => { - const { t } = useTranslation(); - - const { setting, onSubmit } = useSSLProviderContext(); - - const formSchema = z.object({ - kind: z.literal("letsencrypt"), - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - kind: "letsencrypt", - }, - }); - - const onLocalSubmit = async (data: z.infer) => { - const newData = produce(setting, (draft) => { - if (!draft.content) { - draft.content = { - provider: data.kind, - config: { - letsencrypt: {}, - }, - }; - } - }); - onSubmit(newData); - }; - - return ( - - - ( - - )} - /> - - - -
- -
- - - ); -}; - -const SSLProviderZeroSSLForm = () => { - const { t } = useTranslation(); - - const { setting, onSubmit } = useSSLProviderContext(); - - const formSchema = z.object({ - kind: z.literal("zerossl"), - eabKid: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }), - eabHmacKey: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }), - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - kind: "zerossl", - eabKid: "", - eabHmacKey: "", - }, - }); - - useEffect(() => { - if (setting.content) { - const content = setting.content; - - form.reset({ - eabKid: getConfigStr(content, "zerossl", "eabKid"), - eabHmacKey: getConfigStr(content, "zerossl", "eabHmacKey"), - }); - } - }, [setting]); - - const onLocalSubmit = async (data: z.infer) => { - const newData = produce(setting, (draft) => { - if (!draft.content) { - draft.content = { - provider: "zerossl", - config: { - zerossl: {}, - }, - }; - } - - draft.content.config.zerossl = { - eabKid: data.eabKid, - eabHmacKey: data.eabHmacKey, - }; - }); - onSubmit(newData); - }; - - return ( -
- - ( - - )} - /> - ( - - EAB_KID - - - - - - - )} - /> - - ( - - EAB_HMAC_KEY - - - - - - - )} - /> - - - -
- -
- - - ); -}; - -const SSLProviderGoogleTrustServicesForm = () => { - const { t } = useTranslation(); - - const { setting, onSubmit } = useSSLProviderContext(); - - const formSchema = z.object({ - kind: z.literal("gts"), - eabKid: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }), - eabHmacKey: z.string().min(1, { message: t("settings.ca.eab_kid_hmac_key.errmsg.empty") }), - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - kind: "gts", - eabKid: "", - eabHmacKey: "", - }, - }); - - useEffect(() => { - if (setting.content) { - const content = setting.content; - - form.reset({ - eabKid: getConfigStr(content, "gts", "eabKid"), - eabHmacKey: getConfigStr(content, "gts", "eabHmacKey"), - }); - } - }, [setting]); - - const onLocalSubmit = async (data: z.infer) => { - const newData = produce(setting, (draft) => { - if (!draft.content) { - draft.content = { - provider: "gts", - config: { - zerossl: {}, - }, - }; - } - - draft.content.config.gts = { - eabKid: data.eabKid, - eabHmacKey: data.eabHmacKey, - }; - }); - onSubmit(newData); - }; - - return ( -
- - ( - - )} - /> - ( - - EAB_KID - - - - - - - )} - /> - - ( - - EAB_HMAC_KEY - - - - - - - )} - /> - - - -
- -
- - - ); -}; - -export default SSLProvider; diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 9b70a454..530f8033 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -60,7 +60,7 @@ const Settings = () => { label: ( - + ), }, diff --git a/ui/src/pages/settings/SettingsSSLProvider.tsx b/ui/src/pages/settings/SettingsSSLProvider.tsx new file mode 100644 index 00000000..5d63e0b4 --- /dev/null +++ b/ui/src/pages/settings/SettingsSSLProvider.tsx @@ -0,0 +1,301 @@ +import { createContext, useContext, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, Form, Input, message, notification, Skeleton } from "antd"; +import { CheckCard } from "@ant-design/pro-components"; +import { createSchemaFieldRule } from "antd-zod"; +import { produce } from "immer"; +import { z } from "zod"; + +import { SETTINGS_NAMES, SSLPROVIDERS, type SettingsModel, type SSLProviderSettingsContent, type SSLProviders } from "@/domain/settings"; +import { get as getSettings, save as saveSettings } from "@/repository/settings"; +import { getErrMsg } from "@/utils/error"; +import { useDeepCompareEffect } from "ahooks"; + +const SSLProviderContext = createContext( + {} as { + pending: boolean; + settings: SettingsModel; + updateSettings: (settings: MaybeModelRecordWithId>) => Promise; + } +); + +const SSLProviderEditFormLetsEncryptConfig = () => { + const { t } = useTranslation(); + + const { pending, settings, updateSettings } = useContext(SSLProviderContext); + + const [form] = Form.useForm(); + + const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]); + const [initialChanged, setInitialChanged] = useState(false); + useDeepCompareEffect(() => { + setInitialValues(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]); + setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT); + }, [settings]); + + const handleFormChange = () => { + setInitialChanged(true); + }; + + const handleFormFinish = async (fields: NonNullable) => { + const newSettings = produce(settings, (draft) => { + draft.content ??= {} as SSLProviderSettingsContent; + draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT; + + draft.content.config ??= {} as SSLProviderSettingsContent["config"]; + draft.content.config[SSLPROVIDERS.LETS_ENCRYPT] = fields; + }); + await updateSettings(newSettings); + + setInitialChanged(false); + }; + + return ( +
+ + + +
+ ); +}; + +const SSLProviderEditFormZeroSSLConfig = () => { + const { t } = useTranslation(); + + const { pending, settings, updateSettings } = useContext(SSLProviderContext); + + const formSchema = z.object({ + eabKid: z + .string({ message: t("settings.sslprovider.form.zerossl_eab_kid.placeholder") }) + .min(1, t("settings.sslprovider.form.zerossl_eab_kid.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + eabHmacKey: z + .string({ message: t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder") }) + .min(1, t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + }); + const formRule = createSchemaFieldRule(formSchema); + const [form] = Form.useForm>(); + + const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]); + const [initialChanged, setInitialChanged] = useState(false); + useDeepCompareEffect(() => { + setInitialValues(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]); + setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.ZERO_SSL); + }, [settings]); + + const handleFormChange = () => { + setInitialChanged(true); + }; + + const handleFormFinish = async (fields: z.infer) => { + const newSettings = produce(settings, (draft) => { + draft.content ??= {} as SSLProviderSettingsContent; + draft.content.provider = SSLPROVIDERS.ZERO_SSL; + + draft.content.config ??= {} as SSLProviderSettingsContent["config"]; + draft.content.config[SSLPROVIDERS.ZERO_SSL] = fields; + }); + await updateSettings(newSettings); + + setInitialChanged(false); + }; + + return ( +
+ } + > + + + + } + > + + + + + + +
+ ); +}; + +const SSLProviderEditFormGoogleTrustServicesConfig = () => { + const { t } = useTranslation(); + + const { pending, settings, updateSettings } = useContext(SSLProviderContext); + + const formSchema = z.object({ + eabKid: z + .string({ message: t("settings.sslprovider.form.gts_eab_kid.placeholder") }) + .min(1, t("settings.sslprovider.form.gts_eab_kid.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + eabHmacKey: z + .string({ message: t("settings.sslprovider.form.gts_eab_hmac_key.placeholder") }) + .min(1, t("settings.sslprovider.form.gts_eab_hmac_key.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + }); + const formRule = createSchemaFieldRule(formSchema); + const [form] = Form.useForm>(); + + const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]); + const [initialChanged, setInitialChanged] = useState(false); + useDeepCompareEffect(() => { + setInitialValues(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]); + setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.GOOGLE_TRUST_SERVICES); + }, [settings]); + + const handleFormChange = () => { + setInitialChanged(true); + }; + + const handleFormFinish = async (fields: z.infer) => { + const newSettings = produce(settings, (draft) => { + draft.content ??= {} as SSLProviderSettingsContent; + draft.content.provider = SSLPROVIDERS.GOOGLE_TRUST_SERVICES; + + draft.content.config ??= {} as SSLProviderSettingsContent["config"]; + draft.content.config[SSLPROVIDERS.GOOGLE_TRUST_SERVICES] = fields; + }); + await updateSettings(newSettings); + + setInitialChanged(false); + }; + + return ( +
+ } + > + + + + } + > + + + + + + +
+ ); +}; + +const SettingsSSLProvider = () => { + const { t } = useTranslation(); + + const [messageApi, MessageContextHolder] = message.useMessage(); + const [notificationApi, NotificationContextHolder] = notification.useNotification(); + + const [form] = Form.useForm(); + const [formPending, setFormPending] = useState(false); + + const [settings, setSettings] = useState>(); + const [loading, setLoading] = useState(true); + useEffect(() => { + const fetchData = async () => { + setLoading(true); + + const settings = await getSettings(SETTINGS_NAMES.SSL_PROVIDER); + setSettings(settings); + setFormProviderType(settings.content?.provider); + + setLoading(false); + }; + + fetchData(); + }, []); + + const [providerType, setFormProviderType] = useState(); + const providerFormComponent = useMemo(() => { + switch (providerType) { + case SSLPROVIDERS.LETS_ENCRYPT: + return ; + case SSLPROVIDERS.ZERO_SSL: + return ; + case SSLPROVIDERS.GOOGLE_TRUST_SERVICES: + return ; + } + }, [providerType]); + + const updateContextSettings = async (settings: MaybeModelRecordWithId>) => { + setFormPending(true); + + try { + const resp = await saveSettings(settings); + setSettings(resp); + setFormProviderType(resp.content?.provider); + + messageApi.success(t("common.text.operation_succeeded")); + } catch (err) { + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } finally { + setFormPending(false); + } + }; + + return ( + + {MessageContextHolder} + {NotificationContextHolder} + + {loading ? ( + + ) : ( + <> +
+ + setFormProviderType(value as SSLProviders)}> + } + size="small" + title="Let's Encrypt" + value={SSLPROVIDERS.LETS_ENCRYPT} + /> + } size="small" title="ZeroSSL" value={SSLPROVIDERS.ZERO_SSL} /> + } + size="small" + title="Google Trust Services" + value={SSLPROVIDERS.GOOGLE_TRUST_SERVICES} + /> + + +
+ +
{providerFormComponent}
+ + )} +
+ ); +}; + +export default SettingsSSLProvider; diff --git a/ui/src/repository/settings.ts b/ui/src/repository/settings.ts index 7bd2aec0..fe7c8a54 100644 --- a/ui/src/repository/settings.ts +++ b/ui/src/repository/settings.ts @@ -1,9 +1,9 @@ import { ClientResponseError } from "pocketbase"; -import { SETTINGS_NAMES, type SettingsModel } from "@/domain/settings"; +import { type SettingsModel, type SettingsNames } from "@/domain/settings"; import { getPocketBase } from "./pocketbase"; -export const get = async (name: (typeof SETTINGS_NAMES)[keyof typeof SETTINGS_NAMES]) => { +export const get = async >(name: SettingsNames) => { try { const resp = await getPocketBase().collection("settings").getFirstListItem>(`name='${name}'`, { requestKey: null, @@ -21,7 +21,7 @@ export const get = async (name: (typeof SETTINGS_NAMES)[keyof typeof SETTINGS } }; -export const save = async (record: MaybeModelRecordWithId>) => { +export const save = async >(record: MaybeModelRecordWithId>) => { if (record.id) { return await getPocketBase().collection("settings").update>(record.id, record); } diff --git a/ui/src/router.tsx b/ui/src/router.tsx index ef74a31d..357aaf81 100644 --- a/ui/src/router.tsx +++ b/ui/src/router.tsx @@ -12,7 +12,7 @@ import Settings from "./pages/settings/Settings"; import SettingsAccount from "./pages/settings/SettingsAccount"; import SettingsPassword from "./pages/settings/SettingsPassword"; import SettingsNotification from "./pages/settings/SettingsNotification"; -import SSLProvider from "./pages/settings/SSLProvider"; +import SettingsSSLProvider from "./pages/settings/SettingsSSLProvider"; export const router = createHashRouter([ { @@ -57,7 +57,7 @@ export const router = createHashRouter([ }, { path: "/settings/ssl-provider", - element: , + element: , }, ], }, diff --git a/ui/src/stores/notify/index.ts b/ui/src/stores/notify/index.ts index 9a53b63c..b61c1a93 100644 --- a/ui/src/stores/notify/index.ts +++ b/ui/src/stores/notify/index.ts @@ -22,10 +22,12 @@ export const useNotifyChannelStore = create((set, get) => { setChannel: async (channel, config) => { settings ??= await getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS); - return get().setChannels({ - ...settings.content, - [channel]: { ...settings.content[channel], ...config }, - }); + return get().setChannels( + produce(settings, (draft) => { + draft.content ??= {}; + draft.content[channel] = { ...draft.content[channel], ...config }; + }) + ); }, setChannels: async (channels) => { From 63ffb9df1488d3ea279c6ca3265029e621771e70 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 20 Dec 2024 21:01:24 +0800 Subject: [PATCH 28/39] fix(ui): tsc-check error --- ui/src/components/workflow/DeployToLocal.tsx | 14 +++++++------- ui/src/components/workflow/DeployToSSH.tsx | 14 +++++++------- ui/src/domain/workflow.ts | 4 ++-- ui/src/pages/accesses/AccessList.tsx | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ui/src/components/workflow/DeployToLocal.tsx b/ui/src/components/workflow/DeployToLocal.tsx index ef9bfc77..d0fe0605 100644 --- a/ui/src/components/workflow/DeployToLocal.tsx +++ b/ui/src/components/workflow/DeployToLocal.tsx @@ -324,7 +324,7 @@ Remove-Item -Path "$pfxPath" -Force 密钥路径 - + @@ -339,7 +339,7 @@ Remove-Item -Path "$pfxPath" -Force PFX 密码 - + @@ -356,7 +356,7 @@ Remove-Item -Path "$pfxPath" -Force JKS 别名 - + @@ -370,7 +370,7 @@ Remove-Item -Path "$pfxPath" -Force JKS Keypass - + @@ -384,7 +384,7 @@ Remove-Item -Path "$pfxPath" -Force JKS Storepass - + @@ -423,7 +423,7 @@ Remove-Item -Path "$pfxPath" -Force {t("domain.deployment.form.shell_pre_command.label")} -