refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
314
app/data-governance/tasks/page.tsx
Normal file
314
app/data-governance/tasks/page.tsx
Normal file
@@ -0,0 +1,314 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Clock, Play, Pause, CheckCircle, XCircle, RefreshCw, Calendar, Settings, MoreHorizontal } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { ScheduleCalendarDialog } from "@/components/dialogs/schedule-calendar-dialog"
|
||||
import { TaskDetailDialog } from "@/components/dialogs/task-detail-dialog"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
|
||||
interface ScheduledTask {
|
||||
id: string
|
||||
name: string
|
||||
type: "sync" | "cleaning" | "tagging" | "model"
|
||||
schedule: string
|
||||
status: "running" | "completed" | "failed" | "waiting" | "paused"
|
||||
progress: number
|
||||
nextRun: string
|
||||
lastRun: string
|
||||
duration: string
|
||||
logs: string[]
|
||||
}
|
||||
|
||||
const MOCK_TASKS: ScheduledTask[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "用户数据同步",
|
||||
type: "sync",
|
||||
schedule: "*/5 * * * *",
|
||||
status: "running",
|
||||
progress: 67,
|
||||
nextRun: "-",
|
||||
lastRun: "2025-12-12 14:30:00",
|
||||
duration: "2分30秒",
|
||||
logs: ["正在同步数据...", "已处理 67%"],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "交易数据清洗",
|
||||
type: "cleaning",
|
||||
schedule: "0 */1 * * *",
|
||||
status: "completed",
|
||||
progress: 100,
|
||||
nextRun: "2025-12-12 15:00:00",
|
||||
lastRun: "2025-12-12 14:00:00",
|
||||
duration: "5分12秒",
|
||||
logs: ["清洗完成,处理 89234567 条记录"],
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "用户标签计算",
|
||||
type: "tagging",
|
||||
schedule: "0 2 * * *",
|
||||
status: "waiting",
|
||||
progress: 0,
|
||||
nextRun: "2025-12-13 02:00:00",
|
||||
lastRun: "2025-12-12 02:00:00",
|
||||
duration: "15分38秒",
|
||||
logs: ["等待执行"],
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "CLV模型训练",
|
||||
type: "model",
|
||||
schedule: "0 3 * * 0",
|
||||
status: "failed",
|
||||
progress: 45,
|
||||
nextRun: "2025-12-15 03:00:00",
|
||||
lastRun: "2025-12-08 03:00:00",
|
||||
duration: "-",
|
||||
logs: ["错误:内存不足", "任务中断于 45%"],
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "行为数据同步",
|
||||
type: "sync",
|
||||
schedule: "实时",
|
||||
status: "running",
|
||||
progress: 100,
|
||||
nextRun: "-",
|
||||
lastRun: "2025-12-12 14:35:12",
|
||||
duration: "持续运行",
|
||||
logs: ["Kafka消费正常", "当前lag: 125"],
|
||||
},
|
||||
]
|
||||
|
||||
const TYPE_CONFIG = {
|
||||
sync: { label: "数据同步", color: "bg-blue-100 text-blue-700" },
|
||||
cleaning: { label: "数据清洗", color: "bg-green-100 text-green-700" },
|
||||
tagging: { label: "标签计算", color: "bg-purple-100 text-purple-700" },
|
||||
model: { label: "模型训练", color: "bg-orange-100 text-orange-700" },
|
||||
}
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
running: { label: "运行中", color: "bg-blue-100 text-blue-700", icon: RefreshCw },
|
||||
completed: { label: "已完成", color: "bg-green-100 text-green-700", icon: CheckCircle },
|
||||
failed: { label: "失败", color: "bg-red-100 text-red-700", icon: XCircle },
|
||||
waiting: { label: "等待中", color: "bg-gray-100 text-gray-700", icon: Clock },
|
||||
paused: { label: "已暂停", color: "bg-yellow-100 text-yellow-700", icon: Pause },
|
||||
}
|
||||
|
||||
export default function TaskSchedulePage() {
|
||||
const [tasks, setTasks] = useState<ScheduledTask[]>(MOCK_TASKS)
|
||||
const [currentTime, setCurrentTime] = useState(new Date())
|
||||
const [showCalendarDialog, setShowCalendarDialog] = useState(false)
|
||||
const [showDetailDialog, setShowDetailDialog] = useState(false)
|
||||
const [selectedTask, setSelectedTask] = useState<ScheduledTask | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(new Date())
|
||||
// 模拟进度更新
|
||||
setTasks((prev) =>
|
||||
prev.map((task) => {
|
||||
if (task.status === "running" && task.progress < 100) {
|
||||
return { ...task, progress: Math.min(task.progress + Math.random() * 2, 100) }
|
||||
}
|
||||
return task
|
||||
}),
|
||||
)
|
||||
}, 2000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const stats = {
|
||||
total: tasks.length,
|
||||
running: tasks.filter((t) => t.status === "running").length,
|
||||
completed: tasks.filter((t) => t.status === "completed").length,
|
||||
failed: tasks.filter((t) => t.status === "failed").length,
|
||||
}
|
||||
|
||||
const handleViewDetail = (task: ScheduledTask) => {
|
||||
setSelectedTask(task)
|
||||
setShowDetailDialog(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">任务调度</h1>
|
||||
<p className="text-gray-500 mt-1">管理与监控数据处理任务</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="px-3 py-1">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{currentTime.toLocaleTimeString()}
|
||||
</Badge>
|
||||
<Button variant="outline" onClick={() => setShowCalendarDialog(true)}>
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
调度日历
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
|
||||
<Settings className="w-4 h-4" />
|
||||
总任务数
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900">{stats.total}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
|
||||
<RefreshCw className="w-4 h-4 text-blue-500" />
|
||||
运行中
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-blue-600">{stats.running}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
已完成
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-green-600">{stats.completed}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
|
||||
<XCircle className="w-4 h-4 text-red-500" />
|
||||
失败
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-red-600">{stats.failed}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Task List */}
|
||||
<div className="space-y-4">
|
||||
{tasks.map((task) => {
|
||||
const StatusIcon = STATUS_CONFIG[task.status].icon
|
||||
|
||||
return (
|
||||
<Card key={task.id} className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h3 className="font-semibold text-gray-900">{task.name}</h3>
|
||||
<Badge className={TYPE_CONFIG[task.type].color}>{TYPE_CONFIG[task.type].label}</Badge>
|
||||
<Badge className={STATUS_CONFIG[task.status].color}>
|
||||
<StatusIcon className={`w-3 h-3 mr-1 ${task.status === "running" ? "animate-spin" : ""}`} />
|
||||
{STATUS_CONFIG[task.status].label}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{task.status === "running" && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center justify-between text-sm text-gray-500 mb-1">
|
||||
<span>执行进度</span>
|
||||
<span>{Math.round(task.progress)}%</span>
|
||||
</div>
|
||||
<Progress value={task.progress} className="h-2" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">调度规则:</span>
|
||||
<code className="ml-1 text-xs bg-gray-100 px-1 rounded">{task.schedule}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">下次执行:</span>
|
||||
<span className="ml-1 text-gray-900">{task.nextRun}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">上次执行:</span>
|
||||
<span className="ml-1 text-gray-900">{task.lastRun}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">耗时:</span>
|
||||
<span className="ml-1 text-gray-900">{task.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{task.logs.length > 0 && (
|
||||
<div className="mt-3 p-2 bg-gray-50 rounded-lg">
|
||||
<div className="text-xs text-gray-500 mb-1">最新日志:</div>
|
||||
{task.logs.slice(-2).map((log, i) => (
|
||||
<div key={i} className="text-xs text-gray-600">
|
||||
{log}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
{task.status === "running" ? (
|
||||
<Button variant="outline" size="sm">
|
||||
<Pause className="w-4 h-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" size="sm">
|
||||
<Play className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => handleViewDetail(task)}>查看详情</DropdownMenuItem>
|
||||
<DropdownMenuItem>编辑任务</DropdownMenuItem>
|
||||
<DropdownMenuItem>查看日志</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">删除任务</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Schedule Calendar Dialog */}
|
||||
<ScheduleCalendarDialog open={showCalendarDialog} onOpenChange={setShowCalendarDialog} />
|
||||
|
||||
<TaskDetailDialog
|
||||
open={showDetailDialog}
|
||||
onOpenChange={setShowDetailDialog}
|
||||
task={
|
||||
selectedTask
|
||||
? {
|
||||
id: selectedTask.id,
|
||||
name: selectedTask.name,
|
||||
type: selectedTask.type,
|
||||
status: selectedTask.status,
|
||||
progress: selectedTask.progress,
|
||||
schedule: selectedTask.schedule,
|
||||
lastRun: selectedTask.lastRun,
|
||||
nextRun: selectedTask.nextRun,
|
||||
duration: selectedTask.duration,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user