rwadurian/frontend/mining-admin-web/src/lib/hooks/useSnapshotPolling.ts

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 };
}