2026-04-29-21-51-19 - 全栈系统改造:FastAPI后端+SAM2+PostgreSQL+Redis+MinIO+前端Zustand重构
This commit is contained in:
@@ -1,10 +1,99 @@
|
||||
import React from 'react';
|
||||
import { Activity, Clock, Folders, CheckCircle2 } from 'lucide-react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Activity, Clock, Folders, CheckCircle2, Loader2 } from 'lucide-react';
|
||||
import { progressWS, type ProgressMessage } from '../lib/websocket';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface QueueTask {
|
||||
id: string;
|
||||
name: string;
|
||||
progress: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export function Dashboard() {
|
||||
const [tasks, setTasks] = useState<QueueTask[]>([
|
||||
{ id: '1', name: 'City_Driving_Dataset_004.mp4', progress: 85, status: '正在截取帧 (30fps)' },
|
||||
{ id: '2', name: 'Pedestrian_Night_Vision_02.mkv', progress: 32, status: '正在截取帧 (60fps)' },
|
||||
{ id: '3', name: 'Drone_Mapping_Sector_7.avi', progress: 0, status: '队列排队等待中' },
|
||||
]);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [activityLog, setActivityLog] = useState<Array<{ time: string; message: string; project?: string }>>([
|
||||
{ time: '10 分钟前', message: '语义归档完成 54 帧', project: 'Highway_Data' },
|
||||
{ time: '25 分钟前', message: '项目解析开始', project: 'City_Driving_Dataset_004' },
|
||||
{ time: '1 小时前', message: '模板库更新: Cityscapes_v2', project: '系统' },
|
||||
{ time: '2 小时前', message: 'AI 推理完成 12 个实例', project: 'Nav_Cam_Left' },
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
progressWS.connect();
|
||||
|
||||
const unsubscribe = progressWS.onProgress((data: ProgressMessage) => {
|
||||
setIsConnected(progressWS.isConnected());
|
||||
|
||||
if (data.type === 'progress' && data.taskId && data.filename) {
|
||||
setTasks((prev) => {
|
||||
const exists = prev.find((t) => t.id === data.taskId);
|
||||
if (exists) {
|
||||
return prev.map((t) =>
|
||||
t.id === data.taskId
|
||||
? { ...t, progress: data.progress ?? t.progress, status: data.status ?? t.status }
|
||||
: t
|
||||
);
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: data.taskId!,
|
||||
name: data.filename!,
|
||||
progress: data.progress ?? 0,
|
||||
status: data.status ?? '处理中',
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
if (data.type === 'complete' && data.taskId) {
|
||||
setTasks((prev) =>
|
||||
prev.map((t) =>
|
||||
t.id === data.taskId ? { ...t, progress: 100, status: '已完成' } : t
|
||||
)
|
||||
);
|
||||
setActivityLog((prev) => [
|
||||
{ time: '刚刚', message: `解析完成: ${data.filename || data.taskId}`, project: '系统' },
|
||||
...prev.slice(0, 9),
|
||||
]);
|
||||
}
|
||||
|
||||
if (data.type === 'error' && data.taskId) {
|
||||
setTasks((prev) =>
|
||||
prev.map((t) =>
|
||||
t.id === data.taskId ? { ...t, status: `错误: ${data.message || '未知错误'}` } : t
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.type === 'status') {
|
||||
setActivityLog((prev) => [
|
||||
{ time: '刚刚', message: data.message || '状态更新', project: '系统' },
|
||||
...prev.slice(0, 9),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
const checkConnection = setInterval(() => {
|
||||
setIsConnected(progressWS.isConnected());
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(checkConnection);
|
||||
progressWS.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const stats = [
|
||||
{ label: '运行中项目', value: '14', icon: Folders, color: 'text-blue-400', bg: 'bg-blue-400/10' },
|
||||
{ label: '排队处理任务', value: '3,291', icon: Clock, color: 'text-orange-400', bg: 'bg-orange-400/10' },
|
||||
{ label: '排队处理任务', value: tasks.length.toString(), icon: Clock, color: 'text-orange-400', bg: 'bg-orange-400/10' },
|
||||
{ label: '已归档批次', value: '128', icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-400/10' },
|
||||
{ label: '系统负载', value: '78%', icon: Activity, color: 'text-cyan-400', bg: 'bg-cyan-400/10' },
|
||||
];
|
||||
@@ -12,7 +101,18 @@ export function Dashboard() {
|
||||
return (
|
||||
<div className="p-8 w-full h-full overflow-y-auto bg-[#0a0a0a]">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-medium tracking-tight text-white">系统整体概况</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-medium tracking-tight text-white">系统整体概况</h1>
|
||||
<div className={cn(
|
||||
"flex items-center gap-1.5 text-[10px] uppercase font-mono px-2 py-1 rounded border",
|
||||
isConnected
|
||||
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
|
||||
: "bg-amber-500/10 text-amber-400 border-amber-500/20"
|
||||
)}>
|
||||
<div className={cn("w-1.5 h-1.5 rounded-full", isConnected ? "bg-emerald-500" : "bg-amber-500 animate-pulse")} />
|
||||
{isConnected ? 'WebSocket 已连接' : 'WebSocket 断开'}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm mt-1">系统全局数据吞吐状态与所有接入项目进度实时洞察驾驶舱。</p>
|
||||
</header>
|
||||
|
||||
@@ -37,36 +137,43 @@ export function Dashboard() {
|
||||
<div className="lg:col-span-2 bg-[#111] border border-white/5 rounded-xl p-6 min-h-[400px]">
|
||||
<h2 className="text-sm font-medium text-gray-400 uppercase tracking-widest mb-6">解析队列 (FFmpeg 挂起任务)</h2>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ name: 'City_Driving_Dataset_004.mp4', progress: 85, status: '正在截取帧 (30fps)' },
|
||||
{ name: 'Pedestrian_Night_Vision_02.mkv', progress: 32, status: '正在截取帧 (60fps)' },
|
||||
{ name: 'Drone_Mapping_Sector_7.avi', progress: 0, status: '队列排队等待中' }
|
||||
].map((task, i) => (
|
||||
<div key={i} className="bg-[#0d0d0d] border border-white/5 p-4 rounded-lg">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="bg-[#0d0d0d] border border-white/5 p-4 rounded-lg">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="font-mono text-sm text-gray-200">{task.name}</span>
|
||||
<span className="text-xs text-cyan-400 font-mono">{task.progress}%</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-white/5 rounded-full overflow-hidden mb-2">
|
||||
<div className="h-full bg-gradient-to-r from-cyan-600 to-cyan-400 rounded-full" style={{ width: `${task.progress}%` }} />
|
||||
<div className="h-full bg-gradient-to-r from-cyan-600 to-cyan-400 rounded-full transition-all duration-500" style={{ width: `${task.progress}%` }} />
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 flex items-center gap-2">
|
||||
{task.status === '已完成' ? (
|
||||
<CheckCircle2 size={12} className="text-emerald-400" />
|
||||
) : task.status.includes('错误') ? (
|
||||
<span className="text-red-400">●</span>
|
||||
) : (
|
||||
<Loader2 size={12} className="text-cyan-400 animate-spin" />
|
||||
)}
|
||||
{task.status}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{task.status}</div>
|
||||
</div>
|
||||
))}
|
||||
{tasks.length === 0 && (
|
||||
<div className="text-sm text-gray-500 text-center py-12">当前无处理任务</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#111] border border-white/5 rounded-xl p-6 min-h-[400px]">
|
||||
<h2 className="text-sm font-medium text-gray-400 uppercase tracking-widest mb-6">近期实时流转记录</h2>
|
||||
<div className="space-y-6 relative before:absolute before:inset-0 before:ml-[11px] before:-translate-x-px md:before:mx-auto md:before:translate-x-0 before:h-full before:w-0.5 before:bg-gradient-to-b before:from-transparent before:via-white/10 before:to-transparent">
|
||||
{/* Activity log mockup */}
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
{activityLog.map((log, i) => (
|
||||
<div key={i} className="relative flex items-center justify-between md:justify-normal md:odd:flex-row-reverse group is-active">
|
||||
<div className="flex items-center justify-center w-6 h-6 rounded-full border border-white/10 bg-[#111] group-[.is-active]:bg-cyan-500 group-[.is-active]:border-cyan-400 text-slate-500 group-[.is-active]:text-black shadow shrink-0 md:order-1 md:group-odd:-translate-x-1/2 md:group-even:translate-x-1/2 z-10" />
|
||||
<div className="w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] bg-[#0d0d0d] p-3 rounded border border-white/5">
|
||||
<div className="text-xs text-gray-400 mb-1">10 分钟前</div>
|
||||
<div className="text-sm font-medium text-gray-200">语义归档完成 54 帧</div>
|
||||
<div className="text-xs text-gray-500">归属项目: Highway_Data</div>
|
||||
<div className="text-xs text-gray-400 mb-1">{log.time}</div>
|
||||
<div className="text-sm font-medium text-gray-200">{log.message}</div>
|
||||
<div className="text-xs text-gray-500">归属项目: {log.project}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user