feat(ui): add @ant-design/icons

This commit is contained in:
Fu Diwei 2024-12-26 13:02:22 +08:00
parent 8a816ba44f
commit dae6ad2951
20 changed files with 111 additions and 82 deletions

1
ui/package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "ui", "name": "ui",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.5.2",
"@ant-design/pro-components": "^2.8.2", "@ant-design/pro-components": "^2.8.2",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",

View File

@ -10,6 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.5.2",
"@ant-design/pro-components": "^2.8.2", "@ant-design/pro-components": "^2.8.2",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",

View File

@ -3,9 +3,9 @@ import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDeepCompareEffect } from "ahooks"; import { useDeepCompareEffect } from "ahooks";
import { Button, Form, Input, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd"; import { Button, Form, Input, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { useAntdForm } from "@/hooks"; import { useAntdForm } from "@/hooks";
import { type KubernetesAccessConfig } from "@/domain/access"; import { type KubernetesAccessConfig } from "@/domain/access";
@ -78,7 +78,7 @@ const AccessEditFormKubernetesConfig = ({ form, formName, disabled, initialValue
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
> >
<Upload beforeUpload={() => false} fileList={kubeFileList} maxCount={1} onChange={handleKubeFileChange}> <Upload beforeUpload={() => false} fileList={kubeFileList} maxCount={1} onChange={handleKubeFileChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.k8s_kubeconfig.upload")}</Button> <Button icon={<UploadOutlinedIcon />}>{t("access.form.k8s_kubeconfig.upload")}</Button>
</Upload> </Upload>
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -3,9 +3,9 @@ import { flushSync } from "react-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDeepCompareEffect } from "ahooks"; import { useDeepCompareEffect } from "ahooks";
import { Button, Form, Input, InputNumber, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd"; import { Button, Form, Input, InputNumber, Upload, type FormInstance, type UploadFile, type UploadProps } from "antd";
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod"; import { z } from "zod";
import { Upload as UploadIcon } from "lucide-react";
import { useAntdForm } from "@/hooks"; import { useAntdForm } from "@/hooks";
import { type SSHAccessConfig } from "@/domain/access"; import { type SSHAccessConfig } from "@/domain/access";
@ -136,7 +136,7 @@ const AccessEditFormSSHConfig = ({ form, formName, disabled, initialValues, onVa
</Form.Item> </Form.Item>
<Form.Item label={t("access.form.ssh_key.label")} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}> <Form.Item label={t("access.form.ssh_key.label")} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}>
<Upload beforeUpload={() => false} fileList={keyFileList} maxCount={1} onChange={handleKeyFileChange}> <Upload beforeUpload={() => false} fileList={keyFileList} maxCount={1} onChange={handleKeyFileChange}>
<Button icon={<UploadIcon size={16} />}>{t("access.form.ssh_key.upload")}</Button> <Button icon={<UploadOutlinedIcon />}>{t("access.form.ssh_key.upload")}</Button>
</Upload> </Upload>
</Form.Item> </Form.Item>
</div> </div>

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, Dropdown, Form, Input, message, Space, Tooltip } from "antd"; import { Button, Dropdown, Form, Input, message, Space, Tooltip } from "antd";
import { CopyOutlined as CopyOutlinedIcon, DownOutlined as DownOutlinedIcon, LikeOutlined as LikeOutlinedIcon } from "@ant-design/icons";
import { CopyToClipboard } from "react-copy-to-clipboard"; import { CopyToClipboard } from "react-copy-to-clipboard";
import { ChevronDown as ChevronDownIcon, Clipboard as ClipboardIcon, ThumbsUp as ThumbsUpIcon } from "lucide-react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { type CertificateModel } from "@/domain/certificate"; import { type CertificateModel } from "@/domain/certificate";
@ -53,7 +53,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
messageApi.success(t("common.text.copied")); messageApi.success(t("common.text.copied"));
}} }}
> >
<Button type="text" icon={<ClipboardIcon size={14} />}></Button> <Button size="small" type="text" icon={<CopyOutlinedIcon />}></Button>
</CopyToClipboard> </CopyToClipboard>
</Tooltip> </Tooltip>
</div> </div>
@ -70,7 +70,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
messageApi.success(t("common.text.copied")); messageApi.success(t("common.text.copied"));
}} }}
> >
<Button type="text" icon={<ClipboardIcon size={14} />}></Button> <Button size="small" type="text" icon={<CopyOutlinedIcon />}></Button>
</CopyToClipboard> </CopyToClipboard>
</Tooltip> </Tooltip>
</div> </div>
@ -85,7 +85,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
{ {
key: "PEM", key: "PEM",
label: "PEM", label: "PEM",
extra: <ThumbsUpIcon size="14" />, extra: <LikeOutlinedIcon />,
onClick: () => handleDownloadPEMClick(), onClick: () => handleDownloadPEMClick(),
}, },
{ {
@ -110,7 +110,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
<Button type="primary"> <Button type="primary">
<Space> <Space>
<span>{t("certificate.action.download")}</span> <span>{t("certificate.action.download")}</span>
<ChevronDownIcon size={14} /> <DownOutlinedIcon />
</Space> </Space>
</Button> </Button>
</Dropdown> </Dropdown>

View File

@ -2,8 +2,13 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useControllableValue } from "ahooks"; import { useControllableValue } from "ahooks";
import { Button, Input, Space, type InputRef, type InputProps } from "antd"; import { Button, Input, Space, type InputRef, type InputProps } from "antd";
import {
DownOutlined as DownOutlinedIcon,
MinusOutlined as MinusOutlinedIcon,
PlusOutlined as PlusOutlinedIcon,
UpOutlined as UpOutlinedIcon,
} from "@ant-design/icons";
import { produce } from "immer"; import { produce } from "immer";
import { ArrowDown as ArrowDownIcon, ArrowUp as ArrowUpIcon, Minus as MinusIcon, Plus as PlusIcon } from "lucide-react";
export type MultipleInputProps = Omit<InputProps, "count" | "defaultValue" | "showCount" | "value" | "onChange" | "onPressEnter" | "onClear"> & { export type MultipleInputProps = Omit<InputProps, "count" | "defaultValue" | "showCount" | "value" | "onChange" | "onPressEnter" | "onClear"> & {
allowClear?: boolean; allowClear?: boolean;
@ -125,7 +130,7 @@ const MultipleInput = ({
</Button> </Button>
) : ( ) : (
<Space className="w-full" direction="vertical" size="small"> <Space className="w-full" direction="vertical" size="small">
{value.map((element, index) => { {Array.from(value).map((element, index) => {
const allowUp = index > 0; const allowUp = index > 0;
const allowDown = index < value.length - 1; const allowDown = index < value.length - 1;
const allowRemove = minCount == null || value.length > minCount; const allowRemove = minCount == null || value.length > minCount;
@ -192,6 +197,7 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
allowUp, allowUp,
disabled, disabled,
showSortButton, showSortButton,
size,
onChange, onChange,
onClickAdd, onClickAdd,
onClickDown, onClickDown,
@ -212,21 +218,17 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
const upBtn = useMemo(() => { const upBtn = useMemo(() => {
if (!showSortButton) return null; if (!showSortButton) return null;
return <Button icon={<ArrowUpIcon size={14} />} color="default" disabled={disabled || !allowUp} shape="circle" variant="text" onClick={onClickUp} />; return <Button icon={<UpOutlinedIcon />} color="default" disabled={disabled || !allowUp} type="text" onClick={onClickUp} />;
}, [allowUp, disabled, showSortButton, onClickUp]); }, [allowUp, disabled, showSortButton, onClickUp]);
const downBtn = useMemo(() => { const downBtn = useMemo(() => {
if (!showSortButton) return null; if (!showSortButton) return null;
return ( return <Button icon={<DownOutlinedIcon />} color="default" disabled={disabled || !allowDown} type="text" onClick={onClickDown} />;
<Button icon={<ArrowDownIcon size={14} />} color="default" disabled={disabled || !allowDown} shape="circle" variant="text" onClick={onClickDown} />
);
}, [allowDown, disabled, showSortButton, onClickDown]); }, [allowDown, disabled, showSortButton, onClickDown]);
const removeBtn = useMemo(() => { const removeBtn = useMemo(() => {
return ( return <Button icon={<MinusOutlinedIcon />} color="default" disabled={disabled || !allowRemove} type="text" onClick={onClickRemove} />;
<Button icon={<MinusIcon size={14} />} color="default" disabled={disabled || !allowRemove} shape="circle" variant="text" onClick={onClickRemove} />
);
}, [allowRemove, disabled, onClickRemove]); }, [allowRemove, disabled, onClickRemove]);
const addBtn = useMemo(() => { const addBtn = useMemo(() => {
return <Button icon={<PlusIcon size={14} />} color="default" disabled={disabled || !allowAdd} shape="circle" variant="text" onClick={onClickAdd} />; return <Button icon={<PlusOutlinedIcon />} color="default" disabled={disabled || !allowAdd} type="text" onClick={onClickAdd} />;
}, [allowAdd, disabled, onClickAdd]); }, [allowAdd, disabled, onClickAdd]);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
@ -261,12 +263,12 @@ const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputIte
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>
<div> <Button.Group size={size}>
{removeBtn} {removeBtn}
{upBtn} {upBtn}
{downBtn} {downBtn}
{addBtn} {addBtn}
</div> </Button.Group>
</div> </div>
); );
} }

View File

@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Divider, Space, Typography } from "antd"; import { Divider, Space, Typography } from "antd";
import { BookOpen as BookOpenIcon } from "lucide-react"; import { BookOutlined as BookOutlinedIcon } from "@ant-design/icons";
import { version } from "@/domain/version"; import { version } from "@/domain/version";
@ -16,7 +16,7 @@ const Version = ({ className, style }: VersionProps) => {
<Space className={className} style={style} size={4}> <Space className={className} style={style} size={4}>
<Typography.Link type="secondary" href="https://docs.certimate.me" target="_blank"> <Typography.Link type="secondary" href="https://docs.certimate.me" target="_blank">
<div className="flex items-center justify-center space-x-1"> <div className="flex items-center justify-center space-x-1">
<BookOpenIcon size={16} /> <BookOutlinedIcon />
<span>{t("common.menu.document")}</span> <span>{t("common.menu.document")}</span>
</div> </div>
</Typography.Link> </Typography.Link>

View File

@ -1,4 +1,4 @@
import { WorkflowNodeDropdwonItemIcon, WorkflowNodeDropdwonItemIconType } from "@/domain/workflow"; import { type WorkflowNodeDropdwonItemIcon, WorkflowNodeDropdwonItemIconType } from "@/domain/workflow";
import { CloudUpload, GitFork, Megaphone, NotebookPen } from "lucide-react"; import { CloudUpload, GitFork, Megaphone, NotebookPen } from "lucide-react";
const icons = new Map([ const icons = new Map([

View File

@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next";
import { useControllableValue } from "ahooks"; import { useControllableValue } from "ahooks";
import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Switch, Typography, type AutoCompleteProps } from "antd"; import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Switch, Typography, type AutoCompleteProps } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons";
import z from "zod"; import z from "zod";
import { Plus as PlusIcon } from "lucide-react";
import AccessEditModal from "@/components/access/AccessEditModal"; import AccessEditModal from "@/components/access/AccessEditModal";
import AccessSelect from "@/components/access/AccessSelect"; import AccessSelect from "@/components/access/AccessSelect";
@ -77,11 +77,21 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
return ( return (
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical"> <Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="domain" label={t("workflow.nodes.apply.form.domain.label")} rules={[formRule]}> <Form.Item
name="domain"
label={t("workflow.nodes.apply.form.domain.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.domain.tooltip") }}></span>}
>
<Input placeholder={t("workflow.nodes.apply.form.domain.placeholder")} /> <Input placeholder={t("workflow.nodes.apply.form.domain.placeholder")} />
</Form.Item> </Form.Item>
<Form.Item name="email" label={t("workflow.nodes.apply.form.email.label")} rules={[formRule]}> <Form.Item
name="email"
label={t("workflow.nodes.apply.form.email.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.email.tooltip") }}></span>}
>
<ContactEmailSelect placeholder={t("workflow.nodes.apply.form.email.placeholder")} /> <ContactEmailSelect placeholder={t("workflow.nodes.apply.form.email.placeholder")} />
</Form.Item> </Form.Item>
@ -94,7 +104,7 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
preset="add" preset="add"
trigger={ trigger={
<Button className="p-0" type="link"> <Button className="p-0" type="link">
<PlusIcon size={14} /> <PlusOutlinedIcon />
{t("workflow.nodes.apply.form.access.button")} {t("workflow.nodes.apply.form.access.button")}
</Button> </Button>
} }

View File

@ -3,8 +3,8 @@ import { Link } from "react-router";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, Form, Input, Select } from "antd"; import { Button, Form, Input, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { RightOutlined as RightOutlinedIcon } from "@ant-design/icons";
import { z } from "zod"; import { z } from "zod";
import { ChevronRight as ChevronRightIcon } from "lucide-react";
import { usePanel } from "../PanelProvider"; import { usePanel } from "../PanelProvider";
import { useAntdForm, useZustandShallowSelector } from "@/hooks"; import { useAntdForm, useZustandShallowSelector } from "@/hooks";
@ -75,9 +75,9 @@ const NotifyNodeForm = ({ data }: NotifyNodeFormProps) => {
<div className="flex-grow max-w-full truncate">{t("workflow.nodes.notify.form.channel.label")}</div> <div className="flex-grow max-w-full truncate">{t("workflow.nodes.notify.form.channel.label")}</div>
<div className="text-right"> <div className="text-right">
<Link className="ant-typography" to="/settings/notification" target="_blank"> <Link className="ant-typography" to="/settings/notification" target="_blank">
<Button className="p-0" type="link"> <Button size="small" type="link">
{t("workflow.nodes.notify.form.channel.button")} {t("workflow.nodes.notify.form.channel.button")}
<ChevronRightIcon size={14} /> <RightOutlinedIcon className="text-xs" />
</Button> </Button>
</Link> </Link>
</div> </div>

View File

@ -2,7 +2,6 @@ import { cloneElement, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useControllableValue } from "ahooks"; import { useControllableValue } from "ahooks";
import { Alert, Drawer } from "antd"; import { Alert, Drawer } from "antd";
import { CircleCheck as CircleCheckIcon, CircleX as CircleXIcon } from "lucide-react";
import Show from "@/components/Show"; import Show from "@/components/Show";
import { type WorkflowRunModel } from "@/domain/workflowRun"; import { type WorkflowRunModel } from "@/domain/workflowRun";
@ -45,11 +44,11 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}> <Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
<Show when={!!data}> <Show when={!!data}>
<Show when={data!.succeed}> <Show when={data!.succeed}>
<Alert showIcon type="success" message={t("workflow_run.props.status.succeeded")} icon={<CircleCheckIcon size={16} />} /> <Alert showIcon type="success" message={t("workflow_run.props.status.succeeded")} />
</Show> </Show>
<Show when={!!data!.error}> <Show when={!!data!.error}>
<Alert showIcon type="error" message={t("workflow_run.props.status.failed")} description={data!.error} icon={<CircleXIcon size={16} />} /> <Alert showIcon type="error" message={t("workflow_run.props.status.failed")} description={data!.error} />
</Show> </Show>
<div className="mt-4 p-4 bg-black text-stone-200 rounded-md"> <div className="mt-4 p-4 bg-black text-stone-200 rounded-md">

View File

@ -3,7 +3,11 @@ import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Button, Empty, notification, Space, Table, theme, Tooltip, Typography, type TableProps } from "antd"; import { Button, Empty, notification, Space, Table, theme, Tooltip, Typography, type TableProps } from "antd";
import { CircleCheck as CircleCheckIcon, CircleX as CircleXIcon, Eye as EyeIcon } from "lucide-react"; import {
CheckCircleTwoTone as CheckCircleTwoToneIcon,
CloseCircleTwoTone as CloseCircleTwoToneIcon,
SelectOutlined as SelectOutlinedIcon,
} from "@ant-design/icons";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer"; import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
@ -45,14 +49,14 @@ const WorkflowRuns = ({ className, style }: WorkflowRunsProps) => {
if (record.succeed) { if (record.succeed) {
return ( return (
<Space> <Space>
<CircleCheckIcon color={themeToken.colorSuccess} size={16} /> <CheckCircleTwoToneIcon twoToneColor={themeToken.colorSuccess} />
<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text> <Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>
</Space> </Space>
); );
} else { } else {
<Tooltip title={record.error}> <Tooltip title={record.error}>
<Space> <Space>
<CircleXIcon color={themeToken.colorError} size={16} /> <CloseCircleTwoToneIcon twoToneColor={themeToken.colorError} />
<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text> <Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>
</Space> </Space>
</Tooltip>; </Tooltip>;
@ -82,7 +86,7 @@ const WorkflowRuns = ({ className, style }: WorkflowRunsProps) => {
width: 120, width: 120,
render: (_, record) => ( render: (_, record) => (
<Button.Group> <Button.Group>
<WorkflowRunDetailDrawer data={record} trigger={<Button color="primary" icon={<EyeIcon size={16} />} variant="text" />} /> <WorkflowRunDetailDrawer data={record} trigger={<Button color="primary" icon={<SelectOutlinedIcon />} variant="text" />} />
</Button.Group> </Button.Group>
), ),
}, },

View File

@ -40,10 +40,12 @@
"workflow.nodes.start.form.trigger_cron.tooltip": "Time zone is based on the server.", "workflow.nodes.start.form.trigger_cron.tooltip": "Time zone is based on the server.",
"workflow.nodes.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:", "workflow.nodes.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
"workflow.nodes.start.form.trigger_cron_alert.content": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Lets Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Lets Encrypt (ACME) client run at a random time?</a>", "workflow.nodes.start.form.trigger_cron_alert.content": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Lets Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Lets Encrypt (ACME) client run at a random time?</a>",
"workflow.nodes.apply.form.domain.label": "Domain (wildcard domain is supported)", "workflow.nodes.apply.form.domain.label": "Domain",
"workflow.nodes.apply.form.domain.placeholder": "Please enter domain", "workflow.nodes.apply.form.domain.placeholder": "Please enter domain",
"workflow.nodes.apply.form.domain.tooltip": "Wildcard domain: *.example.com",
"workflow.nodes.apply.form.email.label": "Contact Email", "workflow.nodes.apply.form.email.label": "Contact Email",
"workflow.nodes.apply.form.email.placeholder": "Please enter contact email", "workflow.nodes.apply.form.email.placeholder": "Please enter contact email",
"workflow.nodes.apply.form.email.tooltip": "Contact information required for SSL certificate application. Please pay attention to the <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">rate limits</a>.",
"workflow.nodes.apply.form.access.label": "DNS Provider Authorization", "workflow.nodes.apply.form.access.label": "DNS Provider Authorization",
"workflow.nodes.apply.form.access.placeholder": "Please select an authorization of DNS provider", "workflow.nodes.apply.form.access.placeholder": "Please select an authorization of DNS provider",
"workflow.nodes.apply.form.access.button": "Create", "workflow.nodes.apply.form.access.button": "Create",

View File

@ -40,10 +40,12 @@
"workflow.nodes.start.form.trigger_cron.tooltip": "时区以服务器设置为准。", "workflow.nodes.start.form.trigger_cron.tooltip": "时区以服务器设置为准。",
"workflow.nodes.start.form.trigger_cron.extra": "预计最近 5 次执行时间:", "workflow.nodes.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
"workflow.nodes.start.form.trigger_cron_alert.content": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Lets Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Lets Encrypt (ACME) 客户端启动时间应当随机?</a>", "workflow.nodes.start.form.trigger_cron_alert.content": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Lets Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Lets Encrypt (ACME) 客户端启动时间应当随机?</a>",
"workflow.nodes.apply.form.domain.label": "域名(支持泛域名)", "workflow.nodes.apply.form.domain.label": "域名",
"workflow.nodes.apply.form.domain.placeholder": "请输入域名", "workflow.nodes.apply.form.domain.placeholder": "请输入域名",
"workflow.nodes.apply.form.domain.tooltip": "泛域名表示形式为:*.example.com",
"workflow.nodes.apply.form.email.label": "联系邮箱", "workflow.nodes.apply.form.email.label": "联系邮箱",
"workflow.nodes.apply.form.email.placeholder": "请输入联系邮箱", "workflow.nodes.apply.form.email.placeholder": "请输入联系邮箱",
"workflow.nodes.apply.form.email.tooltip": "申请签发 SSL 证书时所需的联系方式。请注意<a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">速率限制</a>。",
"workflow.nodes.apply.form.access.label": "DNS 提供商授权", "workflow.nodes.apply.form.access.label": "DNS 提供商授权",
"workflow.nodes.apply.form.access.placeholder": "请选择 DNS 提供商授权", "workflow.nodes.apply.form.access.placeholder": "请选择 DNS 提供商授权",
"workflow.nodes.apply.form.access.button": "新建", "workflow.nodes.apply.form.access.button": "新建",

View File

@ -3,17 +3,17 @@ import { Link, Navigate, Outlet, useLocation, useNavigate } from "react-router-d
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, Drawer, Dropdown, Layout, Menu, Tooltip, theme, type ButtonProps, type MenuProps } from "antd"; import { Button, Drawer, Dropdown, Layout, Menu, Tooltip, theme, type ButtonProps, type MenuProps } from "antd";
import { import {
Languages as LanguagesIcon, CloudServerOutlined as CloudServerOutlinedIcon,
LogOut as LogOutIcon, GlobalOutlined as GlobalOutlinedIcon,
Home as HomeIcon, HomeOutlined as HomeOutlinedIcon,
Menu as MenuIcon, LogoutOutlined as LogoutOutlinedIcon,
Moon as MoonIcon, MenuOutlined as MenuOutlinedIcon,
Server as ServerIcon, MoonOutlined as MoonOutlinedIcon,
Settings as SettingsIcon, NodeIndexOutlined as NodeIndexOutlinedIcon,
ShieldCheck as ShieldCheckIcon, SafetyOutlined as SafetyOutlinedIcon,
Sun as SunIcon, SettingOutlined as SettingOutlinedIcon,
Workflow as WorkflowIcon, SunOutlined as SunOutlinedIcon,
} from "lucide-react"; } from "@ant-design/icons";
import Version from "@/components/core/Version"; import Version from "@/components/core/Version";
import { useBrowserTheme } from "@/hooks"; import { useBrowserTheme } from "@/hooks";
@ -67,7 +67,7 @@ const ConsoleLayout = () => {
<Layout.Header className="sticky top-0 left-0 right-0 p-0 z-[19] shadow-sm" style={{ background: themeToken.colorBgContainer }}> <Layout.Header className="sticky top-0 left-0 right-0 p-0 z-[19] shadow-sm" style={{ background: themeToken.colorBgContainer }}>
<div className="flex items-center justify-between size-full px-4 overflow-hidden"> <div className="flex items-center justify-between size-full px-4 overflow-hidden">
<div className="flex items-center gap-4 size-full"> <div className="flex items-center gap-4 size-full">
<Button className="md:hidden" icon={<MenuIcon />} size="large" onClick={handleSiderOpen} /> <Button className="md:hidden" icon={<MenuOutlinedIcon />} size="large" onClick={handleSiderOpen} />
<Drawer <Drawer
closable={false} closable={false}
destroyOnClose destroyOnClose
@ -90,10 +90,10 @@ const ConsoleLayout = () => {
<LocaleToggleButton size="large" /> <LocaleToggleButton size="large" />
</Tooltip> </Tooltip>
<Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}> <Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}>
<Button icon={<SettingsIcon size={18} />} size="large" onClick={handleSettingsClick} /> <Button icon={<SettingOutlinedIcon />} size="large" onClick={handleSettingsClick} />
</Tooltip> </Tooltip>
<Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}> <Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}>
<Button danger icon={<LogOutIcon size={18} />} size="large" onClick={handleLogoutClick} /> <Button danger icon={<LogoutOutlinedIcon />} size="large" onClick={handleLogoutClick} />
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
@ -118,10 +118,10 @@ const SiderMenu = memo(({ onSelect }: { onSelect?: (key: string) => void }) => {
const MENU_KEY_CERTIFICATES = "/certificates"; const MENU_KEY_CERTIFICATES = "/certificates";
const MENU_KEY_ACCESSES = "/accesses"; const MENU_KEY_ACCESSES = "/accesses";
const menuItems: Required<MenuProps>["items"] = [ const menuItems: Required<MenuProps>["items"] = [
[MENU_KEY_HOME, <HomeIcon size={16} />, t("dashboard.page.title")], [MENU_KEY_HOME, <HomeOutlinedIcon />, t("dashboard.page.title")],
[MENU_KEY_WORKFLOWS, <WorkflowIcon size={16} />, t("workflow.page.title")], [MENU_KEY_WORKFLOWS, <NodeIndexOutlinedIcon />, t("workflow.page.title")],
[MENU_KEY_CERTIFICATES, <ShieldCheckIcon size={16} />, t("certificate.page.title")], [MENU_KEY_CERTIFICATES, <SafetyOutlinedIcon />, t("certificate.page.title")],
[MENU_KEY_ACCESSES, <ServerIcon size={16} />, t("access.page.title")], [MENU_KEY_ACCESSES, <CloudServerOutlinedIcon />, t("access.page.title")],
].map(([key, icon, label]) => { ].map(([key, icon, label]) => {
return { return {
key: key as string, key: key as string,
@ -201,7 +201,7 @@ const ThemeToggleButton = memo(({ size }: { size?: ButtonProps["size"] }) => {
return ( return (
<Dropdown menu={{ items }} trigger={["click"]}> <Dropdown menu={{ items }} trigger={["click"]}>
<Button icon={theme === "dark" ? <MoonIcon size={18} /> : <SunIcon size={18} />} size={size} /> <Button icon={theme === "dark" ? <MoonOutlinedIcon /> : <SunOutlinedIcon />} size={size} />
</Dropdown> </Dropdown>
); );
}); });
@ -219,7 +219,7 @@ const LocaleToggleButton = memo(({ size }: { size?: ButtonProps["size"] }) => {
return ( return (
<Dropdown menu={{ items }} trigger={["click"]}> <Dropdown menu={{ items }} trigger={["click"]}>
<Button icon={<LanguagesIcon size={18} />} size={size} /> <Button icon={<GlobalOutlinedIcon />} size={size} />
</Dropdown> </Dropdown>
); );
}); });

View File

@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Avatar, Button, Empty, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd"; import { Avatar, Button, Empty, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { Copy as CopyIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react"; import {
DeleteOutlined as DeleteOutlinedIcon,
EditOutlined as EditOutlinedIcon,
PlusOutlined as PlusOutlinedIcon,
SnippetsOutlined as SnippetsOutlinedIcon,
} from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -75,7 +80,7 @@ const AccessList = () => {
preset="edit" preset="edit"
trigger={ trigger={
<Tooltip title={t("access.action.edit")}> <Tooltip title={t("access.action.edit")}>
<Button color="primary" icon={<PencilIcon size={16} />} variant="text" /> <Button color="primary" icon={<EditOutlinedIcon />} variant="text" />
</Tooltip> </Tooltip>
} }
/> />
@ -85,7 +90,7 @@ const AccessList = () => {
preset="add" preset="add"
trigger={ trigger={
<Tooltip title={t("access.action.copy")}> <Tooltip title={t("access.action.copy")}>
<Button color="primary" icon={<CopyIcon size={16} />} variant="text" /> <Button color="primary" icon={<SnippetsOutlinedIcon />} variant="text" />
</Tooltip> </Tooltip>
} }
/> />
@ -93,7 +98,7 @@ const AccessList = () => {
<Tooltip title={t("access.action.delete")}> <Tooltip title={t("access.action.delete")}>
<Button <Button
color="danger" color="danger"
icon={<Trash2Icon size={16} />} icon={<DeleteOutlinedIcon />}
variant="text" variant="text"
onClick={() => { onClick={() => {
handleDeleteClick(record); handleDeleteClick(record);
@ -168,7 +173,7 @@ const AccessList = () => {
key="create" key="create"
preset="add" preset="add"
trigger={ trigger={
<Button type="primary" icon={<PlusIcon size={16} />}> <Button type="primary" icon={<PlusOutlinedIcon />}>
{t("access.action.add")} {t("access.action.add")}
</Button> </Button>
} }

View File

@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { useRequest } from "ahooks"; import { useRequest } from "ahooks";
import { Button, Divider, Empty, Menu, notification, Radio, Space, Table, theme, Tooltip, Typography, type MenuProps, type TableProps } from "antd"; import { Button, Divider, Empty, Menu, notification, Radio, Space, Table, theme, Tooltip, Typography, type MenuProps, type TableProps } from "antd";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { Eye as EyeIcon, Filter as FilterIcon, Trash2 as Trash2Icon } from "lucide-react"; import { DeleteOutlined as DeleteOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -88,7 +88,6 @@ const CertificateList = () => {
</div> </div>
); );
}, },
filterIcon: () => <FilterIcon size={14} />,
render: (_, record) => { render: (_, record) => {
const total = dayjs(record.expireAt).diff(dayjs(record.created), "d") + 1; const total = dayjs(record.expireAt).diff(dayjs(record.created), "d") + 1;
const left = dayjs(record.expireAt).diff(dayjs(), "d"); const left = dayjs(record.expireAt).diff(dayjs(), "d");
@ -158,7 +157,7 @@ const CertificateList = () => {
data={record} data={record}
trigger={ trigger={
<Tooltip title={t("certificate.action.view")}> <Tooltip title={t("certificate.action.view")}>
<Button color="primary" icon={<EyeIcon size={16} />} variant="text" /> <Button color="primary" icon={<SelectOutlinedIcon />} variant="text" />
</Tooltip> </Tooltip>
} }
/> />
@ -166,7 +165,7 @@ const CertificateList = () => {
<Tooltip title={t("certificate.action.delete")}> <Tooltip title={t("certificate.action.delete")}>
<Button <Button
color="danger" color="danger"
icon={<Trash2Icon size={16} />} icon={<DeleteOutlinedIcon />}
variant="text" variant="text"
onClick={() => { onClick={() => {
alert("TODO"); alert("TODO");

View File

@ -3,7 +3,12 @@ import { Outlet, useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, Space } from "antd"; import { Card, Space } from "antd";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { KeyRound as KeyRoundIcon, Megaphone as MegaphoneIcon, ShieldCheck as ShieldCheckIcon, UserRound as UserRoundIcon } from "lucide-react"; import {
ApiOutlined as ApiOutlinedIcon,
LockOutlined as LockOutlinedIcon,
SendOutlined as SendOutlinedIcon,
UserOutlined as UserOutlinedIcon,
} from "@ant-design/icons";
const Settings = () => { const Settings = () => {
const location = useLocation(); const location = useLocation();
@ -32,7 +37,7 @@ const Settings = () => {
key: "account", key: "account",
label: ( label: (
<Space> <Space>
<UserRoundIcon size={14} /> <UserOutlinedIcon />
<label>{t("settings.account.tab")}</label> <label>{t("settings.account.tab")}</label>
</Space> </Space>
), ),
@ -41,7 +46,7 @@ const Settings = () => {
key: "password", key: "password",
label: ( label: (
<Space> <Space>
<KeyRoundIcon size={14} /> <LockOutlinedIcon />
<label>{t("settings.password.tab")}</label> <label>{t("settings.password.tab")}</label>
</Space> </Space>
), ),
@ -50,7 +55,7 @@ const Settings = () => {
key: "notification", key: "notification",
label: ( label: (
<Space> <Space>
<MegaphoneIcon size={14} /> <SendOutlinedIcon />
<label>{t("settings.notification.tab")}</label> <label>{t("settings.notification.tab")}</label>
</Space> </Space>
), ),
@ -59,7 +64,7 @@ const Settings = () => {
key: "ssl-provider", key: "ssl-provider",
label: ( label: (
<Space> <Space>
<ShieldCheckIcon size={14} /> <ApiOutlinedIcon />
<label>{t("settings.sslprovider.tab")}</label> <label>{t("settings.sslprovider.tab")}</label>
</Space> </Space>
), ),

View File

@ -4,8 +4,8 @@ import { useTranslation } from "react-i18next";
import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Tabs, Typography } from "antd"; import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Tabs, Typography } from "antd";
import { createSchemaFieldRule } from "antd-zod"; import { createSchemaFieldRule } from "antd-zod";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { DeleteOutlined as DeleteOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons";
import { z } from "zod"; import { z } from "zod";
import { Ellipsis as EllipsisIcon, Trash2 as Trash2Icon } from "lucide-react";
import Show from "@/components/Show"; import Show from "@/components/Show";
import End from "@/components/workflow/End"; import End from "@/components/workflow/End";
@ -136,7 +136,7 @@ const WorkflowDetail = () => {
key: "delete", key: "delete",
label: t("common.button.delete"), label: t("common.button.delete"),
danger: true, danger: true,
icon: <Trash2Icon size={14} />, icon: <DeleteOutlinedIcon />,
onClick: () => { onClick: () => {
handleDeleteClick(); handleDeleteClick();
}, },
@ -145,7 +145,7 @@ const WorkflowDetail = () => {
}} }}
trigger={["click"]} trigger={["click"]}
> >
<Button icon={<EllipsisIcon size={14} />} /> <Button icon={<EllipsisOutlinedIcon />} />
</Dropdown> </Dropdown>
</Button.Group>, </Button.Group>,
]} ]}

View File

@ -21,7 +21,7 @@ import {
type TableProps, type TableProps,
} from "antd"; } from "antd";
import { PageHeader } from "@ant-design/pro-components"; import { PageHeader } from "@ant-design/pro-components";
import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react"; import { DeleteOutlined as DeleteOutlinedIcon, EditOutlined as EditOutlinedIcon, PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -133,7 +133,6 @@ const WorkflowList = () => {
</div> </div>
); );
}, },
filterIcon: () => <FilterIcon size={14} />,
render: (_, record) => { render: (_, record) => {
const enabled = record.enabled; const enabled = record.enabled;
return ( return (
@ -180,7 +179,7 @@ const WorkflowList = () => {
<Tooltip title={t("workflow.action.edit")}> <Tooltip title={t("workflow.action.edit")}>
<Button <Button
color="primary" color="primary"
icon={<PencilIcon size={16} />} icon={<EditOutlinedIcon />}
variant="text" variant="text"
onClick={() => { onClick={() => {
navigate(`/workflows/${record.id}`); navigate(`/workflows/${record.id}`);
@ -192,7 +191,7 @@ const WorkflowList = () => {
<Button <Button
color="danger" color="danger"
danger={true} danger={true}
icon={<Trash2Icon size={16} />} icon={<DeleteOutlinedIcon />}
variant="text" variant="text"
onClick={() => { onClick={() => {
handleDeleteClick(record); handleDeleteClick(record);
@ -301,7 +300,7 @@ const WorkflowList = () => {
<Button <Button
key="create" key="create"
type="primary" type="primary"
icon={<PlusIcon size={16} />} icon={<PlusOutlinedIcon />}
onClick={() => { onClick={() => {
handleCreateClick(); handleCreateClick();
}} }}