link0518
重构
b88ce1b
import { useState, useEffect, useRef } from 'react';
import { motion } from 'framer-motion';
import { RefreshCw, Trash2, ScrollText, Pause, Play } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import { cn } from '../lib/utils';
export default function Logs() {
const { token: adminToken } = useAuth();
const [logs, setLogs] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [autoRefresh, setAutoRefresh] = useState(false);
const logsEndRef = useRef(null);
const fetchLogs = async () => {
setIsLoading(true);
try {
const res = await fetch('/admin/logs', {
headers: { 'X-Admin-Token': adminToken }
});
const text = await res.text();
// Parse logs: Assuming they are line separated JSON or text
let logLines = [];
try {
const json = JSON.parse(text);
if (Array.isArray(json)) logLines = json;
} catch {
logLines = text.split('\n').filter(Boolean);
}
setLogs(logLines);
} catch (error) {
console.error('Failed to fetch logs', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchLogs();
}, [adminToken]);
useEffect(() => {
let interval;
if (autoRefresh) {
interval = setInterval(fetchLogs, 5000);
}
return () => clearInterval(interval);
}, [autoRefresh, adminToken]);
const clearLogs = async () => {
if (!confirm('确定要清空日志吗?')) return;
try {
await fetch('/admin/logs', {
method: 'DELETE',
headers: { 'X-Admin-Token': adminToken }
});
setLogs([]);
} catch (error) {
console.error('Failed to clear logs', error);
}
};
return (
<div className="space-y-6 h-[calc(100vh-8rem)] flex flex-col">
<div className="flex justify-between items-center shrink-0">
<div>
<h2 className="text-2xl font-semibold text-zinc-900 tracking-tight">系统日志</h2>
<p className="text-zinc-500">查看实时系统运行日志</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setAutoRefresh(!autoRefresh)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-lg transition-colors font-medium text-sm border",
autoRefresh
? "bg-emerald-50 text-emerald-600 border-emerald-100"
: "bg-white border-zinc-200 text-zinc-600 hover:bg-zinc-50"
)}
>
{autoRefresh ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
{autoRefresh ? '自动刷新中' : '自动刷新'}
</button>
<button
onClick={fetchLogs}
className="p-2 text-zinc-600 hover:bg-zinc-100 rounded-lg transition-colors border border-transparent hover:border-zinc-200"
>
<RefreshCw className={cn("w-5 h-5", isLoading && "animate-spin")} />
</button>
<button
onClick={clearLogs}
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors border border-transparent hover:border-red-100"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
</div>
<div className="flex-1 bg-zinc-950 rounded-2xl border border-zinc-800 overflow-hidden flex flex-col shadow-inner">
<div className="flex-1 overflow-y-auto p-4 space-y-1 font-mono text-sm">
{logs.length === 0 ? (
<div className="text-zinc-500 text-center py-12">暂无日志</div>
) : (
logs.map((log, idx) => (
<div key={idx} className="text-zinc-300 break-all hover:bg-white/5 px-2 py-0.5 rounded transition-colors">
{typeof log === 'string' ? log : JSON.stringify(log)}
</div>
))
)}
<div ref={logsEndRef} />
</div>
</div>
</div>
);
}