mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-01 15:39:35 +08:00
feat(ui): new WorkflowList page
This commit is contained in:
+1
-1
@@ -59,12 +59,12 @@ const RootApp = () => {
|
||||
token: {
|
||||
/* @see global.css, YOU MUST MODIFY BOTH DEFINITIONS AT THE SAME TIME! */
|
||||
colorPrimary: browserTheme === "dark" ? "#f97316" : "#ea580c",
|
||||
colorLink: browserTheme === "dark" ? "#f97316" : "#ea580c",
|
||||
colorInfo: browserTheme === "dark" ? "#478be6" : "#0969da",
|
||||
colorSuccess: browserTheme === "dark" ? "#57ab5a" : "#1a7f37",
|
||||
colorWarning: browserTheme === "dark" ? "#daaa3f" : "#eac54f",
|
||||
colorError: browserTheme === "dark" ? "#e5534b" : "#d1242f",
|
||||
colorBgContainer: "var(--color-container)",
|
||||
colorLink: "var(--color-primary)",
|
||||
},
|
||||
components: {
|
||||
Layout: {
|
||||
|
||||
@@ -1,31 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { IconBrowserShare, IconCheck, IconChevronRight, IconDownload, IconSettings2 } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Flex,
|
||||
Skeleton,
|
||||
Space,
|
||||
Spin,
|
||||
Table,
|
||||
type TableProps,
|
||||
Tooltip,
|
||||
Typography,
|
||||
notification,
|
||||
theme,
|
||||
} from "antd";
|
||||
import { Button, Collapse, Divider, Dropdown, Empty, Flex, Skeleton, Spin, Table, type TableProps, Tooltip, Typography, notification, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
@@ -40,6 +17,8 @@ import { listByWorkflowRunId as listLogsByWorkflowRunId } from "@/repository/wor
|
||||
import { mergeCls } from "@/utils/css";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
import WorkflowStatusIcon from "./WorkflowStatusIcon";
|
||||
|
||||
export type WorkflowRunDetailProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
@@ -114,35 +93,35 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
case WORKFLOW_RUN_STATUSES.PENDING:
|
||||
return (
|
||||
<Flex gap="small">
|
||||
<ClockCircleOutlinedIcon />
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.pending")}
|
||||
</Flex>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.RUNNING:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorInfo }}>
|
||||
<SyncOutlinedIcon spin />
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Flex>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.SUCCEEDED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorSuccess }}>
|
||||
<CheckCircleOutlinedIcon />
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Flex>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.FAILED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorError }}>
|
||||
<CloseCircleOutlinedIcon />
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Flex>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.CANCELED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorWarning }}>
|
||||
<StopOutlinedIcon />
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.canceled")}
|
||||
</Flex>
|
||||
);
|
||||
@@ -329,7 +308,7 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
align: "end",
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space.Compact>
|
||||
<div className="flex items-center justify-end">
|
||||
<CertificateDetailDrawer
|
||||
data={record}
|
||||
trigger={
|
||||
@@ -338,7 +317,7 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
PauseOutlined as PauseOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { IconBrowserShare, IconTrash } from "@tabler/icons-react";
|
||||
import { IconBrowserShare, IconPlayerPause, IconTrash } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { Alert, Button, Empty, Modal, Space, Table, type TableProps, Tag, Tooltip, notification } from "antd";
|
||||
import { Alert, Button, Empty, Modal, Table, type TableProps, Tooltip, notification } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { cancelRun as cancelWorkflowRun } from "@/api/workflows";
|
||||
import WorkflowStatusTag from "@/components/workflow/WorkflowStatusTag";
|
||||
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import {
|
||||
@@ -57,35 +50,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
if (record.status === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||
return <Tag icon={<ClockCircleOutlinedIcon />}>{t("workflow_run.props.status.pending")}</Tag>;
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||
return (
|
||||
<Tag icon={<SyncOutlinedIcon spin />} color="processing">
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
return (
|
||||
<Tag icon={<CheckCircleOutlinedIcon />} color="success">
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
return (
|
||||
<Tag icon={<CloseCircleOutlinedIcon />} color="error">
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||
return (
|
||||
<Tag icon={<StopOutlinedIcon />} color="warning">
|
||||
{t("workflow_run.props.status.canceled")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
return <WorkflowStatusTag status={record.status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -139,7 +104,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
record.status === WORKFLOW_RUN_STATUSES.CANCELED;
|
||||
|
||||
return (
|
||||
<Space.Compact>
|
||||
<div className="flex items-center justify-end">
|
||||
<WorkflowRunDetailDrawer
|
||||
data={record}
|
||||
trigger={
|
||||
@@ -153,7 +118,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
<Button
|
||||
color="default"
|
||||
disabled={!allowCancel}
|
||||
icon={<PauseOutlinedIcon />}
|
||||
icon={<IconPlayerPause size="1.25em" />}
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
handleCancelClick(record);
|
||||
@@ -173,7 +138,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { theme } from "antd";
|
||||
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
import { mergeCls } from "@/utils/css";
|
||||
|
||||
export type WorkflowStatusIconProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
color?: string | true;
|
||||
size?: number | string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
const WorkflowStatusIcon = ({ className, style, color, size, status }: WorkflowStatusIconProps) => {
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
switch (status) {
|
||||
case WORKFLOW_RUN_STATUSES.PENDING:
|
||||
if (color === true) {
|
||||
color = themeToken.colorTextSecondary;
|
||||
}
|
||||
return (
|
||||
<ClockCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.RUNNING:
|
||||
if (color === true) {
|
||||
color = themeToken.colorInfo;
|
||||
}
|
||||
return (
|
||||
<SyncOutlinedIcon
|
||||
className={mergeCls("animate-spin", className)}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.SUCCEEDED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorSuccess;
|
||||
console.log(color);
|
||||
}
|
||||
return (
|
||||
<CheckCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.FAILED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorError;
|
||||
}
|
||||
return (
|
||||
<CloseCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.CANCELED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorWarning;
|
||||
}
|
||||
return (
|
||||
<StopOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
export default WorkflowStatusIcon;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tag } from "antd";
|
||||
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
|
||||
import WorkflowStatusIcon from "./WorkflowStatusIcon";
|
||||
|
||||
export type WorkflowStatusTagProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
status: string;
|
||||
};
|
||||
|
||||
const WorkflowStatusTag = ({ className, style, status }: WorkflowStatusTagProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const Icon = <WorkflowStatusIcon status={status} />;
|
||||
|
||||
switch (status) {
|
||||
case WORKFLOW_RUN_STATUSES.PENDING:
|
||||
return (
|
||||
<Tag className={className} style={style} icon={Icon}>
|
||||
{t("workflow_run.props.status.pending")}
|
||||
</Tag>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.RUNNING:
|
||||
return (
|
||||
<Tag className={className} style={style} color="var(--color-info)" icon={Icon}>
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Tag>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.SUCCEEDED:
|
||||
return (
|
||||
<Tag className={className} style={style} color="var(--color-success)" icon={Icon}>
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Tag>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.FAILED:
|
||||
return (
|
||||
<Tag className={className} style={style} color="var(--color-error)" icon={Icon}>
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Tag>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.CANCELED:
|
||||
return (
|
||||
<Tag className={className} style={style} color="var(--color-danger)" icon={Icon}>
|
||||
{t("workflow_run.props.status.canceled")}
|
||||
</Tag>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
export default WorkflowStatusTag;
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
IconActivity,
|
||||
IconBrowserShare,
|
||||
@@ -20,14 +13,15 @@ import {
|
||||
IconUserShield,
|
||||
} from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, type TableProps, Tag, Typography, theme } from "antd";
|
||||
import { App, Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, type TableProps, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { get as getStatistics } from "@/api/statistics";
|
||||
import WorkflowRunDetailDrawer from "@/components/workflow/WorkflowRunDetailDrawer";
|
||||
import WorkflowStatusTag from "@/components/workflow/WorkflowStatusTag";
|
||||
import { type Statistics } from "@/domain/statistics";
|
||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { list as listWorkflowRuns } from "@/repository/workflowRun";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
@@ -102,35 +96,7 @@ const Dashboard = () => {
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
if (record.status === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||
return <Tag icon={<ClockCircleOutlinedIcon />}>{t("workflow_run.props.status.pending")}</Tag>;
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||
return (
|
||||
<Tag icon={<SyncOutlinedIcon spin />} color="processing">
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
return (
|
||||
<Tag icon={<CheckCircleOutlinedIcon />} color="success">
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
return (
|
||||
<Tag icon={<CloseCircleOutlinedIcon />} color="error">
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||
return (
|
||||
<Tag icon={<StopOutlinedIcon />} color="warning">
|
||||
{t("workflow_run.props.status.canceled")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
return <WorkflowStatusTag status={record.status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -162,9 +128,9 @@ const Dashboard = () => {
|
||||
align: "end",
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space.Compact>
|
||||
<div className="flex items-center justify-end">
|
||||
<WorkflowRunDetailDrawer data={record} trigger={<Button color="primary" icon={<IconBrowserShare size="1.25em" />} variant="text" />} />
|
||||
</Space.Compact>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { IconCopy, IconEdit, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Divider, Empty, Input, Menu, type MenuProps, Radio, Space, Switch, Table, type TableProps, Tooltip, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import WorkflowStatusIcon from "@/components/workflow/WorkflowStatusIcon";
|
||||
import { WORKFLOW_TRIGGERS, type WorkflowModel, cloneNode, initWorkflow, isAllNodesValidated } from "@/domain/workflow";
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
import { list as listWorkflows, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
@@ -136,22 +130,9 @@ const WorkflowList = () => {
|
||||
key: "lastRun",
|
||||
title: t("workflow.props.last_run_at"),
|
||||
render: (_, record) => {
|
||||
let icon = <></>;
|
||||
if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||
icon = <ClockCircleOutlinedIcon style={{ color: themeToken.colorTextSecondary }} />;
|
||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||
icon = <SyncOutlinedIcon style={{ color: themeToken.colorInfo }} spin />;
|
||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
icon = <CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />;
|
||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
icon = <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />;
|
||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.CANCELED) {
|
||||
icon = <StopOutlinedIcon style={{ color: themeToken.colorWarning }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Space>
|
||||
{icon}
|
||||
<WorkflowStatusIcon color={true} status={record.lastRunStatus!} />
|
||||
<Typography.Text>{record.lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""}</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
@@ -179,7 +160,7 @@ const WorkflowList = () => {
|
||||
fixed: "right",
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space.Compact>
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip title={t("workflow.action.edit")}>
|
||||
<Button
|
||||
color="primary"
|
||||
@@ -213,7 +194,7 @@ const WorkflowList = () => {
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -117,7 +117,7 @@ const WorkflowNew = () => {
|
||||
<div className="px-6 py-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<h1>{t("workflow.new.title")}</h1>
|
||||
<p className="text-base text-gray-500">{t("workflow.new.subtitle")}</p>
|
||||
<p className="mb-0 text-base text-gray-500">{t("workflow.new.subtitle")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user