import React, { useCallback, useEffect, useState } from 'react'; import { scanDinsarResults } from './api/dinsar'; import { extractDispResults } from './api/idl'; import { clearTaskLogs, deleteTaskLog, getActiveTasks, getTaskLogs } from './api/tasks'; import DinsarCatalogPanel from './components/DinsarCatalogPanel'; const card = { background: '#fff', padding: '12px', borderRadius: '8px', border: '1px solid #e2e8f0', marginBottom: '12px', }; const PRODUCT_TASK_TYPES = [ 'SCAN_DINSAR', 'PUBLISH_DINSAR_PRODUCTS', 'REBUILD_DINSAR_CATALOG', ]; const TASK_TYPE_LABEL = { SCAN_DINSAR: 'D-InSAR结果扫描', PUBLISH_DINSAR_PRODUCTS: 'D-InSAR产物发布', REBUILD_DINSAR_CATALOG: 'D-InSAR目录重建', }; const STATUS_LABEL = { PENDING: '等待中', RUNNING: '运行中', COMPLETED: '已完成', FAILED: '失败', CANCELLED: '已取消', CANCELED: '已取消', }; function formatTaskType(taskType) { return TASK_TYPE_LABEL[taskType] || taskType || '-'; } function formatStatus(status) { return STATUS_LABEL[status] || status || '-'; } export default function DinsarProductsPanel({ readOnly = false, onJobQueued }) { const [extractRootDir, setExtractRootDir] = useState(''); const [extractDestDir, setExtractDestDir] = useState(''); const [extractResult, setExtractResult] = useState(null); const [extracting, setExtracting] = useState(false); const [actionMessage, setActionMessage] = useState(''); const [actionError, setActionError] = useState(false); const [scanning, setScanning] = useState(false); const [activeTask, setActiveTask] = useState(null); const [taskLogs, setTaskLogs] = useState([]); const [taskLogsLoading, setTaskLogsLoading] = useState(false); const [taskLogActionLoading, setTaskLogActionLoading] = useState(false); const [taskLogDeletingId, setTaskLogDeletingId] = useState(null); const loadActiveTask = useCallback(async () => { try { const data = await getActiveTasks(); const tasks = Array.isArray(data) ? data : (data?.tasks || []); const relevantTask = tasks.find(task => PRODUCT_TASK_TYPES.includes(task.task_type)) || null; setActiveTask(relevantTask); } catch { setActiveTask(null); } }, []); const loadTaskLogs = useCallback(async taskId => { if (!taskId) { setTaskLogs([]); return; } setTaskLogsLoading(true); try { const data = await getTaskLogs(taskId, 50, 0); setTaskLogs(data?.logs || []); } catch { setTaskLogs([]); } finally { setTaskLogsLoading(false); } }, []); const handleDeleteTaskLog = useCallback(async logId => { const taskId = activeTask?.task_id; if (!taskId || !logId || taskLogActionLoading) return; if (!window.confirm('确定要删除这条任务日志吗?')) return; setTaskLogDeletingId(logId); setTaskLogActionLoading(true); try { await deleteTaskLog(taskId, logId); await loadTaskLogs(taskId); } catch (error) { setActionMessage(`删除日志失败:${error?.response?.data?.detail || error.message}`); setActionError(true); } finally { setTaskLogDeletingId(null); setTaskLogActionLoading(false); } }, [activeTask?.task_id, loadTaskLogs, taskLogActionLoading]); const handleClearTaskLogs = useCallback(async () => { const taskId = activeTask?.task_id; if (!taskId || taskLogActionLoading || taskLogs.length === 0) return; if (!window.confirm('确定要清空当前任务的全部日志吗?')) return; setTaskLogActionLoading(true); try { await clearTaskLogs(taskId); await loadTaskLogs(taskId); } catch (error) { setActionMessage(`清空日志失败:${error?.response?.data?.detail || error.message}`); setActionError(true); } finally { setTaskLogActionLoading(false); } }, [activeTask?.task_id, loadTaskLogs, taskLogActionLoading, taskLogs.length]); useEffect(() => { loadActiveTask(); }, [loadActiveTask]); useEffect(() => { const timer = setInterval(loadActiveTask, 5000); return () => clearInterval(timer); }, [loadActiveTask]); useEffect(() => { const taskId = activeTask?.task_id || ''; loadTaskLogs(taskId); if (!taskId) return undefined; const timer = setInterval(() => loadTaskLogs(taskId), 5000); return () => clearInterval(timer); }, [activeTask?.task_id, loadTaskLogs]); const handleExtract = async () => { if (!extractRootDir.trim()) return; setExtracting(true); setExtractResult(null); setActionMessage(''); setActionError(false); try { const result = await extractDispResults(extractRootDir.trim(), extractDestDir.trim() || null); setExtractResult(result); } catch (err) { setExtractResult({ error: err?.response?.data?.detail || err.message }); } finally { setExtracting(false); } }; const handleScan = async () => { if (readOnly) return; setScanning(true); setActionMessage(''); setActionError(false); try { const result = await scanDinsarResults(); setActionMessage(result?.message || `D-InSAR结果扫描任务已入队:${result?.task_id || '-'}`); if (result?.task_id) { onJobQueued?.(result.task_id); } loadActiveTask(); } catch (err) { setActionError(true); setActionMessage(err?.response?.data?.detail || err.message || 'D-InSAR结果扫描失败'); } finally { setScanning(false); } }; return (
D-InSAR 产物提取与重扫
这里负责把生产目录中的位移结果提取为标准成果包,并触发结果重扫、发布和编目。生产运行与参数配置已独立放到“D-InSAR生产”选项卡。
setExtractRootDir(event.target.value)} placeholder="结果根目录" style={{ flex: 2, minWidth: 220, padding: '5px 8px', borderRadius: 4, border: '1px solid #e2e8f0', fontSize: 13 }} /> setExtractDestDir(event.target.value)} placeholder="目标目录(可选)" style={{ flex: 1, minWidth: 180, padding: '5px 8px', borderRadius: 4, border: '1px solid #e2e8f0', fontSize: 13 }} />
{actionMessage && (
{actionMessage}
)} {extractResult && (
{extractResult.error ? ( 提取失败:{extractResult.error} ) : (
提取完成:复制 {extractResult.copied || 0} 个文件,覆盖 {extractResult.overwritten || 0} 个文件。 {extractResult.catalog?.attempted && extractResult.catalog?.status === 'ok' && ( 成果目录已同步:发布 {extractResult.catalog?.publish?.processed || 0} 项,重建登记 {extractResult.catalog?.rebuild?.registered || 0} 项。 )} {extractResult.catalog?.attempted && extractResult.catalog?.status === 'error' && ( 标准成果包目录同步失败:{extractResult.catalog?.message} )}
)}
)}
产物任务监控
{!activeTask ? (
当前没有正在执行的产物处理任务。
) : (
当前任务
{activeTask.task_id} - {formatTaskType(activeTask.task_type)} - {formatStatus(activeTask.status)} - {activeTask.message}
{activeTask.progress != null && (
{activeTask.progress}%
)}
任务日志
{!readOnly && ( )}
{taskLogsLoading ? (
加载中...
) : taskLogs.length === 0 ? (
暂无日志。
) : (
{taskLogs.map((log, index) => (
{(log.timestamp || '').replace('T', ' ').replace('Z', '')} [{log.level}]
{log.message}
{!readOnly && ( )}
))}
)}
)}
); }