fix(frontend): optimize

This commit is contained in:
ryan
2026-06-17 13:47:12 +08:00
parent 3a05e7f09c
commit 333e45572a
8 changed files with 137 additions and 64 deletions
+7 -44
View File
@@ -19,12 +19,11 @@ limitations under the License.
import {memo, useCallback, useEffect, useRef, useState} from "react"
import {useVirtualizer} from "@tanstack/react-virtual"
import {toast} from "sonner"
import {ArrowDown, ChevronUp, Loader2, Pause, Play} from "lucide-react"
import {ArrowDown, ChevronUp, Loader2} from "lucide-react"
import {AdminService} from "@/lib/services/admin"
import {ErrorInline} from "@/components/layout/error"
import {LoadingStateWithBorder} from "@/components/layout/loading"
import {Badge} from "@/components/ui/badge"
import {Button} from "@/components/ui/button"
interface LogEntry {
@@ -48,7 +47,7 @@ const LogLine = memo(function LogLine({data}: {data: string}) {
: "text-gray-300"
return (
<div className={`${color} whitespace-pre-wrap break-all hover:bg-white/5`}>
<div className={`${color} whitespace-pre-wrap break-all px-2 py-0.5 rounded hover:bg-white/5`}>
{data}
</div>
)
@@ -59,7 +58,7 @@ function getApiBaseUrl(): string {
// If NEXT_PUBLIC_WAVELET_BACKEND_URL is set, use it. Otherwise, use origin.
const base = process.env.NEXT_PUBLIC_WAVELET_BACKEND_URL || ""
if (base.startsWith("http")) return base
// Relative URL fallback
const proto = window.location.protocol
const host = window.location.host
@@ -92,19 +91,14 @@ export function AppLogs() {
const [nextCursor, setNextCursor] = useState(0)
const [loadingMore, setLoadingMore] = useState(false)
const [connected, setConnected] = useState(false)
const [paused, setPaused] = useState(false)
// autoScroll = true → new logs auto-scroll to bottom
const [autoScroll, setAutoScroll] = useState(true)
const containerRef = useRef<HTMLDivElement>(null)
const wsRef = useRef<WebSocket | null>(null)
const pausedRef = useRef(paused)
const autoScrollRef = useRef(autoScroll)
const isUserScrolling = useRef(false)
useEffect(() => { pausedRef.current = paused }, [paused])
useEffect(() => { autoScrollRef.current = autoScroll }, [autoScroll])
const rowVirtualizer = useVirtualizer({
@@ -213,10 +207,7 @@ export function AppLogs() {
const ws = new WebSocket(buildWsUrl())
wsRef.current = ws
ws.onopen = () => { setConnected(true) }
ws.onmessage = (event) => {
if (pausedRef.current) return
try {
const msg = JSON.parse(event.data)
if (msg.type === "log" && msg.data) {
@@ -229,8 +220,7 @@ export function AppLogs() {
} catch { /* ignore */ }
}
ws.onclose = () => { setConnected(false); wsRef.current = null }
ws.onerror = () => { setConnected(false) }
ws.onclose = () => { wsRef.current = null }
}, [])
// ---- Initialize ------------------------------------------------------
@@ -253,8 +243,6 @@ export function AppLogs() {
})
}, [])
const togglePause = useCallback(() => setPaused(p => !p), [])
const reconnect = useCallback(() => connectWs(), [connectWs])
const handleLoadMore = useCallback(() => {
if (nextCursor > 0) loadHistory(nextCursor)
}, [nextCursor, loadHistory])
@@ -265,38 +253,13 @@ export function AppLogs() {
return (
<div className="flex flex-col h-full relative">
{/* Sub Header / Control Bar */}
<div className="flex items-center justify-between pb-3 border-b border-border/40 mb-3">
<div className="text-sm text-muted-foreground font-medium">
系统后台实时输出的运行日志 (最多缓存 2000 行)
</div>
<div className="flex items-center gap-2">
<Badge variant={connected ? "secondary" : "destructive"} className="h-6">
{connected ? "已连接" : "断开连接"}
</Badge>
{connected && (
<Button variant="outline" size="sm" onClick={togglePause} className="h-8">
{paused
? <><Play className="size-3.5 mr-1.5" />恢复</>
: <><Pause className="size-3.5 mr-1.5" />暂停</>
}
</Button>
)}
{!connected && (
<Button variant="outline" size="sm" onClick={reconnect} className="h-8">
重新连接
</Button>
)}
</div>
</div>
{/* Log viewer — fixed height, scrollable */}
<div
ref={containerRef}
onScroll={handleScroll}
onWheel={handleWheel}
onTouchStart={handleTouchStart}
className="h-[calc(100vh-270px)] overflow-y-auto overflow-x-hidden rounded-md border border-border/50 bg-[#0d1117] font-mono text-[13px] leading-5 relative"
className="h-[calc(100vh-270px)] overflow-y-auto overflow-x-hidden rounded-lg border border-border/40 bg-[#0d1117] font-mono text-[13px] leading-5 relative"
>
{/* Load older logs */}
{hasMore && (
@@ -317,10 +280,10 @@ export function AppLogs() {
)}
{logs.length === 0 ? (
<div className="px-3 py-8 text-center text-gray-500">暂无日志</div>
<div className="px-4 py-8 text-center text-gray-500">暂无日志</div>
) : (
<div
className="relative px-3 py-2"
className="relative px-3 py-3"
style={{height: `${rowVirtualizer.getTotalSize()}px`}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {