фикс задач

This commit is contained in:
dv 2026-05-28 17:37:42 +03:00
parent de0a28ad62
commit 3440c45089
2 changed files with 42 additions and 8 deletions

View file

@ -74,11 +74,12 @@ export const useTasks = (page = 1, pageSize = 50) =>
refetchInterval: 5000,
})
export const useTask = (id: string | undefined) =>
export const useTask = (id: string | undefined, options?: { refetchInterval?: number | false }) =>
useQuery({
queryKey: ['tasks', id],
queryFn: () => api.get<TaskDetail>(`/api/v1/tasks/${id}`).then((r) => r.data),
enabled: !!id,
refetchInterval: options?.refetchInterval,
})
// Subscribe to SSE for a running task — merges results in cache, invalidates on completion.

View file

@ -78,10 +78,17 @@ function groupBy<T>(items: T[], key: (item: T) => string): Record<string, T[]> {
export function TaskDetailPage() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const { data: task, isLoading } = useTask(id)
const [isActive, setIsActive] = useState(true)
const { data: task, isLoading } = useTask(id, { refetchInterval: isActive ? 3000 : false })
const cancelTask = useCancelTask()
const createTask = useCreateTask()
useEffect(() => {
if (task) {
setIsActive(task.status === 'pending' || task.status === 'running')
}
}, [task?.status])
const [log, setLog] = useState<LogLine[]>([])
const [liveResults, setLiveResults] = useState<Record<number, DeviceResultState>>({})
const [totalDevices, setTotalDevices] = useState<number | null>(null)
@ -124,13 +131,40 @@ export function TaskDetailPage() {
// Merge DB results with live SSE updates
const mergedResults = useMemo<(TaskResult & Partial<DeviceResultState>)[]>(() => {
if (!task?.device_results) return []
return task.device_results.map((r) => {
const dbResults = task?.device_results ?? []
const merged = dbResults.map((r) => {
const live = liveResults[r.device_id]
return live ? { ...r, ...live } : r
})
const dbDeviceIds = new Set(dbResults.map((r) => r.device_id))
for (const [deviceId, live] of Object.entries(liveResults)) {
const id = Number(deviceId)
if (!dbDeviceIds.has(id)) {
merged.push({
id: 0,
device_id: id,
device_ip: live.device_ip,
device_hostname: live.device_hostname,
object_id: null,
object_name: null,
rack_id: null,
rack_name: null,
status: live.status,
stdout: live.stdout,
stderr: live.stderr,
exit_code: null,
data: null,
duration_ms: live.duration_ms,
executed_at: null,
})
}
}
return merged
}, [task?.device_results, liveResults])
// Fallback: derive totalDevices from task.result or device_results count
const effectiveTotal = totalDevices ?? task?.result?.total ?? (mergedResults.length > 0 ? mergedResults.length : null)
// Determine if results span multiple objects
const multiObject = useMemo(() => {
const objIds = new Set(mergedResults.map((r) => r.object_id).filter(Boolean))
@ -186,7 +220,6 @@ export function TaskDetailPage() {
if (isLoading) return <Spin />
const isActive = task?.status === 'pending' || task?.status === 'running'
const canCancel = isActive && !task?.cancel_requested
const resultColumns = [
@ -347,13 +380,13 @@ export function TaskDetailPage() {
</Col>
</Row>
{totalDevices != null && totalDevices > 0 && (
{effectiveTotal != null && effectiveTotal > 0 && (
<Row style={{ marginBottom: 16 }}>
<Col span={24}>
{(() => {
const completed = mergedResults.filter((r) => r.status !== 'running').length
const failed = mergedResults.filter((r) => r.status === 'failed').length
const percent = Math.round((completed / totalDevices) * 100)
const percent = Math.round((completed / effectiveTotal) * 100)
const isDone = !isActive
const progressStatus = isDone
? (failed > 0 ? 'exception' : 'success')
@ -362,7 +395,7 @@ export function TaskDetailPage() {
<Progress
percent={percent}
status={progressStatus}
format={() => `${completed} / ${totalDevices}`}
format={() => `${completed} / ${effectiveTotal}`}
strokeColor={failed > 0 ? undefined : '#52c41a'}
/>
)