import React, { useCallback, useEffect, useMemo, useState } from 'react'; import apiClient from '../api/client'; import { getDinsarCatalogStatus, getDinsarProductDetail, getDinsarProductCleanupPlan, listDinsarProductPairs, } from '../api/dinsarProducts'; import { DINSAR_ENGINE_ALL, buildDinsarEngineOptions, getDinsarEngineMeta, } from '../utils/dinsarEngines'; import { formatSatelliteFamilyLabel, inferSatelliteFamilyFromResultLike } from '../utils/satelliteFamily'; const STATUS_TONE_MAP = { READY: 'ready', PARTIAL: 'warn', QUARANTINED: 'error', WARN: 'warn', ERROR: 'error', REBUILDING: 'info', ready: 'ready', missing: 'neutral', failed: 'error', blocked: 'warn', legacy: 'neutral', }; function formatDateTime(value) { if (!value) return '-'; try { return new Date(value).toLocaleString(); } catch { return String(value); } } function formatBytes(value) { const size = Number(value || 0); if (!Number.isFinite(size) || size <= 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let next = size; let index = 0; while (next >= 1024 && index < units.length - 1) { next /= 1024; index += 1; } return `${next.toFixed(index === 0 ? 0 : 1)} ${units[index]}`; } function getMessageTone(message) { return /失败|error|Error|ERROR/.test(String(message || '')) ? 'error' : 'success'; } function StatusPill({ label, tone = 'neutral' }) { return {label}; } function engineStatusTone(status) { const normalized = String(status || '').toLowerCase(); if (normalized === 'ready') return 'ready'; if (normalized === 'failed') return 'error'; if (normalized === 'blocked') return 'warn'; if (normalized === 'running') return 'info'; return 'neutral'; } function formatEngineResultStatus(status) { const normalized = String(status || 'missing').toLowerCase(); if (normalized === 'ready') return '已生产'; if (normalized === 'failed') return '生产失败'; if (normalized === 'missing') return '未生产'; if (normalized === 'blocked') return '不适用'; if (normalized === 'running') return '生产中'; if (normalized === 'legacy') return '历史结果'; return status || '未生产'; } function buildEngineResultTitle(result = {}) { const parts = [formatEngineResultStatus(result.status)]; if (result.skip_reason === 'production_failed') { parts.push('该引擎已有失败生产记录,不是单纯缺失结果。'); } if (result.production_run_id) { parts.push(`run=${result.production_run_id}`); } if (result.production_error) { parts.push(String(result.production_error).replace(/\s+/g, ' ').slice(0, 220)); } if (result.latest_log_path) { parts.push(`log=${result.latest_log_path}`); } return parts.filter(Boolean).join('\n'); } function MetaField({ label, value, multiline = false }) { const displayValue = value === null || value === undefined || value === '' ? '-' : value; return (
{label} {displayValue}
); } export default function DinsarCatalogPanel({ readOnly = false, compact = false, }) { const [catalogStatus, setCatalogStatus] = useState(null); const [products, setProducts] = useState([]); const [productPairs, setProductPairs] = useState([]); const [selectedPairKey, setSelectedPairKey] = useState(''); const [selectedProductId, setSelectedProductId] = useState(null); const [selectedProduct, setSelectedProduct] = useState(null); const [cleanupPlan, setCleanupPlan] = useState(null); const [cleanupPlanLoading, setCleanupPlanLoading] = useState(false); const [loading, setLoading] = useState(false); const [detailLoading, setDetailLoading] = useState(false); const [actionMessage, setActionMessage] = useState(''); const [engineFilter, setEngineFilter] = useState(DINSAR_ENGINE_ALL); const [queryDraft, setQueryDraft] = useState(''); const [queryApplied, setQueryApplied] = useState(''); const listLimit = compact ? 8 : 18; const previewBaseUrl = apiClient.defaults.baseURL || '/api'; const engineOptions = useMemo( () => buildDinsarEngineOptions([], { includeKnown: true }), [] ); const selectedEngineMeta = useMemo( () => (engineFilter === DINSAR_ENGINE_ALL ? null : getDinsarEngineMeta(engineFilter)), [engineFilter] ); useEffect(() => { if (engineFilter === DINSAR_ENGINE_ALL) return; if (!engineOptions.some((option) => option.value === engineFilter)) { setEngineFilter(DINSAR_ENGINE_ALL); } }, [engineFilter, engineOptions]); const loadCatalog = useCallback(async () => { setLoading(true); try { const [statusData, productData] = await Promise.all([ getDinsarCatalogStatus(), listDinsarProductPairs({ limit: listLimit, offset: 0, engine_code: engineFilter === DINSAR_ENGINE_ALL ? undefined : engineFilter, query: queryApplied || undefined, }), ]); setCatalogStatus(statusData); const nextPairs = Array.isArray(productData?.items) ? productData.items : []; setProductPairs(nextPairs); const nextItems = nextPairs .map((item) => ({ id: item.primary_product_id, engine_code: item.primary_engine_code, pair_key: item.pair_key, })) .filter((item) => item.id); setProducts(nextItems); setSelectedPairKey((current) => { if (current && nextPairs.some((item) => (item.pair_key || `pair:${item.primary_product_id}`) === current)) { return current; } const first = nextPairs[0]; return first ? (first.pair_key || `pair:${first.primary_product_id}`) : ''; }); setSelectedProductId((current) => { if (current && nextItems.some((item) => item.id === current)) { return current; } return nextPairs[0]?.primary_product_id ?? null; }); } catch (error) { setActionMessage(`结果目录状态加载失败:${error?.response?.data?.detail || error.message}`); setCatalogStatus(null); setProducts([]); setProductPairs([]); setSelectedPairKey(''); setSelectedProductId(null); } finally { setLoading(false); } }, [engineFilter, listLimit, queryApplied]); const loadProductDetail = useCallback(async (productId) => { if (!productId) { setSelectedProduct(null); setCleanupPlan(null); return; } setSelectedProduct(null); setCleanupPlan(null); setDetailLoading(true); try { const detail = await getDinsarProductDetail(productId); setSelectedProduct(detail); } catch (error) { setSelectedProduct({ error: error?.response?.data?.detail || error.message || '结果详情加载失败', }); } finally { setDetailLoading(false); } }, []); const handleSelectProduct = useCallback((pairKey, productId) => { setSelectedPairKey(pairKey); setSelectedProductId((current) => (current === productId ? current : productId || null)); }, []); const loadCleanupPlan = useCallback(async () => { if (!selectedProductId) return; setCleanupPlanLoading(true); try { const plan = await getDinsarProductCleanupPlan(selectedProductId); setCleanupPlan(plan); } catch (error) { setCleanupPlan({ error: error?.response?.data?.detail || error.message || '中间文件清理计划加载失败', }); } finally { setCleanupPlanLoading(false); } }, [selectedProductId]); useEffect(() => { loadCatalog(); }, [loadCatalog]); useEffect(() => { loadProductDetail(selectedProductId); }, [loadProductDetail, selectedProductId]); const handleApplyFilters = useCallback(() => { setQueryApplied(queryDraft.trim()); }, [queryDraft]); const handleResetFilters = useCallback(() => { setEngineFilter(DINSAR_ENGINE_ALL); setQueryDraft(''); setQueryApplied(''); }, []); const catalogTone = STATUS_TONE_MAP[catalogStatus?.status] || 'neutral'; const actionTone = getMessageTone(actionMessage); const selectedIssues = Array.isArray(selectedProduct?.issues) ? selectedProduct.issues : []; const selectedAssets = Array.isArray(selectedProduct?.assets) ? selectedProduct.assets : []; const selectedPairingTrace = selectedProduct?.pairing_trace || null; const selectedPairingNetwork = selectedProduct?.pairing_network || null; const selectedPairingRun = selectedPairingNetwork?.run || null; const selectedPairingEdge = selectedPairingNetwork?.edge || null; const selectedPairingMetric = selectedPairingNetwork?.metric || null; const selectedProductEngine = getDinsarEngineMeta(selectedProduct?.engine_code); const selectedStatusTone = STATUS_TONE_MAP[selectedProduct?.status] || 'neutral'; return (
{compact ? '结果目录状态' : '标准结果包目录'}

{compact ? '查看结果包目录与数据库索引是否一致。' : '统一结果目录按 engine + pair + run 管理,便于同一对影像保留多套生产结果并行对比。'}

{selectedEngineMeta && ( {selectedEngineMeta.shortLabel} )}
目录状态 {catalogStatus?.status || '未知'}
需要重建 {catalogStatus?.needs_rebuild ? '是' : '否'} {catalogStatus?.needs_rebuild ? 'Manifest 与数据库存在漂移' : '目录登记正常'}
Manifest / 数据库 {catalogStatus?.manifest_count ?? 0} / {catalogStatus?.db_count ?? 0} 已登记结果包总量
问题数量 {catalogStatus?.issue_count ?? 0} 含缺失文件与健康异常
结果包根目录:{catalogStatus?.storage_root || '-'}
最近消息:{catalogStatus?.last_message || '-'}
最近全量重建:{formatDateTime(catalogStatus?.last_full_rebuild_at)}
{actionMessage && (
{actionMessage}
)}
结果包详情 查看选中结果的发布信息、配对溯源与资产健康
{!selectedProductId ? (
请选择一个已有结果的任务查看详情。
) : detailLoading || !selectedProduct ? (
正在加载详情...
) : selectedProduct?.error ? (
{selectedProduct.error}
) : (
{(() => { const satelliteFamily = inferSatelliteFamilyFromResultLike(selectedProduct?.profile || selectedProduct); return (
{selectedProduct.display_name

{selectedProduct.display_name || selectedProduct.product_id}

{selectedProduct.task_alias || selectedProduct.task_name || '未命名任务'}

{selectedProductEngine.shortLabel} {satelliteFamily && ( {formatSatelliteFamilyLabel(satelliteFamily)} )}
); })()}
时空概览
空间范围
配对追踪
{!selectedPairingTrace?.network_run_id ? (
当前结果未携带完整的配对网络追踪信息。
) : (
)}
资产列表 ({selectedAssets.length})
{selectedAssets.length === 0 ? (
暂无资产记录。
) : (
{selectedAssets.map((asset) => (
{asset.asset_role} {asset.exists_flag ? '文件存在' : '文件缺失'}
{asset.asset_name}
{asset.absolute_path}
))}
)}
问题列表 ({selectedIssues.length})
{selectedIssues.length === 0 ? (
当前没有登记问题。
) : (
{selectedIssues.map((issue) => (
{issue.issue_code} {issue.severity}
{issue.message}
{issue.repair_action && (
建议修复动作:{issue.repair_action}
)}
))}
)}
中间文件清理计划
{!cleanupPlan ? (
尚未生成清理计划。
) : cleanupPlan.error ? (
{cleanupPlan.error}
) : (
{Array.isArray(cleanupPlan.blockers) && cleanupPlan.blockers.length > 0 && (
{cleanupPlan.blockers.map((blocker) => (
{blocker}
))}
)}
{(cleanupPlan.candidates || []).map((candidate) => (
{candidate.reason} {candidate.exists ? formatBytes(candidate.size_bytes) : '不存在'}
{candidate.path}
))}
)}
)}
); }