import { useState, useEffect } from 'react' import { Search, RefreshCw, ScrollText, Eye, Clock, Activity, AlertCircle } from 'lucide-react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { Badge } from '@/components/ui/badge' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { ScrollArea } from '@/components/ui/scroll-area' import { getLogList } from '@/api/system/log' import type { OperationLog } from '@/api/system' export default function Logs() { const [logs, setLogs] = useState([]) const [total, setTotal] = useState(0) const [loading, setLoading] = useState(false) const [search, setSearch] = useState({ path: '', method: '', status: undefined as number | undefined }) const [pagination, setPagination] = useState({ current: 1, pageSize: 12 }) // Dialog State for viewing details const [detailOpen, setDetailOpen] = useState(false) const [activeLog, setActiveLog] = useState(null) const fetchLogs = async () => { setLoading(true) try { const res = await getLogList({ ...pagination, ...search }) if (res) { setLogs(res.list) setTotal(res.total) } } finally { setLoading(false) } } useEffect(() => { fetchLogs() }, [pagination.current, pagination.pageSize]) const handleSearch = () => { if (pagination.current === 1) fetchLogs() else setPagination({ ...pagination, current: 1 }) } const handleReset = () => { setSearch({ path: '', method: '', status: undefined }) if (pagination.current === 1) setTimeout(() => fetchLogs(), 0) else setPagination({ ...pagination, current: 1 }) } const openDetail = (log: OperationLog) => { setActiveLog(log) setDetailOpen(true) } const getMethodColor = (method: string) => { switch (method) { case 'GET': return 'bg-blue-50 text-blue-600 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400' case 'POST': return 'bg-emerald-50 text-emerald-600 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' case 'PUT': return 'bg-amber-50 text-amber-600 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400' case 'DELETE': return 'bg-red-50 text-red-600 border-red-200 dark:bg-red-900/30 dark:text-red-400' default: return 'bg-muted text-muted-foreground' } } return (

操作日志

监控系统接口调用情况、操作耗时及返回状态。

日志流水
setSearch({ ...search, status: e.target.value ? Number(e.target.value) : undefined })} onKeyDown={e => e.key === 'Enter' && handleSearch()} />
setSearch({ ...search, path: e.target.value })} onKeyDown={e => e.key === 'Enter' && handleSearch()} />
操作者 客户端 方法 接口描述 & 路径 状态 耗时 操作时间 详情 {loading ? ( 检索日志中... ) : logs.length === 0 ? ( 未检索到相关日志 ) : ( logs.map(log => ( {log.userId || '-'} {log.clientId || 'System'} {log.method}
{log.path}
{log.status === 200 ? (
200 OK
) : (
{log.status}
)}
200000000 ? 'text-amber-500 font-medium' : 'text-muted-foreground'}`}> {(log.latency / 1000000).toFixed(0)}ms
{new Date(log.createdAt * 1000).toLocaleString('zh-CN')}
)) )}
共 {total} 条记录
第 {pagination.current} 页
{/* Detail Dialog */} 日志详情 请求链路分析与载荷追踪 {activeLog && (
接口路径: {activeLog.path}
操作者ID: {activeLog.userId}
客户端: {activeLog.clientId}
IP地址: {activeLog.ip}
响应耗时: 200000000 ? 'text-amber-500' : 'text-emerald-600'}`}> {(activeLog.latency / 1000000).toFixed(0)}ms
状态码: {activeLog.status}
操作时间: {new Date(activeLog.createdAt * 1000).toLocaleString('zh-CN')}
User-Agent: {activeLog.agent}
{activeLog.body && (

Request Payload

                      {JSON.stringify(JSON.parse(activeLog.body), null, 2)}
                    
)} {activeLog.resp && (

Response Data

                      {JSON.stringify(JSON.parse(activeLog.resp), null, 2)}
                    
)}
)}
) }