Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
229 lines
8.4 KiB
TypeScript
229 lines
8.4 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Progress } from "@/components/ui/progress"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
import { Play, Pause, RefreshCw, Clock, CheckCircle, XCircle } from "lucide-react"
|
|
|
|
interface TaskDetailDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
task?: {
|
|
id: string
|
|
name: string
|
|
type: string
|
|
status: string
|
|
progress: number
|
|
schedule: string
|
|
lastRun: string
|
|
nextRun: string
|
|
duration: string
|
|
}
|
|
}
|
|
|
|
const TASK_LOGS = [
|
|
{ time: "14:35:12", level: "INFO", message: "任务开始执行" },
|
|
{ time: "14:35:13", level: "INFO", message: "连接数据源成功" },
|
|
{ time: "14:35:15", level: "INFO", message: "开始读取数据 (批次 1/10)" },
|
|
{ time: "14:35:22", level: "INFO", message: "批次 1 处理完成, 共 12,895 条" },
|
|
{ time: "14:35:25", level: "INFO", message: "开始读取数据 (批次 2/10)" },
|
|
{ time: "14:35:32", level: "WARN", message: "批次 2 发现 3 条异常数据" },
|
|
{ time: "14:35:35", level: "INFO", message: "批次 2 处理完成, 共 12,456 条" },
|
|
{ time: "14:36:02", level: "INFO", message: "任务进度: 67%" },
|
|
]
|
|
|
|
const EXECUTION_STATS = [
|
|
{ label: "总处理记录", value: "128,956" },
|
|
{ label: "成功记录", value: "128,931" },
|
|
{ label: "失败记录", value: "25" },
|
|
{ label: "平均速度", value: "3,800 条/秒" },
|
|
{ label: "内存使用", value: "2.4 GB" },
|
|
{ label: "CPU使用", value: "45%" },
|
|
]
|
|
|
|
export function TaskDetailDialog({ open, onOpenChange, task }: TaskDetailDialogProps) {
|
|
const [activeTab, setActiveTab] = useState("overview")
|
|
|
|
const getStatusBadge = (status: string) => {
|
|
switch (status) {
|
|
case "running":
|
|
return (
|
|
<Badge className="bg-blue-100 text-blue-700">
|
|
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
|
|
运行中
|
|
</Badge>
|
|
)
|
|
case "completed":
|
|
return (
|
|
<Badge className="bg-emerald-100 text-emerald-700">
|
|
<CheckCircle className="w-3 h-3 mr-1" />
|
|
已完成
|
|
</Badge>
|
|
)
|
|
case "failed":
|
|
return (
|
|
<Badge className="bg-red-100 text-red-700">
|
|
<XCircle className="w-3 h-3 mr-1" />
|
|
失败
|
|
</Badge>
|
|
)
|
|
case "waiting":
|
|
return (
|
|
<Badge className="bg-slate-100 text-slate-700">
|
|
<Clock className="w-3 h-3 mr-1" />
|
|
等待中
|
|
</Badge>
|
|
)
|
|
default:
|
|
return <Badge variant="secondary">{status}</Badge>
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-3xl">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-3">
|
|
{task?.name || "任务详情"}
|
|
{getStatusBadge(task?.status || "waiting")}
|
|
</DialogTitle>
|
|
<DialogDescription>查看任务执行详情、日志和统计信息</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{task?.status === "running" && (
|
|
<div className="p-4 bg-blue-50 rounded-lg">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-sm font-medium text-blue-700">执行进度</span>
|
|
<span className="text-sm font-medium text-blue-700">{task.progress}%</span>
|
|
</div>
|
|
<Progress value={task.progress} className="h-2" />
|
|
</div>
|
|
)}
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="overview">概览</TabsTrigger>
|
|
<TabsTrigger value="logs">执行日志</TabsTrigger>
|
|
<TabsTrigger value="config">任务配置</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="overview" className="mt-4 space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="p-4 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center gap-2 text-slate-500 text-sm mb-1">
|
|
<Clock className="w-4 h-4" />
|
|
调度规则
|
|
</div>
|
|
<code className="text-sm bg-white px-2 py-1 rounded">{task?.schedule || "*/5 * * * *"}</code>
|
|
</div>
|
|
<div className="p-4 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center gap-2 text-slate-500 text-sm mb-1">
|
|
<Clock className="w-4 h-4" />
|
|
执行耗时
|
|
</div>
|
|
<p className="font-medium">{task?.duration || "2分30秒"}</p>
|
|
</div>
|
|
<div className="p-4 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center gap-2 text-slate-500 text-sm mb-1">
|
|
<Clock className="w-4 h-4" />
|
|
上次执行
|
|
</div>
|
|
<p className="font-medium">{task?.lastRun || "-"}</p>
|
|
</div>
|
|
<div className="p-4 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center gap-2 text-slate-500 text-sm mb-1">
|
|
<Clock className="w-4 h-4" />
|
|
下次执行
|
|
</div>
|
|
<p className="font-medium">{task?.nextRun || "-"}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{EXECUTION_STATS.map((stat, i) => (
|
|
<div key={i} className="text-center p-3 bg-slate-50 rounded-lg">
|
|
<p className="text-lg font-bold text-slate-800">{stat.value}</p>
|
|
<p className="text-xs text-slate-500">{stat.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="logs" className="mt-4">
|
|
<ScrollArea className="h-[300px] bg-slate-900 rounded-lg p-4">
|
|
<div className="space-y-1 font-mono text-sm">
|
|
{TASK_LOGS.map((log, i) => (
|
|
<div key={i} className="flex gap-3">
|
|
<span className="text-slate-500">{log.time}</span>
|
|
<span
|
|
className={`${
|
|
log.level === "WARN"
|
|
? "text-amber-400"
|
|
: log.level === "ERROR"
|
|
? "text-red-400"
|
|
: "text-blue-400"
|
|
}`}
|
|
>
|
|
[{log.level}]
|
|
</span>
|
|
<span className="text-slate-200">{log.message}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="config" className="mt-4 space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-slate-500">任务类型</label>
|
|
<p className="font-medium">{task?.type || "数据同步"}</p>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-slate-500">超时时间</label>
|
|
<p className="font-medium">30分钟</p>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-slate-500">重试次数</label>
|
|
<p className="font-medium">3次</p>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-slate-500">并发数</label>
|
|
<p className="font-medium">4</p>
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
关闭
|
|
</Button>
|
|
{task?.status === "running" ? (
|
|
<Button variant="destructive">
|
|
<Pause className="w-4 h-4 mr-2" />
|
|
暂停任务
|
|
</Button>
|
|
) : (
|
|
<Button>
|
|
<Play className="w-4 h-4 mr-2" />
|
|
立即执行
|
|
</Button>
|
|
)}
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|