Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
179 lines
6.4 KiB
TypeScript
179 lines
6.4 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
import { FileText, CheckCircle, XCircle, Clock, Download, RefreshCw } from "lucide-react"
|
|
|
|
interface CleaningRuleLogsDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
ruleName?: string
|
|
}
|
|
|
|
const MOCK_LOGS = [
|
|
{
|
|
id: 1,
|
|
timestamp: "2025-12-16 14:35:12",
|
|
level: "INFO",
|
|
message: "开始执行清洗任务",
|
|
details: "任务ID: task_20251216_143512",
|
|
},
|
|
{
|
|
id: 2,
|
|
timestamp: "2025-12-16 14:35:13",
|
|
level: "INFO",
|
|
message: "连接源数据库成功",
|
|
details: "MySQL: 10.88.182.62:3305",
|
|
},
|
|
{
|
|
id: 3,
|
|
timestamp: "2025-12-16 14:35:15",
|
|
level: "INFO",
|
|
message: "读取源数据",
|
|
details: "共读取 128,956 条记录",
|
|
},
|
|
{
|
|
id: 4,
|
|
timestamp: "2025-12-16 14:35:18",
|
|
level: "WARN",
|
|
message: "发现异常数据",
|
|
details: "25 条记录手机号格式不规范",
|
|
},
|
|
{
|
|
id: 5,
|
|
timestamp: "2025-12-16 14:35:22",
|
|
level: "INFO",
|
|
message: "执行清洗规则",
|
|
details: "应用规则: 手机号标准化",
|
|
},
|
|
{
|
|
id: 6,
|
|
timestamp: "2025-12-16 14:35:45",
|
|
level: "INFO",
|
|
message: "数据写入完成",
|
|
details: "成功写入 128,931 条, 跳过 25 条",
|
|
},
|
|
{
|
|
id: 7,
|
|
timestamp: "2025-12-16 14:35:46",
|
|
level: "INFO",
|
|
message: "清洗任务完成",
|
|
details: "总耗时: 34秒, 错误率: 0.02%",
|
|
},
|
|
]
|
|
|
|
const EXECUTION_HISTORY = [
|
|
{ id: 1, time: "2025-12-16 14:35", status: "success", processed: 128956, errors: 25, duration: "34秒" },
|
|
{ id: 2, time: "2025-12-16 13:35", status: "success", processed: 125832, errors: 18, duration: "32秒" },
|
|
{ id: 3, time: "2025-12-16 12:35", status: "success", processed: 130215, errors: 22, duration: "36秒" },
|
|
{ id: 4, time: "2025-12-16 11:35", status: "failed", processed: 45000, errors: 1250, duration: "15秒" },
|
|
{ id: 5, time: "2025-12-16 10:35", status: "success", processed: 128500, errors: 20, duration: "33秒" },
|
|
]
|
|
|
|
export function CleaningRuleLogsDialog({ open, onOpenChange, ruleName }: CleaningRuleLogsDialogProps) {
|
|
const [selectedExecution, setSelectedExecution] = useState(EXECUTION_HISTORY[0])
|
|
|
|
const getLevelColor = (level: string) => {
|
|
switch (level) {
|
|
case "INFO":
|
|
return "text-blue-600 bg-blue-50"
|
|
case "WARN":
|
|
return "text-amber-600 bg-amber-50"
|
|
case "ERROR":
|
|
return "text-red-600 bg-red-50"
|
|
default:
|
|
return "text-slate-600 bg-slate-50"
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-4xl max-h-[80vh]">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<FileText className="w-5 h-5" />
|
|
执行日志 - {ruleName || "清洗规则"}
|
|
</DialogTitle>
|
|
<DialogDescription>查看规则执行历史和详细日志</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{/* 执行历史列表 */}
|
|
<div className="col-span-1 border-r pr-4">
|
|
<h4 className="text-sm font-medium text-slate-700 mb-3">执行历史</h4>
|
|
<ScrollArea className="h-[400px]">
|
|
<div className="space-y-2">
|
|
{EXECUTION_HISTORY.map((exec) => (
|
|
<div
|
|
key={exec.id}
|
|
className={`p-3 rounded-lg cursor-pointer transition-colors ${
|
|
selectedExecution.id === exec.id
|
|
? "bg-blue-50 border border-blue-200"
|
|
: "bg-slate-50 hover:bg-slate-100"
|
|
}`}
|
|
onClick={() => setSelectedExecution(exec)}
|
|
>
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="text-xs text-slate-500">{exec.time}</span>
|
|
{exec.status === "success" ? (
|
|
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
|
) : (
|
|
<XCircle className="w-4 h-4 text-red-500" />
|
|
)}
|
|
</div>
|
|
<div className="text-sm">
|
|
<span className="font-medium">{exec.processed.toLocaleString()}</span>
|
|
<span className="text-slate-500"> 条 / </span>
|
|
<span className={exec.status === "failed" ? "text-red-600" : "text-slate-600"}>
|
|
{exec.errors} 错误
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-1 text-xs text-slate-500 mt-1">
|
|
<Clock className="w-3 h-3" />
|
|
{exec.duration}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
|
|
{/* 日志详情 */}
|
|
<div className="col-span-2">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h4 className="text-sm font-medium text-slate-700">执行日志详情</h4>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" size="sm">
|
|
<RefreshCw className="w-4 h-4 mr-1" />
|
|
刷新
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
<Download className="w-4 h-4 mr-1" />
|
|
导出
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<ScrollArea className="h-[400px] bg-slate-900 rounded-lg p-4">
|
|
<div className="space-y-2 font-mono text-sm">
|
|
{MOCK_LOGS.map((log) => (
|
|
<div key={log.id} className="flex gap-3">
|
|
<span className="text-slate-500 whitespace-nowrap">{log.timestamp}</span>
|
|
<Badge className={`${getLevelColor(log.level)} text-xs px-1.5`}>{log.level}</Badge>
|
|
<div>
|
|
<span className="text-slate-200">{log.message}</span>
|
|
<span className="text-slate-500 ml-2">- {log.details}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|