feat: Add LogStream component for real-time log display with Server-Sent Events, historical log loading, and interactive controls.

This commit is contained in:
2026-02-02 04:27:47 +08:00
parent 8fa529e59d
commit ee5b69af13

View File

@@ -170,26 +170,26 @@ export default function LogStream({ hideHeader = false, className = '' }: LogStr
{/* 紧凑型控制栏 (当 hideHeader=true 时显示) */} {/* 紧凑型控制栏 (当 hideHeader=true 时显示) */}
{hideHeader && ( {hideHeader && (
<div className="flex items-center justify-between gap-2 px-4 py-2 border-b border-white/5 bg-slate-900/40 backdrop-blur-md flex-shrink-0"> <div className="flex items-center justify-between gap-2 px-4 py-2.5 border-b border-white/5 bg-slate-900/40 backdrop-blur-md flex-shrink-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className={`w-1.5 h-1.5 rounded-full ${connected ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]' : 'bg-red-500 shadow-[0_0_6px_rgba(239,68,68,0.4)]'}`} className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]' : 'bg-red-500 shadow-[0_0_6px_rgba(239,68,68,0.4)]'}`}
/> />
<span className="text-[10px] font-medium text-slate-500 uppercase tracking-wider"></span> <span className="text-xs font-medium text-slate-400 uppercase tracking-wider"></span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={togglePause} onClick={togglePause}
className="flex items-center gap-1.5 text-[10px] text-slate-400 hover:text-blue-400 transition-colors py-0.5 px-1.5 rounded hover:bg-white/5" className="flex items-center gap-1.5 text-xs text-slate-400 hover:text-blue-400 transition-colors py-1 px-2 rounded hover:bg-white/5"
> >
{paused ? <Play className="h-3 w-3" /> : <Pause className="h-3 w-3" />} {paused ? <Play className="h-3.5 w-3.5" /> : <Pause className="h-3.5 w-3.5" />}
{paused ? '开始' : '暂停'} {paused ? '开始' : '暂停'}
</button> </button>
<button <button
onClick={handleClear} onClick={handleClear}
className="flex items-center gap-1.5 text-[10px] text-slate-400 hover:text-red-400 transition-colors py-0.5 px-1.5 rounded hover:bg-white/5" className="flex items-center gap-1.5 text-xs text-slate-400 hover:text-red-400 transition-colors py-1 px-2 rounded hover:bg-white/5"
> >
<Trash2 className="h-3 w-3" /> <Trash2 className="h-3.5 w-3.5" />
</button> </button>
</div> </div>
@@ -198,24 +198,24 @@ export default function LogStream({ hideHeader = false, className = '' }: LogStr
<div <div
ref={logContainerRef} ref={logContainerRef}
className="flex-1 overflow-y-auto bg-[#0a0c10] p-4 font-mono text-[11px] leading-relaxed selection:bg-blue-500/30 custom-scrollbar" className="flex-1 overflow-y-auto bg-[#0a0c10] p-4 font-mono text-sm leading-relaxed selection:bg-blue-500/30 custom-scrollbar"
> >
{logs.length === 0 ? ( {logs.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center text-slate-600 gap-2 opacity-50"> <div className="h-full flex flex-col items-center justify-center text-slate-600 gap-2 opacity-50">
<Terminal className="h-8 w-8 stroke-[1px]" /> <Terminal className="h-8 w-8 stroke-[1px]" />
<p>...</p> <p className="text-base">...</p>
</div> </div>
) : ( ) : (
<div className="space-y-0.5"> <div className="space-y-1">
{logs.map((log, i) => ( {logs.map((log, i) => (
<div key={i} className="group flex gap-3 py-0.5 border-l-2 border-transparent hover:border-blue-500/30 hover:bg-white/5 transition-all"> <div key={i} className="group flex gap-3 py-1 border-l-2 border-transparent hover:border-blue-500/30 hover:bg-white/5 transition-all">
<span className="text-slate-600 flex-shrink-0 select-none opacity-60 group-hover:opacity-100"> <span className="text-slate-500 flex-shrink-0 select-none opacity-70 group-hover:opacity-100">
{formatTime(log.timestamp)} {formatTime(log.timestamp)}
</span> </span>
{log.step && ( {log.step && (
<span <span
className={`px-1.5 rounded-[4px] text-[9px] font-bold uppercase flex-shrink-0 h-4 flex items-center ${stepColors[log.step] || 'bg-slate-500/20 text-slate-400'}`} className={`px-1.5 rounded text-xs font-bold uppercase flex-shrink-0 h-5 flex items-center ${stepColors[log.step] || 'bg-slate-500/20 text-slate-400'}`}
> >
{stepLabels[log.step] || log.step} {stepLabels[log.step] || log.step}
</span> </span>
@@ -225,12 +225,12 @@ export default function LogStream({ hideHeader = false, className = '' }: LogStr
{log.level === 'success' ? '✓' : log.level === 'error' ? '✗' : '•'} {log.level === 'success' ? '✓' : log.level === 'error' ? '✗' : '•'}
</span> </span>
<span className="text-slate-300 break-all leading-normal group-hover:text-white transition-colors"> <span className="text-slate-200 break-all leading-normal group-hover:text-white transition-colors">
{log.message} {log.message}
</span> </span>
{log.email && ( {log.email && (
<span className="text-slate-500 flex-shrink-0 ml-auto opacity-0 group-hover:opacity-100 transition-opacity bg-slate-800/50 px-1.5 rounded"> <span className="text-slate-400 flex-shrink-0 ml-auto opacity-0 group-hover:opacity-100 transition-opacity bg-slate-800/50 px-2 rounded text-xs">
{log.email} {log.email}
</span> </span>
)} )}