118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
import { snapshotApi } from '@/lib/api/snapshot.api';
|
|
import type { BackupTarget, SnapshotTask } from '@/types/snapshot.types';
|
|
|
|
interface TargetProgress {
|
|
target: BackupTarget;
|
|
percent: number;
|
|
message: string;
|
|
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
}
|
|
|
|
interface UseSnapshotPollingReturn {
|
|
progresses: Map<string, TargetProgress>;
|
|
taskCompleted: boolean;
|
|
totalSize: string | null;
|
|
duration: number | null;
|
|
error: string | null;
|
|
}
|
|
|
|
const POLL_INTERVAL = 2000; // 2 秒轮询
|
|
|
|
export function useSnapshotPolling(taskId: string | null): UseSnapshotPollingReturn {
|
|
const [progresses, setProgresses] = useState<Map<string, TargetProgress>>(new Map());
|
|
const [taskCompleted, setTaskCompleted] = useState(false);
|
|
const [totalSize, setTotalSize] = useState<string | null>(null);
|
|
const [duration, setDuration] = useState<number | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
const updateFromTask = useCallback((task: SnapshotTask) => {
|
|
// 从 task.details 构建进度 Map
|
|
const next = new Map<string, TargetProgress>();
|
|
for (const d of task.details) {
|
|
const statusMap: Record<string, TargetProgress['status']> = {
|
|
PENDING: 'pending',
|
|
RUNNING: 'running',
|
|
COMPLETED: 'completed',
|
|
FAILED: 'failed',
|
|
};
|
|
next.set(d.target, {
|
|
target: d.target,
|
|
percent: d.status === 'COMPLETED' ? 100 : d.progress,
|
|
message:
|
|
d.error
|
|
? d.error
|
|
: d.status === 'COMPLETED'
|
|
? '完成'
|
|
: d.status === 'RUNNING'
|
|
? `备份中... ${d.progress}%`
|
|
: '等待中',
|
|
status: statusMap[d.status] || 'pending',
|
|
});
|
|
}
|
|
setProgresses(next);
|
|
|
|
// 任务整体完成/失败 → 停止轮询
|
|
if (task.status === 'COMPLETED' || task.status === 'FAILED') {
|
|
setTaskCompleted(true);
|
|
setTotalSize(task.totalSize);
|
|
if (task.startedAt && task.completedAt) {
|
|
setDuration(new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime());
|
|
}
|
|
if (task.error) {
|
|
setError(task.error);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!taskId) return;
|
|
|
|
// 重置状态
|
|
setProgresses(new Map());
|
|
setTaskCompleted(false);
|
|
setTotalSize(null);
|
|
setDuration(null);
|
|
setError(null);
|
|
|
|
let stopped = false;
|
|
|
|
const poll = async () => {
|
|
if (stopped) return;
|
|
try {
|
|
const task = await snapshotApi.getById(taskId);
|
|
if (stopped) return;
|
|
updateFromTask(task);
|
|
|
|
// 任务结束,停止轮询
|
|
if (task.status === 'COMPLETED' || task.status === 'FAILED') {
|
|
if (timerRef.current) {
|
|
clearInterval(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
}
|
|
} catch {
|
|
// 网络错误静默忽略,下次继续轮询
|
|
}
|
|
};
|
|
|
|
// 立即拉一次
|
|
poll();
|
|
// 定时轮询
|
|
timerRef.current = setInterval(poll, POLL_INTERVAL);
|
|
|
|
return () => {
|
|
stopped = true;
|
|
if (timerRef.current) {
|
|
clearInterval(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
};
|
|
}, [taskId, updateFromTask]);
|
|
|
|
return { progresses, taskCompleted, totalSize, duration, error };
|
|
}
|