From 5cabceb08e4f08e2fbe2fdce3cf5c3f5b925b327 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 23 Jan 2025 02:44:02 +0800 Subject: [PATCH] feat(ui): improve workflow elements scroll area --- internal/workflow/service.go | 2 +- ui/package-lock.json | 19 ++ ui/package.json | 2 + ui/src/components/DrawerForm.tsx | 2 - .../certificate/CertificateDetail.tsx | 2 +- .../workflow/WorkflowElementsContainer.tsx | 41 +++ ui/src/pages/ConsoleLayout.tsx | 8 +- ui/src/pages/workflows/WorkflowDetail.tsx | 234 +++++++++--------- ui/src/pages/workflows/WorkflowNew.tsx | 2 +- ui/src/repository/workflowRun.ts | 2 +- ui/src/utils/css.ts | 6 + ui/src/utils/error.ts | 6 +- 12 files changed, 197 insertions(+), 129 deletions(-) create mode 100644 ui/src/components/workflow/WorkflowElementsContainer.tsx create mode 100644 ui/src/utils/css.ts diff --git a/internal/workflow/service.go b/internal/workflow/service.go index 1f103d72..0a5f2f96 100644 --- a/internal/workflow/service.go +++ b/internal/workflow/service.go @@ -105,7 +105,7 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error { // TODO: 取消运行,防止因为某些原因意外挂起(如进程被杀死)导致工作流一直处于 running 状态无法重新运行 - return nil + return errors.New("TODO: 尚未实现") } func (s *WorkflowService) Stop(ctx context.Context) { diff --git a/ui/package-lock.json b/ui/package-lock.json index 97dcc7fe..394aebcd 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13,6 +13,7 @@ "ahooks": "^3.8.4", "antd": "^5.23.1", "antd-zod": "^6.0.1", + "clsx": "^2.1.1", "cron-parser": "^4.9.0", "file-saver": "^2.0.5", "i18next": "^24.2.1", @@ -27,6 +28,7 @@ "react-dom": "^18.3.1", "react-i18next": "^15.4.0", "react-router-dom": "^7.1.3", + "tailwind-merge": "^2.6.0", "zod": "^3.24.1", "zustand": "^5.0.3" }, @@ -4124,6 +4126,14 @@ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", @@ -8563,6 +8573,15 @@ "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==", "dev": true }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/ui/package.json b/ui/package.json index c5cafafc..f2b1e993 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "ahooks": "^3.8.4", "antd": "^5.23.1", "antd-zod": "^6.0.1", + "clsx": "^2.1.1", "cron-parser": "^4.9.0", "file-saver": "^2.0.5", "i18next": "^24.2.1", @@ -29,6 +30,7 @@ "react-dom": "^18.3.1", "react-i18next": "^15.4.0", "react-router-dom": "^7.1.3", + "tailwind-merge": "^2.6.0", "zod": "^3.24.1", "zustand": "^5.0.3" }, diff --git a/ui/src/components/DrawerForm.tsx b/ui/src/components/DrawerForm.tsx index 0c319fb6..46116095 100644 --- a/ui/src/components/DrawerForm.tsx +++ b/ui/src/components/DrawerForm.tsx @@ -49,9 +49,7 @@ const DrawerForm = = any>({ const triggerEl = useTriggerElement(trigger, { onClick: () => { - console.log("click"); setOpen(true); - console.log(open); }, }); diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index b782afb5..6feb992b 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -27,7 +27,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { const blob = new Blob([u8arr], { type: "application/zip" }); saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`); } catch (err) { - console.log(err); + console.error(err); messageApi.warning(t("common.text.operation_failed")); } }; diff --git a/ui/src/components/workflow/WorkflowElementsContainer.tsx b/ui/src/components/workflow/WorkflowElementsContainer.tsx new file mode 100644 index 00000000..2329eeac --- /dev/null +++ b/ui/src/components/workflow/WorkflowElementsContainer.tsx @@ -0,0 +1,41 @@ +import { useState } from "react"; +import { ExpandOutlined as ExpandOutlinedIcon, MinusOutlined as MinusOutlinedIcon, PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons"; +import { Button, Card, Typography } from "antd"; + +import WorkflowElements from "@/components/workflow/WorkflowElements"; +import { mergeCls } from "@/utils/css"; + +export type WorkflowElementsProps = { + className?: string; + style?: React.CSSProperties; + disabled?: boolean; +}; + +const WorkflowElementsContainer = ({ className, style, disabled }: WorkflowElementsProps) => { + const [scale, setScale] = useState(1); + + return ( +
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ ); +}; + +export default WorkflowElementsContainer; diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx index a370d46c..4fbaf492 100644 --- a/ui/src/pages/ConsoleLayout.tsx +++ b/ui/src/pages/ConsoleLayout.tsx @@ -41,7 +41,7 @@ const ConsoleLayout = () => { } return ( - +
@@ -53,8 +53,8 @@ const ConsoleLayout = () => {
- - + +
} size="large" />} /> @@ -76,7 +76,7 @@ const ConsoleLayout = () => {
- + diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index 8df424b0..f85394d1 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -7,10 +7,7 @@ import { DeleteOutlined as DeleteOutlinedIcon, DownOutlined as DownOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon, - ExpandOutlined as ExpandOutlinedIcon, HistoryOutlined as HistoryOutlinedIcon, - MinusOutlined as MinusOutlinedIcon, - PlusOutlined as PlusOutlinedIcon, UndoOutlined as UndoOutlinedIcon, } from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-components"; @@ -22,7 +19,7 @@ import { z } from "zod"; import { startRun as startWorkflowRun } from "@/api/workflows"; import ModalForm from "@/components/ModalForm"; import Show from "@/components/Show"; -import WorkflowElements from "@/components/workflow/WorkflowElements"; +import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; import WorkflowRuns from "@/components/workflow/WorkflowRuns"; import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; @@ -40,8 +37,6 @@ const WorkflowDetail = () => { const [modalApi, ModalContextHolder] = Modal.useModal(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); - const [scale, setScale] = useState(1); - const { id: workflowId } = useParams(); const { workflow, initialized, ...workflowState } = useWorkflowStore( useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"]) @@ -58,15 +53,12 @@ const WorkflowDetail = () => { const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration"); const [isRunning, setIsRunning] = useState(false); + const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); const [allowDiscard, setAllowDiscard] = useState(false); const [allowRelease, setAllowRelease] = useState(false); const [allowRun, setAllowRun] = useState(false); - const lastRunStatus = useMemo(() => { - return workflow.lastRunStatus; - }, [workflow]); - useEffect(() => { setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); }, [lastRunStatus]); @@ -206,123 +198,129 @@ const WorkflowDetail = () => { }; return ( -
+
{MessageContextHolder} {ModalContextHolder} {NotificationContextHolder} - - {t("common.button.edit")}} />, +
+ + {t("common.button.edit")}} />, - , + , - , - onClick: () => { - handleDeleteClick(); - }, - }, - ], - }} - trigger={["click"]} - > - - , - ] - : [] - } - > - {workflow.description} - }, - { key: "runs", label: t("workflow.detail.runs.tab"), icon: }, - ]} - renderTabBar={(props, DefaultTabBar) => } - tabBarStyle={{ border: "none" }} - onChange={(key) => setTabValue(key as typeof tabValue)} - /> - - - -
- - -
-
-
- - {t("workflow.detail.orchestration.draft.alert")}
} type="warning" /> - -
-
- - - - - - - , - onClick: handleDiscardClick, + , + onClick: () => { + handleDeleteClick(); }, - ], - }} - trigger={["click"]} - > -
-
-
-
- -
- -
-
- - - - - + }, + ], + }} + trigger={["click"]} + > + + , + ] + : [] + } + > + {workflow.description} + }, + { key: "runs", label: t("workflow.detail.runs.tab"), icon: }, + ]} + renderTabBar={(props, DefaultTabBar) => } + tabBarStyle={{ border: "none" }} + onChange={(key) => setTabValue(key as typeof tabValue)} + /> +
+ + +
+ +
+
+ + {t("workflow.detail.orchestration.draft.alert")}
} type="warning" /> + +
+
+ + + + + + + , + onClick: handleDiscardClick, + }, + ], + }} + trigger={["click"]} + > +
+
+ + +
+
+ + + +
+ + + +
+
); }; diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index f24dde4c..68c5d96d 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -109,7 +109,7 @@ const WorkflowNew = () => {
{NotificationContextHolder} - + {t("workflow.new.subtitle")} diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts index 2d70c3e2..0aa88080 100644 --- a/ui/src/repository/workflowRun.ts +++ b/ui/src/repository/workflowRun.ts @@ -14,7 +14,7 @@ export type ListWorkflowRunsRequest = { export const list = async (request: ListWorkflowRunsRequest) => { const page = request.page || 1; const perPage = request.perPage || 10; - console.log("request.workflowId", request.workflowId); + let filter = ""; const params: Record = {}; if (request.workflowId) { diff --git a/ui/src/utils/css.ts b/ui/src/utils/css.ts new file mode 100644 index 00000000..048bd5e6 --- /dev/null +++ b/ui/src/utils/css.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export const mergeCls = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)); +}; diff --git a/ui/src/utils/error.ts b/ui/src/utils/error.ts index 0647d48b..df5465f4 100644 --- a/ui/src/utils/error.ts +++ b/ui/src/utils/error.ts @@ -1,5 +1,9 @@ +import { ClientResponseError } from "pocketbase"; + export const getErrMsg = (error: unknown): string => { - if (error instanceof Error) { + if (error instanceof ClientResponseError) { + return error.response != null ? getErrMsg(error.response) : error.message; + } else if (error instanceof Error) { return error.message; } else if (typeof error === "object" && error != null) { if ("message" in error) {