mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-12 15:39:56 +00:00
feat(ui): improve workflow elements scroll area
This commit is contained in:
parent
b67049f9aa
commit
5cabceb08e
@ -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 {
|
func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCancelRunReq) error {
|
||||||
// TODO: 取消运行,防止因为某些原因意外挂起(如进程被杀死)导致工作流一直处于 running 状态无法重新运行
|
// TODO: 取消运行,防止因为某些原因意外挂起(如进程被杀死)导致工作流一直处于 running 状态无法重新运行
|
||||||
|
|
||||||
return nil
|
return errors.New("TODO: 尚未实现")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WorkflowService) Stop(ctx context.Context) {
|
func (s *WorkflowService) Stop(ctx context.Context) {
|
||||||
|
19
ui/package-lock.json
generated
19
ui/package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
"antd-zod": "^6.0.1",
|
"antd-zod": "^6.0.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"cron-parser": "^4.9.0",
|
"cron-parser": "^4.9.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^24.2.1",
|
"i18next": "^24.2.1",
|
||||||
@ -27,6 +28,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.4.0",
|
"react-i18next": "^15.4.0",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
@ -4124,6 +4126,14 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
|
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
|
||||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
"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": {
|
"node_modules/commander": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
|
||||||
@ -8563,6 +8573,15 @@
|
|||||||
"integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==",
|
"integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.17",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
"antd-zod": "^6.0.1",
|
"antd-zod": "^6.0.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"cron-parser": "^4.9.0",
|
"cron-parser": "^4.9.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^24.2.1",
|
"i18next": "^24.2.1",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.4.0",
|
"react-i18next": "^15.4.0",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
@ -49,9 +49,7 @@ const DrawerForm = <T extends NonNullable<unknown> = any>({
|
|||||||
|
|
||||||
const triggerEl = useTriggerElement(trigger, {
|
const triggerEl = useTriggerElement(trigger, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log("click");
|
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
console.log(open);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
const blob = new Blob([u8arr], { type: "application/zip" });
|
const blob = new Blob([u8arr], { type: "application/zip" });
|
||||||
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.error(err);
|
||||||
messageApi.warning(t("common.text.operation_failed"));
|
messageApi.warning(t("common.text.operation_failed"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
41
ui/src/components/workflow/WorkflowElementsContainer.tsx
Normal file
41
ui/src/components/workflow/WorkflowElementsContainer.tsx
Normal file
@ -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 (
|
||||||
|
<div className={mergeCls("relative size-full overflow-hidden", className)} style={style}>
|
||||||
|
<div className="size-full overflow-auto">
|
||||||
|
<div className="relative z-[1]">
|
||||||
|
<div className="origin-center transition-transform duration-300" style={{ zoom: `${scale}` }}>
|
||||||
|
<div className="p-4">
|
||||||
|
<WorkflowElements disabled={disabled} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="absolute bottom-4 right-6 z-[2] rounded-lg p-2 shadow-lg" styles={{ body: { padding: 0 } }}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button icon={<MinusOutlinedIcon />} disabled={scale <= 0.5} onClick={() => setScale((s) => Math.max(0.5, s - 0.1))} />
|
||||||
|
<Typography.Text className="min-w-[3em] text-center">{Math.round(scale * 100)}%</Typography.Text>
|
||||||
|
<Button icon={<PlusOutlinedIcon />} disabled={scale >= 2} onClick={() => setScale((s) => Math.min(2, s + 0.1))} />
|
||||||
|
<Button icon={<ExpandOutlinedIcon />} onClick={() => setScale(1)} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowElementsContainer;
|
@ -41,7 +41,7 @@ const ConsoleLayout = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className="min-h-screen" hasSider>
|
<Layout className="h-screen" hasSider>
|
||||||
<Layout.Sider className="fixed left-0 top-0 z-20 h-full max-md:static max-md:hidden" width="256px" theme="light">
|
<Layout.Sider className="fixed left-0 top-0 z-20 h-full max-md:static max-md:hidden" width="256px" theme="light">
|
||||||
<div className="flex size-full flex-col items-center justify-between overflow-hidden">
|
<div className="flex size-full flex-col items-center justify-between overflow-hidden">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@ -53,8 +53,8 @@ const ConsoleLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Layout.Sider>
|
</Layout.Sider>
|
||||||
|
|
||||||
<Layout className="pl-[256px] max-md:pl-0">
|
<Layout className="flex flex-col overflow-hidden pl-[256px] max-md:pl-0">
|
||||||
<Layout.Header className="sticky inset-x-0 top-0 z-[19] p-0 shadow-sm" style={{ background: themeToken.colorBgContainer }}>
|
<Layout.Header className="p-0 shadow-sm" style={{ background: themeToken.colorBgContainer }}>
|
||||||
<div className="flex size-full items-center justify-between overflow-hidden px-4">
|
<div className="flex size-full items-center justify-between overflow-hidden px-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<SiderMenuDrawer trigger={<Button className="md:hidden" icon={<MenuOutlinedIcon />} size="large" />} />
|
<SiderMenuDrawer trigger={<Button className="md:hidden" icon={<MenuOutlinedIcon />} size="large" />} />
|
||||||
@ -76,7 +76,7 @@ const ConsoleLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Layout.Header>
|
</Layout.Header>
|
||||||
|
|
||||||
<Layout.Content style={{ overflow: "initial" }}>
|
<Layout.Content className="flex-1 overflow-y-auto overflow-x-hidden">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -7,10 +7,7 @@ import {
|
|||||||
DeleteOutlined as DeleteOutlinedIcon,
|
DeleteOutlined as DeleteOutlinedIcon,
|
||||||
DownOutlined as DownOutlinedIcon,
|
DownOutlined as DownOutlinedIcon,
|
||||||
EllipsisOutlined as EllipsisOutlinedIcon,
|
EllipsisOutlined as EllipsisOutlinedIcon,
|
||||||
ExpandOutlined as ExpandOutlinedIcon,
|
|
||||||
HistoryOutlined as HistoryOutlinedIcon,
|
HistoryOutlined as HistoryOutlinedIcon,
|
||||||
MinusOutlined as MinusOutlinedIcon,
|
|
||||||
PlusOutlined as PlusOutlinedIcon,
|
|
||||||
UndoOutlined as UndoOutlinedIcon,
|
UndoOutlined as UndoOutlinedIcon,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-components";
|
import { PageHeader } from "@ant-design/pro-components";
|
||||||
@ -22,7 +19,7 @@ import { z } from "zod";
|
|||||||
import { startRun as startWorkflowRun } from "@/api/workflows";
|
import { startRun as startWorkflowRun } from "@/api/workflows";
|
||||||
import ModalForm from "@/components/ModalForm";
|
import ModalForm from "@/components/ModalForm";
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import WorkflowElements from "@/components/workflow/WorkflowElements";
|
import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer";
|
||||||
import WorkflowRuns from "@/components/workflow/WorkflowRuns";
|
import WorkflowRuns from "@/components/workflow/WorkflowRuns";
|
||||||
import { isAllNodesValidated } from "@/domain/workflow";
|
import { isAllNodesValidated } from "@/domain/workflow";
|
||||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||||
@ -40,8 +37,6 @@ const WorkflowDetail = () => {
|
|||||||
const [modalApi, ModalContextHolder] = Modal.useModal();
|
const [modalApi, ModalContextHolder] = Modal.useModal();
|
||||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||||
|
|
||||||
const [scale, setScale] = useState(1);
|
|
||||||
|
|
||||||
const { id: workflowId } = useParams();
|
const { id: workflowId } = useParams();
|
||||||
const { workflow, initialized, ...workflowState } = useWorkflowStore(
|
const { workflow, initialized, ...workflowState } = useWorkflowStore(
|
||||||
useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"])
|
useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"])
|
||||||
@ -58,15 +53,12 @@ const WorkflowDetail = () => {
|
|||||||
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
||||||
|
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]);
|
||||||
|
|
||||||
const [allowDiscard, setAllowDiscard] = useState(false);
|
const [allowDiscard, setAllowDiscard] = useState(false);
|
||||||
const [allowRelease, setAllowRelease] = useState(false);
|
const [allowRelease, setAllowRelease] = useState(false);
|
||||||
const [allowRun, setAllowRun] = useState(false);
|
const [allowRun, setAllowRun] = useState(false);
|
||||||
|
|
||||||
const lastRunStatus = useMemo(() => {
|
|
||||||
return workflow.lastRunStatus;
|
|
||||||
}, [workflow]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
setIsRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
|
||||||
}, [lastRunStatus]);
|
}, [lastRunStatus]);
|
||||||
@ -206,12 +198,13 @@ const WorkflowDetail = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex size-full flex-col">
|
||||||
{MessageContextHolder}
|
{MessageContextHolder}
|
||||||
{ModalContextHolder}
|
{ModalContextHolder}
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
<Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }}>
|
<div>
|
||||||
|
<Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }} style={{ borderRadius: 0 }}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
style={{ paddingBottom: 0 }}
|
style={{ paddingBottom: 0 }}
|
||||||
title={workflow.name}
|
title={workflow.name}
|
||||||
@ -263,18 +256,28 @@ const WorkflowDetail = () => {
|
|||||||
/>
|
/>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
<Card loading={!initialized}>
|
|
||||||
<Show when={tabValue === "orchestration"}>
|
<Show when={tabValue === "orchestration"}>
|
||||||
<div className="relative">
|
<div className="min-h-[360px] flex-1 overflow-hidden p-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<Card
|
||||||
|
className="size-full overflow-hidden"
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
position: "relative",
|
||||||
|
height: "100%",
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
loading={!initialized}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-x-6 top-4 z-[2] flex items-center justify-between gap-4">
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<Show when={workflow.hasDraft!}>
|
<Show when={workflow.hasDraft!}>
|
||||||
<Alert banner message={<div className="truncate">{t("workflow.detail.orchestration.draft.alert")}</div>} type="warning" />
|
<Alert banner message={<div className="truncate">{t("workflow.detail.orchestration.draft.alert")}</div>} type="warning" />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end ">
|
<div className="flex justify-end">
|
||||||
<Space>
|
<Space>
|
||||||
<Button disabled={!allowRun} icon={<CaretRightOutlinedIcon />} loading={isRunning} type="primary" onClick={handleRunClick}>
|
<Button disabled={!allowRun} icon={<CaretRightOutlinedIcon />} loading={isRunning} type="primary" onClick={handleRunClick}>
|
||||||
{t("workflow.detail.orchestration.action.run")}
|
{t("workflow.detail.orchestration.action.run")}
|
||||||
@ -305,24 +308,19 @@ const WorkflowDetail = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="fixed bottom-8 right-8 z-10 flex items-center gap-2 rounded-lg bg-white p-2 shadow-lg">
|
|
||||||
<Button icon={<MinusOutlinedIcon />} disabled={scale <= 0.5} onClick={() => setScale((s) => Math.max(0.5, s - 0.1))} />
|
|
||||||
<Typography.Text className="min-w-[3em] text-center">{Math.round(scale * 100)}%</Typography.Text>
|
|
||||||
<Button icon={<PlusOutlinedIcon />} disabled={scale >= 2} onClick={() => setScale((s) => Math.min(2, s + 0.1))} />
|
|
||||||
<Button icon={<ExpandOutlinedIcon />} onClick={() => setScale(1)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="size-full origin-top px-12 py-8 transition-transform duration-300 max-md:px-4" style={{ transform: `scale(${scale})` }}>
|
<WorkflowElementsContainer className="pt-16" />
|
||||||
<WorkflowElements />
|
</Card>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={tabValue === "runs"}>
|
<Show when={tabValue === "runs"}>
|
||||||
|
<div className="p-4">
|
||||||
|
<Card loading={!initialized}>
|
||||||
<WorkflowRuns workflowId={workflowId!} />
|
<WorkflowRuns workflowId={workflowId!} />
|
||||||
</Show>
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -109,7 +109,7 @@ const WorkflowNew = () => {
|
|||||||
<div>
|
<div>
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
<Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }}>
|
<Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }} style={{ borderRadius: 0 }}>
|
||||||
<PageHeader title={t("workflow.new.title")}>
|
<PageHeader title={t("workflow.new.title")}>
|
||||||
<Typography.Paragraph type="secondary">{t("workflow.new.subtitle")}</Typography.Paragraph>
|
<Typography.Paragraph type="secondary">{t("workflow.new.subtitle")}</Typography.Paragraph>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
@ -14,7 +14,7 @@ export type ListWorkflowRunsRequest = {
|
|||||||
export const list = async (request: ListWorkflowRunsRequest) => {
|
export const list = async (request: ListWorkflowRunsRequest) => {
|
||||||
const page = request.page || 1;
|
const page = request.page || 1;
|
||||||
const perPage = request.perPage || 10;
|
const perPage = request.perPage || 10;
|
||||||
console.log("request.workflowId", request.workflowId);
|
|
||||||
let filter = "";
|
let filter = "";
|
||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
if (request.workflowId) {
|
if (request.workflowId) {
|
||||||
|
6
ui/src/utils/css.ts
Normal file
6
ui/src/utils/css.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export const mergeCls = (...inputs: ClassValue[]) => {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
};
|
@ -1,5 +1,9 @@
|
|||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
export const getErrMsg = (error: unknown): string => {
|
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;
|
return error.message;
|
||||||
} else if (typeof error === "object" && error != null) {
|
} else if (typeof error === "object" && error != null) {
|
||||||
if ("message" in error) {
|
if ("message" in error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user