File size: 4,693 Bytes
b88ce1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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>
    );
}