feat(ui): replace some icons

This commit is contained in:
Fu Diwei
2025-07-21 13:41:36 +08:00
parent 27c8f6e7df
commit 03dcddb9e3
21 changed files with 296 additions and 308 deletions
-1
View File
@@ -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",
-1
View File
@@ -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",
+19
View File
@@ -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: {
+10 -8
View File
@@ -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]);
+5 -5
View File
@@ -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>
);
},
},
+6 -4
View File
@@ -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>
);
}
};
+21 -23
View File
@@ -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>
),
+22 -16
View File
@@ -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);
},
};
});
+3 -4
View File
@@ -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>
),
+25 -21
View File
@@ -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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</span>
<Typography.Text ellipsis type="warning">
<span className="mr-2 inline-block size-2 rounded-full bg-warning leading-2">&nbsp;</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">&nbsp;</span>
<Typography.Text ellipsis type="danger">
<span className="mr-2 inline-block size-2 rounded-full bg-error leading-2">&nbsp;</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>
),
+16 -12
View File
@@ -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} />;
},
+10 -2
View File
@@ -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);
+3 -3
View File
@@ -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>
),
+4 -6
View File
@@ -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>
),
+90 -96
View File
@@ -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>
);
};