improve multi language
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Link, Navigate, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
||||
import { CircleUser, Home, Menu, Server, ShieldCheck, Workflow } from "lucide-react";
|
||||
|
||||
import LocaleToggle from "@/components/LocaleToggle";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
@@ -58,16 +58,16 @@ export default function Dashboard() {
|
||||
{t("dashboard.page.title")}
|
||||
</Link>
|
||||
<Link to="/workflow" className={cn("flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary", getClass("/workflow"))}>
|
||||
<Earth className="h-4 w-4" />
|
||||
工作流列表
|
||||
<Workflow className="h-4 w-4" />
|
||||
{t("workflow.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/certificate"
|
||||
className={cn("flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary", getClass("/certificate"))}
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
证书列表
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
{t("certificate.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link to="/access" className={cn("flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary", getClass("/access"))}>
|
||||
@@ -106,16 +106,16 @@ export default function Dashboard() {
|
||||
to="/workflow"
|
||||
className={cn("mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 hover:text-foreground", getClass("/workflow"))}
|
||||
>
|
||||
<Earth className="h-5 w-5" />
|
||||
工作流
|
||||
<Workflow className="h-5 w-5" />
|
||||
{t("workflow.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/certificate"
|
||||
className={cn("mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 hover:text-foreground", getClass("/certificate"))}
|
||||
>
|
||||
<History className="h-5 w-5" />
|
||||
证书
|
||||
<ShieldCheck className="h-5 w-5" />
|
||||
{t("certificate.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import CertificateList from "@/components/certificate/CertificateList";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Certificate = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col space-y-5">
|
||||
<div className="text-muted-foreground">证书</div>
|
||||
<div className="text-muted-foreground">{t("certificate.page.title")}</div>
|
||||
|
||||
<CertificateList withPagination={true} />
|
||||
</div>
|
||||
|
||||
@@ -34,11 +34,11 @@ const Dashboard = () => {
|
||||
<SquareSigma size={48} strokeWidth={1} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">全部证书</div>
|
||||
<div className="text-muted-foreground font-semibold">{t("dashboard.statistics.all.certificate")}</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.certificateTotal ? (
|
||||
<Link to="/domains" className="hover:underline">
|
||||
<Link to="/certificate" className="hover:underline">
|
||||
{statistic?.certificateTotal}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -55,11 +55,11 @@ const Dashboard = () => {
|
||||
<CalendarClock size={48} strokeWidth={1} className="text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">即将过期证书</div>
|
||||
<div className="text-muted-foreground font-semibold">{t("dashboard.statistics.near_expired.certificate")}</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.certificateExpireSoon ? (
|
||||
<Link to="/domains?state=expired" className="hover:underline">
|
||||
<Link to="/certificate?state=expireSoon" className="hover:underline">
|
||||
{statistic?.certificateExpireSoon}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -76,11 +76,11 @@ const Dashboard = () => {
|
||||
<CalendarX2 size={48} strokeWidth={1} className="text-red-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">已过期证书</div>
|
||||
<div className="text-muted-foreground font-semibold">{t("dashboard.statistics.expired.certificate")}</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.certificateExpired ? (
|
||||
<Link to="/domains?state=enabled" className="hover:underline">
|
||||
<Link to="/certificate?state=expired" className="hover:underline">
|
||||
{statistic?.certificateExpired}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -97,11 +97,11 @@ const Dashboard = () => {
|
||||
<Workflow size={48} strokeWidth={1} className="text-emerald-500" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">全部工作流</div>
|
||||
<div className="text-muted-foreground font-semibold">{t("dashboard.statistics.all.workflow")}</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.workflowTotal ? (
|
||||
<Link to="/domains?state=disabled" className="hover:underline">
|
||||
<Link to="/workflow" className="hover:underline">
|
||||
{statistic?.workflowTotal}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -118,11 +118,11 @@ const Dashboard = () => {
|
||||
<FolderCheck size={48} strokeWidth={1} className="text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">已启用工作流</div>
|
||||
<div className="text-muted-foreground font-semibold">{t("dashboard.statistics.enabled.workflow")}</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
{statistic?.workflowEnabled ? (
|
||||
<Link to="/domains?state=disabled" className="hover:underline">
|
||||
<Link to="/workflow?state=enabled" className="hover:underline">
|
||||
{statistic?.workflowEnabled}
|
||||
</Link>
|
||||
) : (
|
||||
@@ -140,7 +140,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-muted-foreground mt-5 text-sm">最新证书</div>
|
||||
<div className="text-muted-foreground mt-5 text-sm">{t("dashboard.certificate")}</div>
|
||||
|
||||
<CertificateList />
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { cn } from "@/lib/utils";
|
||||
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { useShallow } from "zustand/shallow";
|
||||
@@ -53,6 +54,8 @@ const WorkflowDetail = () => {
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const elements = useMemo(() => {
|
||||
let current = workflow.draft as WorkflowNode;
|
||||
|
||||
@@ -77,8 +80,8 @@ const WorkflowDetail = () => {
|
||||
const handleEnableChange = () => {
|
||||
if (!workflow.enabled && !allNodesValidated(workflow.draft as WorkflowNode)) {
|
||||
toast({
|
||||
title: "无法启用",
|
||||
description: "有尚未设置完成的节点",
|
||||
title: t("workflow.detail.action.save.failed"),
|
||||
description: t("workflow.detail.action.save.failed.uncompleted"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -89,8 +92,8 @@ const WorkflowDetail = () => {
|
||||
const handleWorkflowSaveClick = () => {
|
||||
if (!allNodesValidated(workflow.draft as WorkflowNode)) {
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "有尚未设置完成的节点",
|
||||
title: t("workflow.detail.action.save.failed"),
|
||||
description: t("workflow.detail.action.save.failed.uncompleted"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -113,13 +116,13 @@ const WorkflowDetail = () => {
|
||||
try {
|
||||
await run(workflow.id as string);
|
||||
toast({
|
||||
title: "执行成功",
|
||||
description: "工作流已成功执行",
|
||||
title: t("workflow.detail.action.run.success"),
|
||||
description: t("workflow.detail.action.run.success"),
|
||||
variant: "default",
|
||||
});
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "执行失败",
|
||||
title: t("workflow.detail.action.run.failed"),
|
||||
description: getErrMessage(e),
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -138,8 +141,10 @@ const WorkflowDetail = () => {
|
||||
<WorkflowBaseInfoEditDialog
|
||||
trigger={
|
||||
<div className="flex flex-col space-y-1 cursor-pointer items-start">
|
||||
<div className="truncate max-w-[200px]">{workflow.name ? workflow.name : "未命名工作流"}</div>
|
||||
<div className="text-sm text-muted-foreground truncate max-w-[200px]">{workflow.description ? workflow.description : "添加流程说明"}</div>
|
||||
<div className="truncate max-w-[200px]">{workflow.name ? workflow.name : t("workflow.props.name.default")}</div>
|
||||
<div className="text-sm text-muted-foreground truncate max-w-[200px]">
|
||||
{workflow.description ? workflow.description : t("workflow.props.description.placeholder")}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -152,7 +157,7 @@ const WorkflowDetail = () => {
|
||||
setTab("workflow");
|
||||
}}
|
||||
>
|
||||
<div>流程</div>
|
||||
<div>{t("workflow.detail.title")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn("h-full flex items-center cursor-pointer border-b-2", getTabCls("history"))}
|
||||
@@ -160,7 +165,7 @@ const WorkflowDetail = () => {
|
||||
setTab("history");
|
||||
}}
|
||||
>
|
||||
<div>历史</div>
|
||||
<div>{t("workflow.detail.history")}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -170,12 +175,12 @@ const WorkflowDetail = () => {
|
||||
when={!!workflow.hasDraft}
|
||||
fallback={
|
||||
<Button variant={"secondary"} onClick={handleRunClick}>
|
||||
{running ? "执行中" : "立即执行"}
|
||||
{running ? t("workflow.detail.action.running") : t("workflow.detail.action.run")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button variant={"secondary"} onClick={handleWorkflowSaveClick}>
|
||||
保存变更
|
||||
{t("workflow.detail.action.save")}
|
||||
</Button>
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MoreHorizontal, Plus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Workflow as WorkflowType } from "@/domain/workflow";
|
||||
import { DataTable } from "@/components/workflow/DataTable";
|
||||
import { useState } from "react";
|
||||
import { list, remove, save } from "@/repository/workflow";
|
||||
import { list, remove, save, WorkflowListReq } from "@/repository/workflow";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
@@ -28,8 +28,15 @@ const Workflow = () => {
|
||||
onConfirm: () => void;
|
||||
}>();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const fetchData = async (page: number, pageSize?: number) => {
|
||||
const resp = await list({ page: page, perPage: pageSize });
|
||||
const state = searchParams.get("state");
|
||||
const req: WorkflowListReq = { page: page, perPage: pageSize };
|
||||
if (state && state == "enabled") {
|
||||
req.enabled = true;
|
||||
}
|
||||
const resp = await list(req);
|
||||
setData(resp.items);
|
||||
setPageCount(resp.totalPages);
|
||||
};
|
||||
@@ -37,18 +44,18 @@ const Workflow = () => {
|
||||
const columns: ColumnDef<WorkflowType>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "名称",
|
||||
header: t("workflow.props.name"),
|
||||
cell: ({ row }) => {
|
||||
let name: string = row.getValue("name");
|
||||
if (!name) {
|
||||
name = "未命名工作流";
|
||||
name = t("workflow.props.name.default");
|
||||
}
|
||||
return <div className="max-w-[150px] truncate">{name}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "描述",
|
||||
header: t("workflow.props.description"),
|
||||
cell: ({ row }) => {
|
||||
let description: string = row.getValue("description");
|
||||
if (!description) {
|
||||
@@ -59,18 +66,18 @@ const Workflow = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "执行方式",
|
||||
header: t("workflow.props.executionMethod"),
|
||||
cell: ({ row }) => {
|
||||
const method = row.getValue("type");
|
||||
if (!method) {
|
||||
return "-";
|
||||
} else if (method === "manual") {
|
||||
return "手动";
|
||||
return t("workflow.node.start.form.executionMethod.options.manual");
|
||||
} else if (method === "auto") {
|
||||
const crontab: string = row.original.crontab ?? "";
|
||||
return (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div>定时</div>
|
||||
<div>{t("workflow.node.start.form.executionMethod.options.auto")}</div>
|
||||
<div className="text-muted-foreground text-xs">{crontab}</div>
|
||||
</div>
|
||||
);
|
||||
@@ -79,7 +86,7 @@ const Workflow = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "enabled",
|
||||
header: "是否启用",
|
||||
header: t("workflow.props.enabled"),
|
||||
cell: ({ row }) => {
|
||||
const enabled: boolean = row.getValue("enabled");
|
||||
|
||||
@@ -100,7 +107,7 @@ const Workflow = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "created",
|
||||
header: "创建时间",
|
||||
header: t("workflow.props.created"),
|
||||
cell: ({ row }) => {
|
||||
const date: string = row.getValue("created");
|
||||
return new Date(date).toLocaleString();
|
||||
@@ -108,7 +115,7 @@ const Workflow = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "updated",
|
||||
header: "更新时间",
|
||||
header: t("workflow.props.updated"),
|
||||
cell: ({ row }) => {
|
||||
const date: string = row.getValue("updated");
|
||||
return new Date(date).toLocaleString();
|
||||
@@ -128,14 +135,14 @@ const Workflow = () => {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>操作</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>{t("workflow.action")}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/workflow/detail?id=${workflow.id}`);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
{t("workflow.action.edit")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="text-red-500"
|
||||
@@ -169,8 +176,8 @@ const Workflow = () => {
|
||||
|
||||
const handleDeleteClick = (id: string) => {
|
||||
setAlertProps({
|
||||
title: "删除工作流",
|
||||
description: "确定删除工作流吗?",
|
||||
title: t("workflow.action.delete.alert.title"),
|
||||
description: t("workflow.action.delete.alert.description"),
|
||||
onConfirm: async () => {
|
||||
const resp = await remove(id);
|
||||
if (resp) {
|
||||
@@ -192,10 +199,10 @@ const Workflow = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">工作流</div>
|
||||
<div className="text-muted-foreground">{t("workflow.page.title")}</div>
|
||||
<Button onClick={handleCreateClick}>
|
||||
<Plus size={16} />
|
||||
新建工作流
|
||||
{t("workflow.action.create")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user