mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(ui): replace some icons
This commit is contained in:
Generated
-1
@@ -8,7 +8,6 @@
|
||||
"name": "ui",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/pro-components": "^2.8.9",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/pro-components": "^2.8.9",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
|
||||
@@ -66,6 +66,25 @@ const RootApp = () => {
|
||||
colorSuccess: browserTheme === "dark" ? "#57ab5a" : "#1a7f37",
|
||||
colorWarning: browserTheme === "dark" ? "#daaa3f" : "#eac54f",
|
||||
colorError: browserTheme === "dark" ? "#e5534b" : "#d1242f",
|
||||
|
||||
/* @see https://tailwindcss.com/docs/responsive-design#overview */
|
||||
screenXS: 30 * 16,
|
||||
screenXSMin: 30 * 16,
|
||||
screenXSMax: 40 * 16 - 1,
|
||||
screenSM: 40 * 16,
|
||||
screenSMMin: 40 * 16,
|
||||
screenSMMax: 48 * 16 - 1,
|
||||
screenMD: 48 * 16,
|
||||
screenMDMin: 48 * 16,
|
||||
screenMDMax: 64 * 16 - 1,
|
||||
screenLG: 64 * 16,
|
||||
screenLGMin: 64 * 16,
|
||||
screenLGMax: 80 * 16 - 1,
|
||||
screenXL: 80 * 16,
|
||||
screenXLMin: 80 * 16,
|
||||
screenXLMax: 96 * 16 - 1,
|
||||
screenXXL: 96 * 16,
|
||||
screenXXLMin: 96 * 16,
|
||||
},
|
||||
components: {
|
||||
Layout: {
|
||||
|
||||
@@ -12,15 +12,17 @@ export const useAppThemeMenuItems = () => {
|
||||
|
||||
const { themeMode, setThemeMode } = useBrowserTheme();
|
||||
|
||||
const items = [
|
||||
["light", "common.theme.light", <IconSun size="1em" />],
|
||||
["dark", "common.theme.dark", <IconMoon size="1em" />],
|
||||
["system", "common.theme.system", <IconSunMoon size="1em" />],
|
||||
].map(([key, label, icon]) => {
|
||||
const items = (
|
||||
[
|
||||
["light", "common.theme.light", <IconSun size="1em" />],
|
||||
["dark", "common.theme.dark", <IconMoon size="1em" />],
|
||||
["system", "common.theme.system", <IconSunMoon size="1em" />],
|
||||
] satisfies Array<[string, string, React.ReactNode]>
|
||||
).map(([key, label, icon]) => {
|
||||
return {
|
||||
key: key as string,
|
||||
label: t(label as string),
|
||||
icon: icon as React.ReactElement,
|
||||
key: key,
|
||||
label: t(label),
|
||||
icon: icon,
|
||||
onClick: () => {
|
||||
if (key !== themeMode) {
|
||||
setThemeMode(key as Parameters<typeof setThemeMode>[0]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type ChangeEvent, forwardRef, useImperativeHandle, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconArrowDown, IconArrowUp, IconMinus, IconPlus } from "@tabler/icons-react";
|
||||
import { IconCircleArrowDown, IconCircleArrowUp, IconCircleMinus, IconCirclePlus } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Button, Input, type InputProps, type InputRef, Space } from "antd";
|
||||
import { produce } from "immer";
|
||||
@@ -210,17 +210,17 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
|
||||
|
||||
const upBtn = useMemo(() => {
|
||||
if (!showSortButton) return null;
|
||||
return <Button icon={<IconArrowUp size="1.25em" />} color="default" disabled={disabled || !allowUp} type="text" onClick={onEntryUp} />;
|
||||
return <Button icon={<IconCircleArrowUp size="1.25em" />} color="default" disabled={disabled || !allowUp} type="text" onClick={onEntryUp} />;
|
||||
}, [allowUp, disabled, showSortButton, onEntryUp]);
|
||||
const downBtn = useMemo(() => {
|
||||
if (!showSortButton) return null;
|
||||
return <Button icon={<IconArrowDown size="1.25em" />} color="default" disabled={disabled || !allowDown} type="text" onClick={onEntryDown} />;
|
||||
return <Button icon={<IconCircleArrowDown size="1.25em" />} color="default" disabled={disabled || !allowDown} type="text" onClick={onEntryDown} />;
|
||||
}, [allowDown, disabled, showSortButton, onEntryDown]);
|
||||
const removeBtn = useMemo(() => {
|
||||
return <Button icon={<IconMinus size="1.25em" />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onEntryRemove} />;
|
||||
return <Button icon={<IconCircleMinus size="1.25em" />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onEntryRemove} />;
|
||||
}, [allowRemove, disabled, onEntryRemove]);
|
||||
const addBtn = useMemo(() => {
|
||||
return <Button icon={<IconPlus size="1.25em" />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onEntryAdd} />;
|
||||
return <Button icon={<IconCirclePlus size="1.25em" />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onEntryAdd} />;
|
||||
}, [allowAdd, disabled, onEntryAdd]);
|
||||
|
||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconChevronDown, IconChevronUp, IconPlus, IconX } from "@tabler/icons-react";
|
||||
import { IconCircleArrowDown, IconCircleArrowUp, IconCircleMinus, IconCirclePlus } from "@tabler/icons-react";
|
||||
import { Button, Collapse, Form, type FormInstance, Input, InputNumber, Select, Space } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod/v4";
|
||||
@@ -246,7 +246,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
||||
extra: (
|
||||
<div className="flex items-center justify-end">
|
||||
<Button
|
||||
icon={<IconChevronUp size="1.25em" />}
|
||||
icon={<IconCircleArrowUp size="1.25em" />}
|
||||
color="default"
|
||||
disabled={disabled || index === 0}
|
||||
size="small"
|
||||
@@ -257,7 +257,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconChevronDown size="1.25em" />}
|
||||
icon={<IconCircleArrowDown size="1.25em" />}
|
||||
color="default"
|
||||
disabled={disabled || index === fields.length - 1}
|
||||
size="small"
|
||||
@@ -268,7 +268,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconX size="1.25em" />}
|
||||
icon={<IconCircleMinus size="1.25em" />}
|
||||
color="default"
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
@@ -285,7 +285,7 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
<Button className="w-full" type="dashed" icon={<IconPlus size="1.25em" />} onClick={() => add(initFormModel())}>
|
||||
<Button className="w-full" type="dashed" icon={<IconCirclePlus size="1.25em" />} onClick={() => add(initFormModel())}>
|
||||
{t("access.form.ssh_jump_servers.add")}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@@ -89,45 +89,31 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
const [showWhitespace, setShowWhitespace] = useState(true);
|
||||
|
||||
const renderBadge = () => {
|
||||
let color: string | undefined;
|
||||
|
||||
switch (runStatus) {
|
||||
case WORKFLOW_RUN_STATUSES.PENDING:
|
||||
return (
|
||||
<Flex gap="small">
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.pending")}
|
||||
</Flex>
|
||||
);
|
||||
break;
|
||||
case WORKFLOW_RUN_STATUSES.RUNNING:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorInfo }}>
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Flex>
|
||||
);
|
||||
color = themeToken.colorInfo;
|
||||
break;
|
||||
case WORKFLOW_RUN_STATUSES.SUCCEEDED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorSuccess }}>
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Flex>
|
||||
);
|
||||
color = themeToken.colorSuccess;
|
||||
break;
|
||||
case WORKFLOW_RUN_STATUSES.FAILED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorError }}>
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Flex>
|
||||
);
|
||||
color = themeToken.colorError;
|
||||
break;
|
||||
case WORKFLOW_RUN_STATUSES.CANCELED:
|
||||
return (
|
||||
<Flex gap="small" style={{ color: themeToken.colorWarning }}>
|
||||
<WorkflowStatusIcon status={runStatus} />
|
||||
{t("workflow_run.props.status.canceled")}
|
||||
</Flex>
|
||||
);
|
||||
color = themeToken.colorWarning;
|
||||
break;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
return (
|
||||
<Flex gap="small" style={{ color: color }}>
|
||||
<WorkflowStatusIcon size="1.25em" status={runStatus} />
|
||||
{t(`workflow_run.props.status.${runStatus}`)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRecord = (record: Log) => {
|
||||
@@ -294,12 +280,13 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
{
|
||||
key: "name",
|
||||
title: t("workflow_run_artifact.props.name"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Typography.Text delete={!!record.deleted} ellipsis>
|
||||
{record.subjectAltNames}
|
||||
</Typography.Text>
|
||||
<div className="max-w-full truncate">
|
||||
<Typography.Text delete={!!record.deleted} ellipsis>
|
||||
{record.subjectAltNames}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -45,13 +45,11 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
{
|
||||
key: "id",
|
||||
title: t("workflow_run.props.id"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => <span className="font-mono">{record.id}</span>,
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return <WorkflowStatusTag status={record.status} />;
|
||||
},
|
||||
@@ -243,7 +241,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
/>
|
||||
),
|
||||
icon: (
|
||||
<span className="anticon">
|
||||
<span className="anticon" role="img">
|
||||
<IconTrash className="text-error" size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -273,7 +271,11 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
locale={{
|
||||
emptyText: loading ? <Skeleton /> : <Empty title={t("common.text.nodata")} icon={<IconHistory size={24} />} />,
|
||||
emptyText: loading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
<Empty title={t("common.text.nodata")} description={loadedError ? getErrMsg(loadedError) : undefined} icon={<IconHistory size={24} />} />
|
||||
),
|
||||
}}
|
||||
pagination={{
|
||||
current: page,
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
StopOutlined as StopOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { IconCircleCheck, IconCircleDashed, IconCircleOff, IconCircleX, IconClock, IconLoader3 } from "@tabler/icons-react";
|
||||
import { theme } from "antd";
|
||||
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
@@ -18,7 +12,9 @@ export type WorkflowStatusIconProps = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
const WorkflowStatusIcon = ({ className, style, color, size, status }: WorkflowStatusIconProps) => {
|
||||
const WorkflowStatusIcon = ({ className, style, color, status, ...props }: WorkflowStatusIconProps) => {
|
||||
const { size = "1em" } = props;
|
||||
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
switch (status) {
|
||||
@@ -27,74 +23,55 @@ const WorkflowStatusIcon = ({ className, style, color, size, status }: WorkflowS
|
||||
color = themeToken.colorTextSecondary;
|
||||
}
|
||||
return (
|
||||
<ClockCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
<span className={mergeCls("anticon", className)} style={style} role="img">
|
||||
<IconClock color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.RUNNING:
|
||||
if (color === true) {
|
||||
color = themeToken.colorInfo;
|
||||
}
|
||||
return (
|
||||
<SyncOutlinedIcon
|
||||
className={mergeCls("animate-spin", className)}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
<span className={mergeCls("anticon", "animate-spin", className)} style={style} role="img">
|
||||
<IconLoader3 color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.SUCCEEDED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorSuccess;
|
||||
console.log(color);
|
||||
}
|
||||
return (
|
||||
<CheckCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
<span className={mergeCls("anticon", className)} style={style} role="img">
|
||||
<IconCircleCheck color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.FAILED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorError;
|
||||
}
|
||||
return (
|
||||
<CloseCircleOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
<span className={mergeCls("anticon", className)} style={style} role="img">
|
||||
<IconCircleX color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
case WORKFLOW_RUN_STATUSES.CANCELED:
|
||||
if (color === true) {
|
||||
color = themeToken.colorWarning;
|
||||
}
|
||||
return (
|
||||
<StopOutlinedIcon
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
color: color,
|
||||
fontSize: size,
|
||||
}}
|
||||
/>
|
||||
<span className={mergeCls("anticon", className)} style={style} role="img">
|
||||
<IconCircleOff color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
if (color === true) {
|
||||
color = themeToken.colorTextSecondary;
|
||||
}
|
||||
return (
|
||||
<span className={mergeCls("anticon", className)} style={style} role="img">
|
||||
<IconCircleDashed color={color} size={size} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { memo, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
CloudUploadOutlined as CloudUploadOutlinedIcon,
|
||||
DeploymentUnitOutlined as DeploymentUnitOutlinedIcon,
|
||||
MonitorOutlined as MonitorOutlinedIcon,
|
||||
SendOutlined as SendOutlinedIcon,
|
||||
SisternodeOutlined as SisternodeOutlinedIcon,
|
||||
SolutionOutlined as SolutionOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import { IconCloudUpload, IconContract, IconDeviceDesktopSearch, IconPackage, IconPlus, IconSend, IconSitemap } from "@tabler/icons-react";
|
||||
import { Dropdown } from "antd";
|
||||
|
||||
import { WorkflowNodeType, newNode } from "@/domain/workflow";
|
||||
@@ -25,15 +17,17 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
|
||||
const { addNode } = useWorkflowStore(useZustandShallowSelector(["addNode"]));
|
||||
|
||||
const dropdownMenus = useMemo(() => {
|
||||
return [
|
||||
[WorkflowNodeType.Apply, "workflow_node.apply.label", <SolutionOutlinedIcon />],
|
||||
[WorkflowNodeType.Upload, "workflow_node.upload.label", <CloudUploadOutlinedIcon />],
|
||||
[WorkflowNodeType.Monitor, "workflow_node.monitor.label", <MonitorOutlinedIcon />],
|
||||
[WorkflowNodeType.Deploy, "workflow_node.deploy.label", <DeploymentUnitOutlinedIcon />],
|
||||
[WorkflowNodeType.Notify, "workflow_node.notify.label", <SendOutlinedIcon />],
|
||||
[WorkflowNodeType.Branch, "workflow_node.branch.label", <SisternodeOutlinedIcon />],
|
||||
[WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", <SisternodeOutlinedIcon />],
|
||||
]
|
||||
return (
|
||||
[
|
||||
[WorkflowNodeType.Apply, "workflow_node.apply.label", <IconContract size="1em" />],
|
||||
[WorkflowNodeType.Upload, "workflow_node.upload.label", <IconCloudUpload size="1em" />],
|
||||
[WorkflowNodeType.Monitor, "workflow_node.monitor.label", <IconDeviceDesktopSearch size="1em" />],
|
||||
[WorkflowNodeType.Deploy, "workflow_node.deploy.label", <IconPackage size="1em" />],
|
||||
[WorkflowNodeType.Notify, "workflow_node.notify.label", <IconSend size="1em" />],
|
||||
[WorkflowNodeType.Branch, "workflow_node.branch.label", <IconSitemap size="1em" />],
|
||||
[WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", <IconSitemap size="1em" />],
|
||||
] satisfies Array<[WorkflowNodeType, string, React.ReactNode]>
|
||||
)
|
||||
.filter(([type]) => {
|
||||
const hasExecuteResult = [
|
||||
WorkflowNodeType.Apply,
|
||||
@@ -50,17 +44,21 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
|
||||
})
|
||||
.map(([type, label, icon]) => {
|
||||
return {
|
||||
key: type as string,
|
||||
key: type,
|
||||
disabled: disabled,
|
||||
label: t(label as string),
|
||||
icon: icon,
|
||||
label: t(label),
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
{icon}
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
const nextNode = newNode(type as WorkflowNodeType);
|
||||
const nextNode = newNode(type);
|
||||
addNode(nextNode, node.id);
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [node.id, disabled, node.type]);
|
||||
}, [node.id, node.type, disabled]);
|
||||
|
||||
return (
|
||||
<div className="relative py-6 before:absolute before:top-0 before:left-1/2 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
import { IconChevronRight, IconHelp, IconPlus, IconX } from "@tabler/icons-react";
|
||||
import { IconChevronRight, IconCircleMinus, IconHelp, IconPlus } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { AutoComplete, Button, Divider, Flex, Form, type FormInstance, Input, InputNumber, Select, Switch, Tooltip, Typography } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
@@ -607,7 +607,7 @@ const EmailInput = memo(
|
||||
<Button
|
||||
color="default"
|
||||
disabled={disabled}
|
||||
icon={<IconX size="1.25em" />}
|
||||
icon={<IconCircleMinus size="1.25em" />}
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconPlus, IconX } from "@tabler/icons-react";
|
||||
import { IconCircleMinus, IconCirclePlus } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Button, Form, Input, Radio, Select, theme } from "antd";
|
||||
|
||||
@@ -368,20 +368,12 @@ const ConditionNodeConfigFormExpressionEditor = forwardRef<ConditionNodeConfigFo
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Button
|
||||
className="my-1"
|
||||
color="default"
|
||||
disabled={disabled}
|
||||
icon={<IconX size="1.25em" />}
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
<Button color="default" disabled={disabled} icon={<IconCircleMinus size="1.25em" />} type="text" onClick={() => remove(index)} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Form.Item>
|
||||
<Button type="dashed" block icon={<IconPlus size="1.25em" />} onClick={() => add({})}>
|
||||
<Button type="dashed" block icon={<IconCirclePlus size="1.25em" />} onClick={() => add({})}>
|
||||
{t("workflow_node.condition.form.expression.add_condition.button")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
@@ -145,7 +145,7 @@ const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex,
|
||||
disabled: disabled,
|
||||
label: isNodeBranchLike(node) ? t("workflow_node.action.rename_branch") : t("workflow_node.action.rename_node"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconLabel size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -180,7 +180,7 @@ const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex,
|
||||
disabled: disabled || isNodeUnduplicatable(node),
|
||||
label: isNodeBranchLike(node) ? t("workflow_node.action.duplicate_branch") : t("workflow_node.action.duplicate_node"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconCopy size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -194,7 +194,7 @@ const SharedNodeMenu = ({ menus, trigger, node, disabled, branchId, branchIndex,
|
||||
disabled: disabled || isNodeUnremovable(node),
|
||||
label: isNodeBranchLike(node) ? t("workflow_node.action.remove_branch") : t("workflow_node.action.remove_node"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconTrashX size="1em" />
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -79,7 +79,7 @@ const ConsoleLayout = () => {
|
||||
{
|
||||
key: "theme",
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<AppTheme.Icon size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -89,7 +89,7 @@ const ConsoleLayout = () => {
|
||||
{
|
||||
key: "locale",
|
||||
icon: (
|
||||
<span className="anticon scale-115">
|
||||
<span className="anticon scale-115" role="img">
|
||||
<AppLocale.Icon size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -99,7 +99,7 @@ const ConsoleLayout = () => {
|
||||
{
|
||||
key: "document",
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconHelpCircle size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -110,7 +110,7 @@ const ConsoleLayout = () => {
|
||||
key: "logout",
|
||||
danger: true,
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconLogout size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -166,20 +166,26 @@ const SiderMenu = memo(({ onSelect }: { onSelect?: (key: string) => void }) => {
|
||||
const MENU_KEY_CERTIFICATES = "/certificates";
|
||||
const MENU_KEY_ACCESSES = "/accesses";
|
||||
const MENU_KEY_SETTINGS = "/settings";
|
||||
const menuItems: Required<MenuProps>["items"] = [
|
||||
[MENU_KEY_HOME, <IconHome size="1em" />, t("dashboard.page.title")],
|
||||
[MENU_KEY_WORKFLOWS, <IconSchema size="1em" />, t("workflow.page.title")],
|
||||
[MENU_KEY_CERTIFICATES, <IconShieldCheckered size="1em" />, t("certificate.page.title")],
|
||||
[MENU_KEY_ACCESSES, <IconFingerprint size="1em" />, t("access.page.title")],
|
||||
[MENU_KEY_SETTINGS, <IconSettings size="1em" />, t("settings.page.title")],
|
||||
].map(([key, icon, label]) => {
|
||||
const menuItems: Required<MenuProps>["items"] = (
|
||||
[
|
||||
[MENU_KEY_HOME, "dashboard.page.title", <IconHome size="1em" />],
|
||||
[MENU_KEY_WORKFLOWS, "workflow.page.title", <IconSchema size="1em" />],
|
||||
[MENU_KEY_CERTIFICATES, "certificate.page.title", <IconShieldCheckered size="1em" />],
|
||||
[MENU_KEY_ACCESSES, "access.page.title", <IconFingerprint size="1em" />],
|
||||
[MENU_KEY_SETTINGS, "settings.page.title", <IconSettings size="1em" />],
|
||||
] satisfies Array<[string, string, React.ReactNode]>
|
||||
).map(([key, label, icon]) => {
|
||||
return {
|
||||
key: key as string,
|
||||
icon: <span className="anticon scale-125">{icon}</span>,
|
||||
label: label,
|
||||
key: key,
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
{icon}
|
||||
</span>
|
||||
),
|
||||
label: t(label),
|
||||
onClick: () => {
|
||||
navigate(key as string);
|
||||
onSelect?.(key as string);
|
||||
navigate(key);
|
||||
onSelect?.(key);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -48,12 +48,11 @@ const AccessList = () => {
|
||||
{
|
||||
key: "name",
|
||||
title: t("access.props.name"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<div className="flex max-w-full items-center gap-4 truncate overflow-hidden">
|
||||
<div className="flex max-w-full items-center gap-4 overflow-hidden">
|
||||
<Avatar shape="square" src={accessProvidersMap.get(record.provider)?.icon} size={28} />
|
||||
<div className="flex max-w-full flex-col gap-1">
|
||||
<div className="flex max-w-full flex-col gap-1 truncate">
|
||||
<Typography.Text ellipsis>{record.name || "\u00A0"}</Typography.Text>
|
||||
<Typography.Text ellipsis type="secondary">
|
||||
{t(accessProvidersMap.get(record.provider)?.name ?? "") || "\u00A0"}
|
||||
@@ -204,7 +203,7 @@ const AccessList = () => {
|
||||
title: <span className="text-error">{t("access.action.delete")}</span>,
|
||||
content: <span dangerouslySetInnerHTML={{ __html: t("access.action.delete.confirm", { name: access.name }) }} />,
|
||||
icon: (
|
||||
<span className="anticon">
|
||||
<span className="anticon" role="img">
|
||||
<IconTrash className="text-error" size="1em" />
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -49,36 +49,41 @@ const CertificateList = () => {
|
||||
{
|
||||
key: "expiry",
|
||||
title: t("certificate.props.validity"),
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
sortOrder: sorter.columnKey === "expiry" ? sorter.order : undefined,
|
||||
render: (_, record) => {
|
||||
const total = dayjs(record.expireAt).diff(dayjs(record.created), "d") + 1;
|
||||
const expired = dayjs().isAfter(dayjs(record.expireAt));
|
||||
const leftDays = dayjs(record.expireAt).diff(dayjs(), "d");
|
||||
const isExpired = dayjs().isAfter(dayjs(record.expireAt));
|
||||
const leftHours = dayjs(record.expireAt).diff(dayjs(), "h");
|
||||
const leftDays = Math.round(leftHours / 24);
|
||||
|
||||
return (
|
||||
<div className="flex max-w-full flex-col gap-1">
|
||||
{!expired ? (
|
||||
leftDays >= 1 ? (
|
||||
<Typography.Text type="success">
|
||||
<span className="mr-1 inline-block size-2 rounded-full bg-success leading-2"> </span>
|
||||
<div className="flex max-w-full flex-col gap-1 truncate">
|
||||
{!isExpired ? (
|
||||
leftDays >= 20 ? (
|
||||
<Typography.Text ellipsis type="success">
|
||||
<span className="mr-2 inline-block size-2 rounded-full bg-success leading-2"> </span>
|
||||
{t("certificate.props.validity.left_days", { left: leftDays, total })}
|
||||
</Typography.Text>
|
||||
) : leftDays >= 1 ? (
|
||||
<Typography.Text ellipsis type="warning">
|
||||
<span className="mr-2 inline-block size-2 rounded-full bg-warning leading-2"> </span>
|
||||
{t("certificate.props.validity.left_days", { left: leftDays, total })}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text type="warning">
|
||||
<span className="mr-1 inline-block size-2 rounded-full bg-warning leading-2"> </span>
|
||||
<Typography.Text ellipsis type="warning">
|
||||
<span className="mr-2 inline-block size-2 rounded-full bg-warning leading-2"> </span>
|
||||
{t("certificate.props.validity.less_than_a_day")}
|
||||
</Typography.Text>
|
||||
)
|
||||
) : (
|
||||
<Typography.Text type="danger">
|
||||
<span className="mr-1 inline-block size-2 rounded-full bg-error leading-2"> </span>
|
||||
<Typography.Text ellipsis type="danger">
|
||||
<span className="mr-2 inline-block size-2 rounded-full bg-error leading-2"> </span>
|
||||
{t("certificate.props.validity.expired")}
|
||||
</Typography.Text>
|
||||
)}
|
||||
|
||||
<Typography.Text type="secondary">
|
||||
<Typography.Text ellipsis type="secondary">
|
||||
{t("certificate.props.validity.expiration", { date: dayjs(record.expireAt).format("YYYY-MM-DD") })}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
@@ -89,24 +94,23 @@ const CertificateList = () => {
|
||||
key: "brand",
|
||||
title: t("certificate.props.brand"),
|
||||
render: (_, record) => (
|
||||
<div className="flex max-w-full flex-col gap-1">
|
||||
<Typography.Text>{record.issuerOrg || "\u00A0"}</Typography.Text>
|
||||
<Typography.Text>{record.keyAlgorithm || "\u00A0"}</Typography.Text>
|
||||
<div className="flex max-w-full flex-col gap-1 truncate">
|
||||
<Typography.Text ellipsis>{record.issuerOrg || "\u00A0"}</Typography.Text>
|
||||
<Typography.Text ellipsis>{record.keyAlgorithm || "\u00A0"}</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "source",
|
||||
title: t("certificate.props.source"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
const workflowId = record.workflowId;
|
||||
return (
|
||||
<div className="flex max-w-full flex-col gap-1">
|
||||
<Typography.Text>{t(`certificate.props.source.${record.source}`)}</Typography.Text>
|
||||
<div className="flex max-w-full flex-col gap-1 truncate">
|
||||
<Typography.Text ellipsis>{t(`certificate.props.source.${record.source}`)}</Typography.Text>
|
||||
<Typography.Link
|
||||
type="secondary"
|
||||
ellipsis
|
||||
type="secondary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (workflowId) {
|
||||
@@ -226,7 +230,7 @@ const CertificateList = () => {
|
||||
title: <span className="text-error">{t("certificate.action.delete")}</span>,
|
||||
content: <span dangerouslySetInnerHTML={{ __html: t("certificate.action.delete.confirm", { name: certificate.subjectAltNames }) }} />,
|
||||
icon: (
|
||||
<span className="anticon">
|
||||
<span className="anticon" role="img">
|
||||
<IconTrash className="text-error" size="1em" />
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -189,8 +189,12 @@ const StatisticCards = () => {
|
||||
};
|
||||
|
||||
const WorkflowRunHistoryTable = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { notification } = App.useApp();
|
||||
|
||||
const tableColumns: TableProps<WorkflowRunModel>["columns"] = [
|
||||
{
|
||||
key: "$index",
|
||||
@@ -202,27 +206,27 @@ const WorkflowRunHistoryTable = () => {
|
||||
{
|
||||
key: "name",
|
||||
title: t("workflow.props.name"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
const workflow = record.expand?.workflowId;
|
||||
return (
|
||||
<Typography.Link
|
||||
ellipsis
|
||||
onClick={() => {
|
||||
if (workflow) {
|
||||
navigate(`/workflows/${workflow.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{workflow?.name ?? <span className="font-mono">{t(`#${record.workflowId}`)}</span>}
|
||||
</Typography.Link>
|
||||
<div className="max-w-full truncate">
|
||||
<Typography.Link
|
||||
ellipsis
|
||||
onClick={() => {
|
||||
if (workflow) {
|
||||
navigate(`/workflows/${workflow.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{workflow?.name ?? <span className="font-mono">{t(`#${record.workflowId}`)}</span>}
|
||||
</Typography.Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return <WorkflowStatusTag status={record.status} />;
|
||||
},
|
||||
|
||||
@@ -40,7 +40,11 @@ const Settings = () => {
|
||||
items={menus.map(([key, label, icon]) => ({
|
||||
key,
|
||||
label: t(label),
|
||||
icon: <span className="anticon scale-125">{icon}</span>,
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
{icon}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
onClick={({ key }) => {
|
||||
setMenuValue(key);
|
||||
@@ -58,7 +62,11 @@ const Settings = () => {
|
||||
items={menus.map(([key, label, icon]) => ({
|
||||
key,
|
||||
label: t(label),
|
||||
icon: <span className="anticon scale-125">{icon}</span>,
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
{icon}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
onClick={({ key }) => {
|
||||
setMenuValue(key);
|
||||
|
||||
@@ -93,7 +93,7 @@ const WorkflowDetail = () => {
|
||||
title: <span className="text-error">{t("workflow.action.delete")}</span>,
|
||||
content: <span dangerouslySetInnerHTML={{ __html: t("workflow.action.delete.confirm", { name: workflow.name }) }} />,
|
||||
icon: (
|
||||
<span className="anticon">
|
||||
<span className="anticon" role="img">
|
||||
<IconTrash className="text-error" size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -246,7 +246,7 @@ const WorkflowDetail = () => {
|
||||
key: "orchestration",
|
||||
label: t("workflow.detail.orchestration.tab"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconRobot size="1em" />
|
||||
</span>
|
||||
),
|
||||
@@ -255,7 +255,7 @@ const WorkflowDetail = () => {
|
||||
key: "runs",
|
||||
label: t("workflow.detail.runs.tab"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconHistory size="1em" />
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -41,9 +41,8 @@ const WorkflowList = () => {
|
||||
{
|
||||
key: "name",
|
||||
title: t("workflow.props.name"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => (
|
||||
<div className="flex max-w-full flex-col gap-1">
|
||||
<div className="flex max-w-full flex-col gap-1 truncate">
|
||||
<Typography.Text ellipsis>{record.name || "\u00A0"}</Typography.Text>
|
||||
<Typography.Text ellipsis type="secondary">
|
||||
{record.description || "\u00A0"}
|
||||
@@ -54,7 +53,6 @@ const WorkflowList = () => {
|
||||
{
|
||||
key: "trigger",
|
||||
title: t("workflow.props.trigger"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
const trigger = record.trigger;
|
||||
if (!trigger) {
|
||||
@@ -99,7 +97,7 @@ const WorkflowList = () => {
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Flex gap="small">
|
||||
<WorkflowStatusIcon color={true} status={record.lastRunStatus!} />
|
||||
<WorkflowStatusIcon color={true} size="1.25em" status={record.lastRunStatus!} />
|
||||
<Typography.Text>{record.lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""}</Typography.Text>
|
||||
</Flex>
|
||||
);
|
||||
@@ -137,7 +135,7 @@ const WorkflowList = () => {
|
||||
color="primary"
|
||||
icon={<IconCopy size="1.25em" />}
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRecordDuplicateClick(record);
|
||||
}}
|
||||
@@ -276,7 +274,7 @@ const WorkflowList = () => {
|
||||
title: <span className="text-error">{t("workflow.action.delete")}</span>,
|
||||
content: <span dangerouslySetInnerHTML={{ __html: t("workflow.action.delete.confirm", { name: workflow.name }) }} />,
|
||||
icon: (
|
||||
<span className="anticon">
|
||||
<span className="anticon" role="img">
|
||||
<IconTrash className="text-error" size="1em" />
|
||||
</span>
|
||||
),
|
||||
|
||||
@@ -25,7 +25,7 @@ const WorkflowNew = () => {
|
||||
|
||||
const templateGridSpans = {
|
||||
xs: { flex: "100%" },
|
||||
md: { flex: "100%" },
|
||||
md: { flex: "50%" },
|
||||
lg: { flex: "50%" },
|
||||
xl: { flex: "33.3333%" },
|
||||
xxl: { flex: "33.3333%" },
|
||||
@@ -112,102 +112,96 @@ const WorkflowNew = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card styles={{ body: { padding: 0 } }}>
|
||||
<div className="px-6 py-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<h1>{t("workflow.new.title")}</h1>
|
||||
<p className="mb-0 text-base text-gray-500">{t("workflow.new.subtitle")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<Typography.Text type="secondary">
|
||||
<div className="mt-4 mb-8 text-xl">{t("workflow.new.templates.title")}</div>
|
||||
</Typography.Text>
|
||||
|
||||
<Row className="justify-stretch" gutter={[16, 16]}>
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-standard.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_STANDARD)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.standard.title")}
|
||||
description={t("workflow.new.templates.template.standard.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_STANDARD} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-certtest.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_CERTTEST)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.certtest.title")}
|
||||
description={t("workflow.new.templates.template.certtest.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_CERTTEST} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-blank.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_EMPTY)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.empty.title")}
|
||||
description={t("workflow.new.templates.template.empty.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_EMPTY} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<ModalForm
|
||||
{...formProps}
|
||||
autoFocus
|
||||
disabled={formPending}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
okText={t("common.button.submit")}
|
||||
open={formModalOpen}
|
||||
title={t(`workflow.new.modal.title`)}
|
||||
width={480}
|
||||
onFinish={handleModalFormFinish}
|
||||
onOpenChange={handleModalOpenChange}
|
||||
>
|
||||
<Form.Item name="name" label={t("workflow.new.modal.form.name.label")} rules={[formRule]}>
|
||||
<Input ref={inputRef} autoFocus placeholder={t("workflow.new.modal.form.name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label={t("workflow.new.modal.form.description.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow.new.modal.form.description.placeholder")} />
|
||||
</Form.Item>
|
||||
</ModalForm>
|
||||
<div className="px-6 py-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<h1>{t("workflow.new.title")}</h1>
|
||||
<p className="mb-0 text-base text-gray-500">{t("workflow.new.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-320">
|
||||
<Typography.Text type="secondary">
|
||||
<div className="mt-12 mb-8 text-center text-xl">{t("workflow.new.templates.title")}</div>
|
||||
</Typography.Text>
|
||||
|
||||
<Row className="justify-stretch" gutter={[16, 16]}>
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-standard.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_STANDARD)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.standard.title")}
|
||||
description={t("workflow.new.templates.template.standard.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_STANDARD} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-certtest.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_CERTTEST)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.certtest.title")}
|
||||
description={t("workflow.new.templates.template.certtest.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_CERTTEST} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-blank.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateClick(TEMPLATE_KEY_EMPTY)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card.Meta
|
||||
className="grow"
|
||||
title={t("workflow.new.templates.template.empty.title")}
|
||||
description={t("workflow.new.templates.template.empty.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_EMPTY} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<ModalForm
|
||||
{...formProps}
|
||||
autoFocus
|
||||
disabled={formPending}
|
||||
layout="vertical"
|
||||
form={formInst}
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
okText={t("common.button.submit")}
|
||||
open={formModalOpen}
|
||||
title={t(`workflow.new.modal.title`)}
|
||||
width={480}
|
||||
onFinish={handleModalFormFinish}
|
||||
onOpenChange={handleModalOpenChange}
|
||||
>
|
||||
<Form.Item name="name" label={t("workflow.new.modal.form.name.label")} rules={[formRule]}>
|
||||
<Input ref={inputRef} autoFocus placeholder={t("workflow.new.modal.form.name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label={t("workflow.new.modal.form.description.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow.new.modal.form.description.placeholder")} />
|
||||
</Form.Item>
|
||||
</ModalForm>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user