diff --git a/internal/certificate/service.go b/internal/certificate/service.go
index 36acac3b..2b29b9f5 100644
--- a/internal/certificate/service.go
+++ b/internal/certificate/service.go
@@ -41,6 +41,7 @@ func (s *certificateService) InitSchedule(ctx context.Context) error {
return
}
msg := buildMsg(certs)
+ // TODO: 空指针 Bug
if err := notify.SendToAllChannels(msg.Subject, msg.Message); err != nil {
app.GetApp().Logger().Error("failed to send expire soon certificate", "err", err)
}
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 465706d3..cce4813e 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -31,6 +31,7 @@
"lucide-react": "^0.469.0",
"nanoid": "^5.0.9",
"pocketbase": "^0.21.5",
+ "radash": "^12.1.0",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
@@ -6743,6 +6744,14 @@
}
]
},
+ "node_modules/radash": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmmirror.com/radash/-/radash-12.1.0.tgz",
+ "integrity": "sha512-b0Zcf09AhqKS83btmUeYBS8tFK7XL2e3RvLmZcm0sTdF1/UUlHSsjXdCcWNxe7yfmAlPve5ym0DmKGtTzP6kVQ==",
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
"node_modules/rc-cascader": {
"version": "3.30.0",
"resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.30.0.tgz",
diff --git a/ui/package.json b/ui/package.json
index 107d2be8..2c514768 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -33,6 +33,7 @@
"lucide-react": "^0.469.0",
"nanoid": "^5.0.9",
"pocketbase": "^0.21.5",
+ "radash": "^12.1.0",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
diff --git a/ui/src/components/access/AccessSelect.tsx b/ui/src/components/access/AccessSelect.tsx
index 3cbca057..1e7d9f3f 100644
--- a/ui/src/components/access/AccessSelect.tsx
+++ b/ui/src/components/access/AccessSelect.tsx
@@ -12,7 +12,7 @@ export type AccessTypeSelectProps = Omit<
};
const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => {
- const { initialized, accesses, fetchAccesses } = useAccessStore();
+ const { accesses, loadedAtOnce, fetchAccesses } = useAccessStore();
useEffect(() => {
fetchAccesses();
}, [fetchAccesses]);
@@ -64,7 +64,7 @@ const AccessSelect = ({ filter, ...props }: AccessTypeSelectProps) => {
return {props.placeholder};
}}
- loading={!initialized}
+ loading={!loadedAtOnce}
options={options}
optionFilterProp="label"
optionLabelProp={undefined}
diff --git a/ui/src/components/notification/NotifyChannels.tsx b/ui/src/components/notification/NotifyChannels.tsx
index 76d09817..6e9c8d23 100644
--- a/ui/src/components/notification/NotifyChannels.tsx
+++ b/ui/src/components/notification/NotifyChannels.tsx
@@ -70,7 +70,7 @@ export type NotifyChannelsProps = {
const NotifyChannels = ({ className, classNames, style, styles }: NotifyChannelsProps) => {
const { t, i18n } = useTranslation();
- const { initialized, channels, setChannel, fetchChannels } = useNotifyChannelStore();
+ const { channels, loadedAtOnce, setChannel, fetchChannels } = useNotifyChannelStore();
useEffect(() => {
fetchChannels();
}, [fetchChannels]);
@@ -105,7 +105,7 @@ const NotifyChannels = ({ className, classNames, style, styles }: NotifyChannels
return (
- {!initialized ? (
+ {!loadedAtOnce ? (
) : (
diff --git a/ui/src/components/workflow/AddNode.tsx b/ui/src/components/workflow/AddNode.tsx
index 95307cac..854a9281 100644
--- a/ui/src/components/workflow/AddNode.tsx
+++ b/ui/src/components/workflow/AddNode.tsx
@@ -3,8 +3,8 @@ import { Plus } from "lucide-react";
import { BrandNodeProps, NodeProps } from "./types";
import { newWorkflowNode, workflowNodeDropdownList, WorkflowNodeType } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useZustandShallowSelector } from "@/hooks";
+import { useWorkflowStore } from "@/stores/workflow";
import {
DropdownMenu,
DropdownMenuContent,
@@ -21,12 +21,8 @@ import DropdownMenuItemIcon from "./DropdownMenuItemIcon";
import Show from "../Show";
import { useTranslation } from "react-i18next";
-const selectState = (state: WorkflowState) => ({
- addNode: state.addNode,
-});
-
const AddNode = ({ data }: NodeProps | BrandNodeProps) => {
- const { addNode } = useWorkflowStore(useShallow(selectState));
+ const { addNode } = useWorkflowStore(useZustandShallowSelector(["addNode"]));
const { t } = useTranslation();
const handleTypeSelected = (type: WorkflowNodeType, provider?: string) => {
diff --git a/ui/src/components/workflow/ApplyForm.tsx b/ui/src/components/workflow/ApplyForm.tsx
index baf18d4c..33217b98 100644
--- a/ui/src/components/workflow/ApplyForm.tsx
+++ b/ui/src/components/workflow/ApplyForm.tsx
@@ -13,23 +13,20 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrig
import AccessEditModal from "@/components/access/AccessEditModal";
import EmailsEdit from "@/components/certimate/EmailsEdit";
import StringList from "@/components/certimate/StringList";
-
import { accessProvidersMap } from "@/domain/access";
+import { useZustandShallowSelector } from "@/hooks";
import { useAccessStore } from "@/stores/access";
import { useContactStore } from "@/stores/contact";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
import { usePanel } from "./PanelProvider";
type ApplyFormProps = {
data: WorkflowNode;
};
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
-});
+
const ApplyForm = ({ data }: ApplyFormProps) => {
- const { updateNode } = useWorkflowStore(useShallow(selectState));
+ const { updateNode } = useWorkflowStore(useZustandShallowSelector(["updateNode"]));
const { accesses } = useAccessStore();
const { emails, fetchEmails } = useContactStore();
diff --git a/ui/src/components/workflow/BranchNode.tsx b/ui/src/components/workflow/BranchNode.tsx
index b09b2fd3..5e1d5ccf 100644
--- a/ui/src/components/workflow/BranchNode.tsx
+++ b/ui/src/components/workflow/BranchNode.tsx
@@ -4,15 +4,12 @@ import { WorkflowBranchNode, WorkflowNode } from "@/domain/workflow";
import NodeRender from "./NodeRender";
import { memo } from "react";
import { BrandNodeProps } from "./types";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { useTranslation } from "react-i18next";
-const selectState = (state: WorkflowState) => ({
- addBranch: state.addBranch,
-});
const BranchNode = memo(({ data }: BrandNodeProps) => {
- const { addBranch } = useWorkflowStore(useShallow(selectState));
+ const { addBranch } = useWorkflowStore(useZustandShallowSelector(["addBranch"]));
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/ConditionNode.tsx b/ui/src/components/workflow/ConditionNode.tsx
index d35d80cc..9c29ca7d 100644
--- a/ui/src/components/workflow/ConditionNode.tsx
+++ b/ui/src/components/workflow/ConditionNode.tsx
@@ -1,16 +1,12 @@
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
+import { useWorkflowStore } from "@/stores/workflow";
import AddNode from "./AddNode";
import { NodeProps } from "./types";
-import { useShallow } from "zustand/shallow";
+import { useZustandShallowSelector } from "@/hooks";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
import { Ellipsis, Trash2 } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- removeBranch: state.removeBranch,
-});
const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => {
- const { updateNode, removeBranch } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, removeBranch } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeBranch"]));
const handleNameBlur = (e: React.FocusEvent
) => {
updateNode({ ...data, name: e.target.innerText });
};
diff --git a/ui/src/components/workflow/DeployToAliyunALB.tsx b/ui/src/components/workflow/DeployToAliyunALB.tsx
index 270a2e74..8c14e8fe 100644
--- a/ui/src/components/workflow/DeployToAliyunALB.tsx
+++ b/ui/src/components/workflow/DeployToAliyunALB.tsx
@@ -6,8 +6,8 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { DeployFormProps } from "./DeployForm";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -18,13 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const DeployToAliyunALB = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToAliyunCDN.tsx b/ui/src/components/workflow/DeployToAliyunCDN.tsx
index 5c031833..6bb794b3 100644
--- a/ui/src/components/workflow/DeployToAliyunCDN.tsx
+++ b/ui/src/components/workflow/DeployToAliyunCDN.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -19,12 +19,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToAliyunCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToAliyunCLB.tsx b/ui/src/components/workflow/DeployToAliyunCLB.tsx
index 3562978f..83a0eb86 100644
--- a/ui/src/components/workflow/DeployToAliyunCLB.tsx
+++ b/ui/src/components/workflow/DeployToAliyunCLB.tsx
@@ -6,8 +6,8 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { DeployFormProps } from "./DeployForm";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -18,13 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const DeployToAliyunCLB = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToAliyunNLB.tsx b/ui/src/components/workflow/DeployToAliyunNLB.tsx
index 5f183dae..45204067 100644
--- a/ui/src/components/workflow/DeployToAliyunNLB.tsx
+++ b/ui/src/components/workflow/DeployToAliyunNLB.tsx
@@ -6,21 +6,16 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { DeployFormProps } from "./DeployForm";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { Button } from "../ui/button";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const DeployToAliyunNLB = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToAliyunOss.tsx b/ui/src/components/workflow/DeployToAliyunOss.tsx
index 49536ccf..f0cc90df 100644
--- a/ui/src/components/workflow/DeployToAliyunOss.tsx
+++ b/ui/src/components/workflow/DeployToAliyunOss.tsx
@@ -7,25 +7,19 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
-
import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToAliyunOSS = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx b/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx
index c5449272..ca01bc51 100644
--- a/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx
+++ b/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToByteplusCDN.tsx b/ui/src/components/workflow/DeployToByteplusCDN.tsx
index c6dcb424..99a57db1 100644
--- a/ui/src/components/workflow/DeployToByteplusCDN.tsx
+++ b/ui/src/components/workflow/DeployToByteplusCDN.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToByteplusCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToDogeCloudCDN.tsx b/ui/src/components/workflow/DeployToDogeCloudCDN.tsx
index 24aa21cb..f01a0dca 100644
--- a/ui/src/components/workflow/DeployToDogeCloudCDN.tsx
+++ b/ui/src/components/workflow/DeployToDogeCloudCDN.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -19,12 +19,8 @@ import AccessSelect from "./AccessSelect";
import { Plus } from "lucide-react";
import AccessEditModal from "../access/AccessEditModal";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx b/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx
index 903bc4a9..0a430422 100644
--- a/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx
+++ b/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -20,12 +20,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx b/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx
index d56b62ba..30541463 100644
--- a/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx
+++ b/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -19,12 +19,8 @@ import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
import AccessSelect from "./AccessSelect";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToKubernetesSecret.tsx b/ui/src/components/workflow/DeployToKubernetesSecret.tsx
index 9cc418ba..9ee37ff6 100644
--- a/ui/src/components/workflow/DeployToKubernetesSecret.tsx
+++ b/ui/src/components/workflow/DeployToKubernetesSecret.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -19,12 +19,8 @@ import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
import AccessSelect from "./AccessSelect";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToKubernetesSecret = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToLocal.tsx b/ui/src/components/workflow/DeployToLocal.tsx
index 942f646b..96827afb 100644
--- a/ui/src/components/workflow/DeployToLocal.tsx
+++ b/ui/src/components/workflow/DeployToLocal.tsx
@@ -7,8 +7,8 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select";
import { Button } from "../ui/button";
import { DeployFormProps } from "./DeployForm";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useEffect, useState } from "react";
import i18n from "@/i18n";
@@ -19,11 +19,6 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const t = i18n.t;
const formSchema = z
@@ -75,7 +70,7 @@ const formSchema = z
});
const DeployToLocal = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToQiniuCDN.tsx b/ui/src/components/workflow/DeployToQiniuCDN.tsx
index 839b4d7f..ce027aa1 100644
--- a/ui/src/components/workflow/DeployToQiniuCDN.tsx
+++ b/ui/src/components/workflow/DeployToQiniuCDN.tsx
@@ -7,8 +7,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
@@ -19,12 +19,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToQiniuCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToSSH.tsx b/ui/src/components/workflow/DeployToSSH.tsx
index f41596ce..78bafc42 100644
--- a/ui/src/components/workflow/DeployToSSH.tsx
+++ b/ui/src/components/workflow/DeployToSSH.tsx
@@ -7,8 +7,8 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select";
import { Button } from "../ui/button";
import { DeployFormProps } from "./DeployForm";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useEffect, useState } from "react";
import i18n from "@/i18n";
@@ -18,11 +18,6 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const t = i18n.t;
const formSchema = z
@@ -71,7 +66,7 @@ const formSchema = z
});
const DeployToSSH = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToTencentCDN.tsx b/ui/src/components/workflow/DeployToTencentCDN.tsx
index b7189e51..7d2f723d 100644
--- a/ui/src/components/workflow/DeployToTencentCDN.tsx
+++ b/ui/src/components/workflow/DeployToTencentCDN.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToTencentCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToTencentCLB.tsx b/ui/src/components/workflow/DeployToTencentCLB.tsx
index e97b95cb..05ad2e35 100644
--- a/ui/src/components/workflow/DeployToTencentCLB.tsx
+++ b/ui/src/components/workflow/DeployToTencentCLB.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -21,12 +20,8 @@ import { Plus } from "lucide-react";
type TencentResourceType = "ssl-deploy" | "loadbalancer" | "listener" | "ruledomain";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToTencentCLB = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToTencentCOS.tsx b/ui/src/components/workflow/DeployToTencentCOS.tsx
index 3766a52a..7a04c7ac 100644
--- a/ui/src/components/workflow/DeployToTencentCOS.tsx
+++ b/ui/src/components/workflow/DeployToTencentCOS.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessEditModal from "../access/AccessEditModal";
import AccessSelect from "./AccessSelect";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToTencentCOS = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToTencentTEO.tsx b/ui/src/components/workflow/DeployToTencentTEO.tsx
index faa0e43e..95d27a5c 100644
--- a/ui/src/components/workflow/DeployToTencentTEO.tsx
+++ b/ui/src/components/workflow/DeployToTencentTEO.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToTencentTEO = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToVolcengineCDN.tsx b/ui/src/components/workflow/DeployToVolcengineCDN.tsx
index 444410fd..ccb05267 100644
--- a/ui/src/components/workflow/DeployToVolcengineCDN.tsx
+++ b/ui/src/components/workflow/DeployToVolcengineCDN.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToVolcengineCDN = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToVolcengineLive.tsx b/ui/src/components/workflow/DeployToVolcengineLive.tsx
index 06c34dd4..b06309a7 100644
--- a/ui/src/components/workflow/DeployToVolcengineLive.tsx
+++ b/ui/src/components/workflow/DeployToVolcengineLive.tsx
@@ -7,11 +7,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,12 +18,8 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
const DeployToVolcengineLive = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/DeployToWebhook.tsx b/ui/src/components/workflow/DeployToWebhook.tsx
index 7c383056..cdb59ef2 100644
--- a/ui/src/components/workflow/DeployToWebhook.tsx
+++ b/ui/src/components/workflow/DeployToWebhook.tsx
@@ -6,11 +6,10 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { Button } from "../ui/button";
-
import { useEffect, useState } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { SelectLabel } from "@radix-ui/react-select";
@@ -19,17 +18,12 @@ import AccessSelect from "./AccessSelect";
import AccessEditModal from "../access/AccessEditModal";
import { Plus } from "lucide-react";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
-});
-
const KVTypeSchema = z.object({
key: z.string(),
value: z.string(),
});
const DeployToWebhook = ({ data }: DeployFormProps) => {
- const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/Node.tsx b/ui/src/components/workflow/Node.tsx
index 0496d8fa..a8ac3762 100644
--- a/ui/src/components/workflow/Node.tsx
+++ b/ui/src/components/workflow/Node.tsx
@@ -1,7 +1,7 @@
import { WorkflowNode, WorkflowNodeType } from "@/domain/workflow";
import AddNode from "./AddNode";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
import { Ellipsis, Trash2 } from "lucide-react";
import { usePanel } from "./PanelProvider";
@@ -17,12 +17,8 @@ type NodeProps = {
const i18nPrefix = "workflow.node";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
- removeNode: state.removeNode,
-});
const Node = ({ data }: NodeProps) => {
- const { updateNode, removeNode } = useWorkflowStore(useShallow(selectState));
+ const { updateNode, removeNode } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeNode"]));
const handleNameBlur = (e: React.FocusEvent) => {
updateNode({ ...data, name: e.target.innerText });
};
diff --git a/ui/src/components/workflow/NotifyForm.tsx b/ui/src/components/workflow/NotifyForm.tsx
index a632ddb2..75d87643 100644
--- a/ui/src/components/workflow/NotifyForm.tsx
+++ b/ui/src/components/workflow/NotifyForm.tsx
@@ -5,8 +5,8 @@ import { z } from "zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger } from "../ui/select";
import { Input } from "../ui/input";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { usePanel } from "./PanelProvider";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button";
@@ -21,9 +21,6 @@ type NotifyFormProps = {
data: WorkflowNode;
};
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
-});
type ChannelName = {
key: string;
label: string;
@@ -31,7 +28,7 @@ type ChannelName = {
const i18nPrefix = "workflow.node.notify.form";
const NotifyForm = ({ data }: NotifyFormProps) => {
- const { updateNode } = useWorkflowStore(useShallow(selectState));
+ const { updateNode } = useWorkflowStore(useZustandShallowSelector(["updateNode"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
const { channels: supportedChannels, fetchChannels } = useNotifyChannelStore();
diff --git a/ui/src/components/workflow/StartForm.tsx b/ui/src/components/workflow/StartForm.tsx
index f87cc510..d8645660 100644
--- a/ui/src/components/workflow/StartForm.tsx
+++ b/ui/src/components/workflow/StartForm.tsx
@@ -5,12 +5,12 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { Radio } from "antd";
import { parseExpression } from "cron-parser";
import { z } from "zod";
-import { useShallow } from "zustand/shallow";
import { Button } from "../ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
import { Input } from "../ui/input";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
+import { useWorkflowStore } from "@/stores/workflow";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
import { usePanel } from "./PanelProvider";
import { RadioChangeEvent } from "antd/lib";
@@ -41,11 +41,8 @@ type StartFormProps = {
const i18nPrefix = "workflow.node.start.form";
-const selectState = (state: WorkflowState) => ({
- updateNode: state.updateNode,
-});
const StartForm = ({ data }: StartFormProps) => {
- const { updateNode } = useWorkflowStore(useShallow(selectState));
+ const { updateNode } = useWorkflowStore(useZustandShallowSelector(["updateNode"]));
const { hidePanel } = usePanel();
const { t } = useTranslation();
diff --git a/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx
index 0ed4162a..770f154a 100644
--- a/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx
+++ b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx
@@ -1,7 +1,7 @@
import { z } from "zod";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
-import { useShallow } from "zustand/shallow";
+import { useWorkflowStore } from "@/stores/workflow";
+import { useZustandShallowSelector } from "@/hooks";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
@@ -20,12 +20,8 @@ const formSchema = z.object({
description: z.string(),
});
-const selectState = (state: WorkflowState) => ({
- setBaseInfo: state.setBaseInfo,
- workflow: state.workflow,
-});
const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => {
- const { setBaseInfo, workflow } = useWorkflowStore(useShallow(selectState));
+ const { setBaseInfo, workflow } = useWorkflowStore(useZustandShallowSelector(["setBaseInfo", "workflow"]));
const form = useForm>({
resolver: zodResolver(formSchema),
diff --git a/ui/src/components/workflow/WorkflowLog.tsx b/ui/src/components/workflow/WorkflowLog.tsx
index c2b117d1..d94a3f86 100644
--- a/ui/src/components/workflow/WorkflowLog.tsx
+++ b/ui/src/components/workflow/WorkflowLog.tsx
@@ -3,7 +3,7 @@ import { list as logs } from "@/repository/workflowRunLog";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { DataTable } from "./DataTable";
-import { useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { Check, X } from "lucide-react";
import WorkflowLogDetail from "./WorkflowLogDetail";
import { useTranslation } from "react-i18next";
@@ -12,8 +12,7 @@ const WorkflowLog = () => {
const [data, setData] = useState([]);
const [pageCount, setPageCount] = useState(0);
- const [searchParams] = useSearchParams();
- const id = searchParams.get("id");
+ const { id } = useParams();
const { t } = useTranslation();
diff --git a/ui/src/hooks/index.ts b/ui/src/hooks/index.ts
index e90b87d9..69e1b33c 100644
--- a/ui/src/hooks/index.ts
+++ b/ui/src/hooks/index.ts
@@ -1,3 +1,4 @@
import useBrowserTheme from "./useBrowserTheme";
+import useZustandShallowSelector from "./useZustandShallowSelector";
-export { useBrowserTheme };
+export { useBrowserTheme, useZustandShallowSelector };
diff --git a/ui/src/hooks/useBrowserTheme.ts b/ui/src/hooks/useBrowserTheme.ts
index baa6b2fd..09b83d59 100644
--- a/ui/src/hooks/useBrowserTheme.ts
+++ b/ui/src/hooks/useBrowserTheme.ts
@@ -1,5 +1,5 @@
import { useTheme } from "ahooks";
-export default () => {
+export default function () {
return useTheme({ localStorageKey: "certimate-ui-theme" });
-};
+}
diff --git a/ui/src/hooks/useZustandShallowSelector.ts b/ui/src/hooks/useZustandShallowSelector.ts
new file mode 100644
index 00000000..92bf83ca
--- /dev/null
+++ b/ui/src/hooks/useZustandShallowSelector.ts
@@ -0,0 +1,18 @@
+import { pick, isArray } from "radash";
+
+import { useRef } from "react";
+import { shallow } from "zustand/shallow";
+
+type MaybeMany = T | readonly T[];
+
+export default function (paths: MaybeMany): (state: T) => Pick {
+ const prev = useRef>({} as Pick);
+
+ return (state: T) => {
+ if (state) {
+ const next = pick(state, isArray(paths) ? paths : [paths]);
+ return shallow(prev.current, next) ? prev.current : (prev.current = next);
+ }
+ return prev.current;
+ };
+}
diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx
index ab83d2fe..ce1860e5 100644
--- a/ui/src/pages/accesses/AccessList.tsx
+++ b/ui/src/pages/accesses/AccessList.tsx
@@ -18,7 +18,7 @@ const AccessList = () => {
const [modalApi, ModelContextHolder] = Modal.useModal();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
- const { initialized, accesses, fetchAccesses, deleteAccess } = useAccessStore();
+ const { accesses, loadedAtOnce, fetchAccesses, deleteAccess } = useAccessStore();
const tableColumns: TableProps["columns"] = [
{
@@ -181,7 +181,7 @@ const AccessList = () => {
columns={tableColumns}
dataSource={tableData}
- loading={!initialized || loading}
+ loading={!loadedAtOnce || loading}
locale={{
emptyText: ,
}}
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
index 3e68b5b3..de80ddc5 100644
--- a/ui/src/pages/certificates/CertificateList.tsx
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -120,7 +120,7 @@ const CertificateList = () => {
type="secondary"
ellipsis
onClick={() => {
- navigate(`/workflows/detail?id=${workflowId}`);
+ navigate(`/workflows/${workflowId}`);
}}
>
{record.expand?.workflow?.name ?? ""}
diff --git a/ui/src/pages/settings/SettingsNotification.tsx b/ui/src/pages/settings/SettingsNotification.tsx
index 555af3a1..7ac8aa31 100644
--- a/ui/src/pages/settings/SettingsNotification.tsx
+++ b/ui/src/pages/settings/SettingsNotification.tsx
@@ -8,7 +8,7 @@ import { useNotifyChannelStore } from "@/stores/notify";
const SettingsNotification = () => {
const { t } = useTranslation();
- const { initialized } = useNotifyChannelStore();
+ const { loadedAtOnce } = useNotifyChannelStore();
return (
@@ -20,7 +20,7 @@ const SettingsNotification = () => {
-
+
diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx
index 5eb5e2f7..a1c7d5e1 100644
--- a/ui/src/pages/workflows/WorkflowDetail.tsx
+++ b/ui/src/pages/workflows/WorkflowDetail.tsx
@@ -1,8 +1,7 @@
import { useEffect, useMemo, useState } from "react";
-import { useNavigate, useSearchParams } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Button, message, notification, Switch } from "antd";
-import { useShallow } from "zustand/shallow";
import { ArrowLeft as ArrowLeftIcon } from "lucide-react";
import Show from "@/components/Show";
@@ -12,20 +11,14 @@ import WorkflowBaseInfoEditDialog from "@/components/workflow/WorkflowBaseInfoEd
import WorkflowLog from "@/components/workflow/WorkflowLog";
import WorkflowProvider from "@/components/workflow/WorkflowProvider";
import { cn } from "@/components/ui/utils";
+import { useZustandShallowSelector } from "@/hooks";
import { allNodesValidated, WorkflowNode } from "@/domain/workflow";
-import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
+import { useWorkflowStore } from "@/stores/workflow";
import { run as runWorkflow } from "@/api/workflow";
-const selectState = (state: WorkflowState) => ({
- workflow: state.workflow,
- init: state.init,
- switchEnable: state.switchEnable,
- save: state.save,
-});
-
const WorkflowDetail = () => {
const navigate = useNavigate();
- const [searchParams] = useSearchParams();
+ const { id } = useParams();
const { t } = useTranslation();
@@ -33,11 +26,10 @@ const WorkflowDetail = () => {
const [_, NotificationContextHolder] = notification.useNotification();
// 3. 使用正确的选择器和 shallow 比较
- const { workflow, init, switchEnable, save } = useWorkflowStore(useShallow(selectState));
+ const { workflow, init, switchEnable, save } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "switchEnable", "save"]));
// 从 url 中获取 workflowId
const [locId, setLocId] = useState("");
- const id = searchParams.get("id");
const [tab, setTab] = useState("workflow");
@@ -78,7 +70,7 @@ const WorkflowDetail = () => {
}
switchEnable();
if (!locId) {
- navigate(`/workflows/detail?id=${workflow.id}`);
+ navigate(`/workflows/${workflow.id}`);
}
};
@@ -89,7 +81,7 @@ const WorkflowDetail = () => {
}
save();
if (!locId) {
- navigate(`/workflows/detail?id=${workflow.id}`);
+ navigate(`/workflows/${workflow.id}`);
}
};
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index a154c5c2..54c3c068 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -180,7 +180,7 @@ const WorkflowList = () => {
type="link"
icon={}
onClick={() => {
- navigate(`/workflows/detail?id=${record.id}`);
+ navigate(`/workflows/${record.id}`);
}}
/>
@@ -276,7 +276,7 @@ const WorkflowList = () => {
};
const handleCreateClick = () => {
- navigate("/workflows/detail");
+ navigate("/workflows/");
};
return (
diff --git a/ui/src/repository/access.ts b/ui/src/repository/access.ts
index 085c1675..fd53d852 100644
--- a/ui/src/repository/access.ts
+++ b/ui/src/repository/access.ts
@@ -23,5 +23,11 @@ export const save = async (record: MaybeModelRecord) => {
export const remove = async (record: MaybeModelRecordWithId) => {
record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") };
+
+ // TODO: 仅为兼容旧版本,后续迭代时删除
+ if ("configType" in record && record.configType === "httpreq") record.configType = "acmehttpreq";
+ if ("configType" in record && record.configType === "tencent") record.configType = "tencentcloud";
+ if ("configType" in record && record.configType === "pdns") record.configType = "powerdns";
+
await getPocketBase().collection(COLLECTION_NAME).update(record.id!, record);
};
diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts
index a0418a98..7a3ac1d6 100644
--- a/ui/src/repository/certificate.ts
+++ b/ui/src/repository/certificate.ts
@@ -36,3 +36,9 @@ export const list = async (request: ListCertificateRequest) => {
return pb.collection(COLLECTION_NAME).getList(page, perPage, options);
};
+
+export const remove = async (record: MaybeModelRecordWithId) => {
+ 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/repository/workflow.ts b/ui/src/repository/workflow.ts
index cc8bb302..b4596d17 100644
--- a/ui/src/repository/workflow.ts
+++ b/ui/src/repository/workflow.ts
@@ -17,7 +17,11 @@ export const list = async (request: ListWorkflowRequest) => {
const page = request.page || 1;
const perPage = request.perPage || 10;
- const options: RecordListOptions = { requestKey: null, sort: "-created" };
+ const options: RecordListOptions = {
+ sort: "-created",
+ requestKey: null,
+ };
+
if (request.enabled != null) {
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled });
}
diff --git a/ui/src/repository/workflowRunLog.ts b/ui/src/repository/workflowRunLog.ts
index 780077a6..b358d12c 100644
--- a/ui/src/repository/workflowRunLog.ts
+++ b/ui/src/repository/workflowRunLog.ts
@@ -1,6 +1,8 @@
import { type WorkflowRunLog } from "@/domain/workflow";
import { getPocketBase } from "./pocketbase";
+const COLLECTION_NAME = "workflow_run_log";
+
export type ListWorkflowLogsRequest = {
id: string;
page?: number;
@@ -12,7 +14,7 @@ export const list = async (request: ListWorkflowLogsRequest) => {
const perPage = request.perPage || 10;
return await getPocketBase()
- .collection("workflow_run_log")
+ .collection(COLLECTION_NAME)
.getList(page, perPage, {
filter: getPocketBase().filter("workflow={:workflowId}", { workflowId: request.id }),
sort: "-created",
diff --git a/ui/src/router.tsx b/ui/src/router.tsx
index 357aaf81..787a8def 100644
--- a/ui/src/router.tsx
+++ b/ui/src/router.tsx
@@ -36,7 +36,7 @@ export const router = createHashRouter([
element: ,
},
{
- path: "/workflows/detail",
+ path: "/workflows/:id",
element: ,
},
{
diff --git a/ui/src/stores/access/index.ts b/ui/src/stores/access/index.ts
index 290ec27f..d7c67e41 100644
--- a/ui/src/stores/access/index.ts
+++ b/ui/src/stores/access/index.ts
@@ -5,20 +5,23 @@ import { type AccessModel } from "@/domain/access";
import { list as listAccess, save as saveAccess, remove as removeAccess } from "@/repository/access";
export interface AccessState {
- initialized: boolean;
accesses: AccessModel[];
- createAccess: (access: MaybeModelRecord) => void;
- updateAccess: (access: MaybeModelRecordWithId) => void;
- deleteAccess: (access: MaybeModelRecordWithId) => void;
+ loading: boolean;
+ loadedAtOnce: boolean;
+
fetchAccesses: () => Promise;
+ createAccess: (access: MaybeModelRecord) => Promise;
+ updateAccess: (access: MaybeModelRecordWithId) => Promise;
+ deleteAccess: (access: MaybeModelRecordWithId) => Promise;
}
export const useAccessStore = create((set) => {
let fetcher: Promise | null = null; // 防止多次重复请求
return {
- initialized: false,
accesses: [],
+ loading: false,
+ loadedAtOnce: false,
createAccess: async (access) => {
const record = await saveAccess(access);
@@ -57,10 +60,12 @@ export const useAccessStore = create((set) => {
fetcher ??= listAccess();
try {
+ set({ loading: true });
const accesses = await fetcher;
- set({ accesses: accesses ?? [], initialized: true });
+ set({ accesses: accesses ?? [], loadedAtOnce: true });
} finally {
fetcher = null;
+ set({ loading: false });
}
},
};
diff --git a/ui/src/stores/contact/index.ts b/ui/src/stores/contact/index.ts
index 00889a65..e973bc38 100644
--- a/ui/src/stores/contact/index.ts
+++ b/ui/src/stores/contact/index.ts
@@ -5,10 +5,12 @@ import { SETTINGS_NAMES, type EmailsSettingsContent, type SettingsModel } from "
import { get as getSettings, save as saveSettings } from "@/repository/settings";
export interface ContactState {
- initialized: boolean;
emails: string[];
- setEmails: (emails: string[]) => void;
+ loading: boolean;
+ loadedAtOnce: boolean;
+
fetchEmails: () => Promise;
+ setEmails: (emails: string[]) => Promise;
}
export const useContactStore = create((set) => {
@@ -16,8 +18,9 @@ export const useContactStore = create((set) => {
let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用
return {
- initialized: false,
emails: [],
+ loading: false,
+ loadedAtOnce: false,
setEmails: async (emails) => {
settings ??= await getSettings(SETTINGS_NAMES.EMAILS);
@@ -32,7 +35,7 @@ export const useContactStore = create((set) => {
set(
produce((state: ContactState) => {
state.emails = settings.content.emails;
- state.initialized = true;
+ state.loadedAtOnce = true;
})
);
},
@@ -41,10 +44,12 @@ export const useContactStore = create((set) => {
fetcher ??= getSettings(SETTINGS_NAMES.EMAILS);
try {
+ set({ loading: true });
settings = await fetcher;
- set({ emails: settings.content.emails?.sort() ?? [], initialized: true });
+ set({ emails: settings.content.emails?.sort() ?? [], loadedAtOnce: true });
} finally {
fetcher = null;
+ set({ loading: false });
}
},
};
diff --git a/ui/src/stores/notify/index.ts b/ui/src/stores/notify/index.ts
index 86f81fe7..b2d6db58 100644
--- a/ui/src/stores/notify/index.ts
+++ b/ui/src/stores/notify/index.ts
@@ -5,11 +5,13 @@ import { SETTINGS_NAMES, type NotifyChannelsSettingsContent, type SettingsModel
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;
+ loading: boolean;
+ loadedAtOnce: boolean;
+
fetchChannels: () => Promise;
+ setChannel: (channel: keyof NotifyChannelsSettingsContent, config: NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent]) => Promise;
+ setChannels: (channels: NotifyChannelsSettingsContent) => Promise;
}
export const useNotifyChannelStore = create((set, get) => {
@@ -17,8 +19,9 @@ export const useNotifyChannelStore = create((set, get) => {
let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用
return {
- initialized: false,
channels: {},
+ loading: false,
+ loadedAtOnce: false,
setChannel: async (channel, config) => {
settings ??= await getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS);
@@ -40,7 +43,7 @@ export const useNotifyChannelStore = create((set, get) => {
set(
produce((state: NotifyChannelState) => {
state.channels = settings.content;
- state.initialized = true;
+ state.loadedAtOnce = true;
})
);
},
@@ -49,10 +52,12 @@ export const useNotifyChannelStore = create((set, get) => {
fetcher ??= getSettings(SETTINGS_NAMES.NOTIFY_CHANNELS);
try {
+ set({ loading: true });
settings = await fetcher;
- set({ channels: settings.content ?? {}, initialized: true });
+ set({ channels: settings.content ?? {}, loadedAtOnce: true });
} finally {
fetcher = null;
+ set({ loading: false });
}
},
};