feat: Implement S2A account cleaner management and a global application configuration context, with supporting backend API.

This commit is contained in:
2026-01-31 02:24:08 +08:00
parent 92383f2f20
commit 634b493524
4 changed files with 251 additions and 91 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { useState, useEffect, useRef } from 'react'
import { Terminal, Play, Pause, Trash2, ChevronDown } from 'lucide-react'
interface LogEntry {
@@ -43,11 +43,34 @@ export default function LiveLogViewer({
const eventSourceRef = useRef<EventSource | null>(null)
const pausedLogsRef = useRef<LogEntry[]>([])
// 连接 SSE
const connect = useCallback(() => {
if (eventSourceRef.current) {
eventSourceRef.current.close()
const isPausedRef = useRef(isPaused)
isPausedRef.current = isPaused
// 加载历史日志
const loadHistoryLogs = async () => {
try {
const res = await fetch('/api/logs')
const data = await res.json()
if (data.code === 0 && Array.isArray(data.data)) {
// 转换为 LogEntry 格式
const historyLogs: LogEntry[] = data.data.map((log: { level: string; message: string; timestamp: string; email?: string; module?: string }) => ({
type: 'log',
level: log.level || 'info',
message: log.message || '',
timestamp: log.timestamp ? new Date(log.timestamp).toLocaleTimeString('zh-CN') : '',
email: log.email,
module: log.module || 'system',
}))
setLogs(historyLogs.slice(-maxLogs))
}
} catch (e) {
console.error('Failed to load history logs:', e)
}
}
// 组件挂载时先加载历史日志,再连接 SSE
useEffect(() => {
loadHistoryLogs()
const es = new EventSource('/api/logs/stream')
eventSourceRef.current = es
@@ -61,12 +84,11 @@ export default function LiveLogViewer({
const data = JSON.parse(event.data) as LogEntry
if (data.type === 'connected') {
console.log('SSE connected:', data)
return
}
if (data.type === 'log') {
if (isPaused) {
if (isPausedRef.current) {
pausedLogsRef.current.push(data)
} else {
setLogs((prev) => {
@@ -82,20 +104,13 @@ export default function LiveLogViewer({
es.onerror = () => {
setIsConnected(false)
// 自动重连
setTimeout(connect, 3000)
}
}, [isPaused, maxLogs])
// 组件挂载时连接
useEffect(() => {
connect()
return () => {
if (eventSourceRef.current) {
eventSourceRef.current.close()
}
es.close()
}
}, [connect])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// 自动滚动
useEffect(() => {