import React, { useCallback, useEffect, useRef, useState } from 'react'; import './App.css'; import { getHealth } from './api/health'; import { getStatistics } from './api/stats'; import { cleanupSessions } from './api/auth'; import { syncWaterScenesFromDisk } from './api/water'; import { listEngines, runWslCheck } from './api/dinsarProduction'; import { getOrbitStatus, syncOrbitPools } from './api/orbit'; import LogManagementPanel from './LogManagementPanel'; const toNumber = (value) => { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : 0; }; const asArray = (value) => (Array.isArray(value) ? value : []); const asObject = (value) => ( value && typeof value === 'object' && !Array.isArray(value) ? value : {} ); const countGroupedColumns = (groups) => ( Object.values(asObject(groups)).reduce((sum, columns) => sum + asArray(columns).length, 0) ); const formatGroupedColumns = (groups) => ( Object.entries(asObject(groups)) .map(([table, columns]) => `${table}(${asArray(columns).join(', ')})`) .join('; ') ); const formatMismatchList = (items, formatter) => ( asArray(items).map(formatter).join('; ') ); const formatIntegerText = (value) => { const parsed = Number(value); return Number.isFinite(parsed) ? parsed.toLocaleString() : '0'; }; const isCatalogHealthy = (catalog) => { if (!catalog || typeof catalog !== 'object') { return false; } if (catalog.enabled === false) { return true; } return Boolean(catalog.ok) && !catalog.needs_rebuild; }; const formatCatalogStatusLabel = (catalog, en = false) => { if (!catalog || typeof catalog !== 'object') { return en ? 'Unknown' : '未知'; } if (catalog.enabled === false) { return en ? 'Disabled' : '已禁用'; } if (catalog.needs_rebuild) { return en ? 'Rebuild required' : '需要重建'; } return catalog.catalog_status || (en ? 'Unknown' : '未知'); }; const getOrbitSourcePath = (item) => item?.source || item?.source_path || ''; const hasOrbitCorruptionSignal = (item) => Boolean( item && ( item.has_corruption_signal || item.contains_nul_bytes || toNumber(item.nul_byte_count) > 0 || item.tail_has_nul_bytes || Number(item.first_nul_offset) >= 0 || item.read_error ) ); const renderOrbitSourceIssueDetails = (item, en, formatPathText) => { const sourcePath = getOrbitSourcePath(item); const nulCount = toNumber(item?.nul_byte_count); const firstNulOffset = Number(item?.first_nul_offset); const hasNulDetails = item?.contains_nul_bytes || nulCount > 0 || item?.tail_has_nul_bytes || firstNulOffset >= 0; return ( <> {sourcePath && (
{en ? 'Source: ' : '源文件:'}{formatPathText(sourcePath)}
)} {item?.envi_path && (
{en ? 'ENVI: ' : 'ENVI:'}{formatPathText(item.envi_path)}
)} {hasNulDetails && (
{en ? 'NUL bytes: ' : 'NUL 字节:'}{formatIntegerText(nulCount)} {firstNulOffset >= 0 ? `${en ? ', first offset: ' : ',首个偏移:'}${formatIntegerText(firstNulOffset)}` : ''} {item?.tail_has_nul_bytes ? (en ? ' (tail contains NUL)' : '(文件尾含 NUL)') : ''}
)} {item?.read_error && (
{en ? 'Health scan error: ' : '文件健康扫描异常:'}{item.read_error}
)} ); }; const formatSourceRootRole = (role, en = false) => { switch (role) { case 'radar_source': return en ? 'Radar source' : '雷达源目录'; case 'orbit_source': return en ? 'Orbit source' : '精轨源目录'; case 'dinsar_source': return en ? 'D-InSAR source' : 'D-InSAR 结果源目录'; default: return role || (en ? 'Unknown' : '未知'); } }; const buildConsistencySummary = (stats, en = false) => { const dinsar = stats?.dinsar_cache_consistency || {}; const preview = stats?.source_preview_consistency || {}; const xml = stats?.source_xml_consistency || {}; const water = stats?.water_geo_consistency || {}; const issues = [ { key: 'dinsar_db_cached_missing', label: en ? 'D-InSAR: DB cached but file missing' : 'D-InSAR:库标记已缓存但文件缺失', count: toNumber(dinsar.db_cached_but_file_missing_count), level: 'error', }, { key: 'source_ready_missing', label: en ? 'Source: DB READY but preview cache missing' : '源影像:DB READY 但预览缓存缺失', count: toNumber(preview.db_ready_but_cache_missing_count), level: 'error', }, { key: 'water_db_missing_file', label: en ? 'Water: DB DONE but geo_db file missing' : '水体:DB DONE 但 geo_db 文件缺失', count: toNumber(water.registered_but_missing_count), level: 'error', }, { key: 'dinsar_db_uncached_exists', label: en ? 'D-InSAR: DB uncached but file exists' : 'D-InSAR:库未标记缓存但文件已存在', count: toNumber(dinsar.db_uncached_but_file_exists_count), level: 'warn', }, { key: 'manifest_missing_file', label: en ? 'D-InSAR: Manifest references missing file' : 'D-InSAR:Manifest 引用缺失文件', count: toNumber(dinsar.manifest_missing_file_count), level: 'warn', }, { key: 'xml_unparsed', label: en ? 'Source: XML detected but key fields not imported' : '源影像:检测到 XML 但关键字段未入库', count: toNumber(xml.xml_detected_but_unparsed_count), level: 'warn', }, { key: 'xml_missing', label: en ? 'Source: XML not detected' : '源影像:未检测到 XML', count: toNumber(xml.xml_missing_count), level: 'warn', }, { key: 'water_unregistered', label: en ? 'Water: geo_db exists but not registered' : '水体:geo_db 存在但未入库', count: toNumber(water.unregistered_count), level: 'warn', }, ]; const total = issues.reduce((sum, item) => sum + item.count, 0); const critical = issues .filter((item) => item.level === 'error') .reduce((sum, item) => sum + item.count, 0); const warning = total - critical; return { total, critical, warning, issues, }; }; const HEALTH_PANEL_POLL_INTERVAL_MS = 5 * 60 * 1000; const HealthCheckPanel = ({ currentUser }) => { const en = false; const isAdmin = currentUser?.role === 'admin'; const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [lastChecked, setLastChecked] = useState(null); const [consistencySummary, setConsistencySummary] = useState(null); const [consistencyError, setConsistencyError] = useState(''); const [syncLoading, setSyncLoading] = useState(false); const [syncResult, setSyncResult] = useState(null); const [cleanupLoading, setCleanupLoading] = useState(false); const [cleanupResult, setCleanupResult] = useState(null); const [dinsarEngines, setDinsarEngines] = useState([]); const [enginesLoading, setEnginesLoading] = useState(false); const [wslReport, setWslReport] = useState(null); const [wslChecking, setWslChecking] = useState(false); const [orbitStatus, setOrbitStatus] = useState(null); const [orbitSyncing, setOrbitSyncing] = useState(false); const [orbitSyncResult, setOrbitSyncResult] = useState(null); const statusFetchInFlightRef = useRef(false); const refreshOrbitStatus = useCallback(async () => { try { const data = await getOrbitStatus(); setOrbitStatus(data); } catch (err) { setOrbitStatus(null); setOrbitSyncResult({ error: err.response?.data?.detail || err.message || '精轨状态获取失败' }); } }, [en]); const refreshEngineStatus = useCallback(async () => { setEnginesLoading(true); try { const data = await listEngines(); setDinsarEngines(Array.isArray(data.engines) ? data.engines : []); } catch { setDinsarEngines([]); } finally { setEnginesLoading(false); } }, []); const fetchStatus = useCallback(async (options = {}) => { const { force = false } = options; if (statusFetchInFlightRef.current) { return; } statusFetchInFlightRef.current = true; setLoading(true); setError(''); setConsistencyError(''); try { try { const healthData = await getHealth(force ? { full: true, refresh: true } : { full: true }); setStatus(healthData); setLastChecked(new Date()); } catch (err) { setError(err.response?.data?.detail || err.message || '运维自检失败'); setStatus(null); } try { const statsData = await getStatistics(force); setConsistencySummary(buildConsistencySummary(statsData, en)); setConsistencyError(''); } catch (err) { setConsistencySummary(null); setConsistencyError(err.response?.data?.detail || err.message || '一致性统计获取失败'); } // 引擎状态独立加载,不影响主健康检查。 await refreshEngineStatus(); } finally { setLoading(false); statusFetchInFlightRef.current = false; } }, [en, refreshEngineStatus]); useEffect(() => { void fetchStatus(); const timer = setInterval(() => { if ( syncLoading || cleanupLoading || wslChecking || orbitSyncing ) { return; } void fetchStatus(); }, HEALTH_PANEL_POLL_INTERVAL_MS); return () => clearInterval(timer); }, [ cleanupLoading, fetchStatus, orbitSyncing, syncLoading, wslChecking, ]); const renderBadge = (ok, label = '') => ( {ok ? (en ? 'OK' : '正常') : (en ? 'Error' : '异常')}{label ? ` · ${label}` : ''} ); const renderSignal = (ok, label, detail = '') => (
{label} {ok ? '正常' : '异常'} {detail && {detail}}
); const formatIso = (iso) => { if (!iso) return en ? 'Unknown' : '未知'; try { return new Date(iso).toLocaleString(); } catch { return iso; } }; const formatPathText = (value) => value || (en ? 'Not configured' : '未配置'); const databaseStatus = status?.database || {}; const dinsarBridge = asObject(status?.dinsar_bridge); const dinsarResultCatalog = asObject(status?.dinsar_result_catalog || status?.result_catalog); const timeseriesResultCatalog = asObject(status?.timeseries_result_catalog || status?.psinsar_result_catalog); const dinsarCatalogNeedsRebuild = typeof dinsarResultCatalog.needs_rebuild === 'boolean' ? dinsarResultCatalog.needs_rebuild : null; const timeseriesCatalogNeedsRebuild = typeof timeseriesResultCatalog.needs_rebuild === 'boolean' ? timeseriesResultCatalog.needs_rebuild : null; const sourceRoots = asObject(status?.source_roots); const productPackages = asObject(status?.product_packages); const assetInventory = asObject(status?.asset_inventory); const hasAssetInventory = Boolean(status?.asset_inventory); const assetSourceRoots = asObject(assetInventory.source_roots); const assetOrbitRoots = asObject(assetInventory.orbit_roots); const sourceAssets = asObject(assetInventory.source_assets); const orbitAssets = asObject(assetInventory.orbit_assets); const orbitBindings = asObject(assetInventory.bindings); const assetIssues = asObject(assetInventory.issues); const wslRuntime = asObject(status?.wsl_runtime); const wslRuntimeItems = asArray(wslRuntime.runtimes); const pairingSystem = asObject(status?.pairing_system); const sourceRootItems = asArray(sourceRoots.items); const bridgeDiagnosisIssueCount = toNumber(dinsarBridge.diagnosis_missing_product_row_count) + toNumber(dinsarBridge.diagnosis_product_identity_mismatch_count) + toNumber(dinsarBridge.diagnosis_result_product_mismatch_count); const bridgeIssueCount = toNumber(dinsarBridge.missing_compat_count) + toNumber(dinsarBridge.orphan_compat_count) + toNumber(dinsarBridge.duplicate_compat_product_count) + toNumber(dinsarBridge.annotation_drift_count) + bridgeDiagnosisIssueCount; const pairingIssueCount = toNumber(pairingSystem.duplicate_reverse_pair_count) + toNumber(pairingSystem.orphan_edge_count); const dbRequiredTables = asArray(databaseStatus.required_tables); const dbMissingTables = asArray(databaseStatus.missing_tables); const dbExtraTables = asArray(databaseStatus.extra_tables); const dbMissingColumns = asObject(databaseStatus.missing_columns); const dbExtraColumns = asObject(databaseStatus.extra_columns); const dbTypeMismatches = asArray(databaseStatus.type_mismatches); const dbNullableMismatches = asArray(databaseStatus.nullable_mismatches); const dbSchemaReasons = asArray(databaseStatus.schema_reasons); const dbMissingColumnCount = countGroupedColumns(dbMissingColumns); const dbExtraColumnCount = countGroupedColumns(dbExtraColumns); const dbHasStructureDetails = dbRequiredTables.length > 0 || dbSchemaReasons.length > 0; const orbitSource = orbitStatus?.source || {}; const orbitPools = orbitStatus?.pools || {}; const orbitConsistency = orbitStatus?.consistency || {}; const orbitDatabase = orbitStatus?.database || {}; const orbitMismatchCount = toNumber(orbitConsistency.mismatch_count); const orbitDbMissingEnviCount = toNumber(orbitDatabase.stems_missing_in_envi_count); const orbitDbMissingPathCount = toNumber(orbitDatabase.db_missing_path_count); const orbitDbFlagIssueCount = toNumber(orbitDatabase.has_orbit_but_missing_path_count) + toNumber(orbitDatabase.without_orbit_but_path_present_count); const orbitScanErrorCount = (orbitSource.errors?.length || 0) + (orbitPools.envi?.errors?.length || 0); const orbitDuplicateCount = toNumber(orbitSource.duplicate_count) + toNumber(orbitPools.envi?.duplicate_count); const orbitSuspectBadCount = toNumber(orbitSource.suspect_bad_count); const orbitSourceWithoutEnviCount = toNumber(orbitSource.source_without_envi_count); const orbitEnviWithoutSourceCount = toNumber(orbitSource.envi_without_source_count); const orbitBadSourceSamples = asArray(orbitSource.bad_source_samples).filter(hasOrbitCorruptionSignal); const orbitBadSourceSampleCount = toNumber(orbitSource.bad_source_sample_count || orbitBadSourceSamples.length); const orbitOverallHealthy = Boolean( orbitStatus && orbitMismatchCount === 0 && orbitDbMissingEnviCount === 0 && orbitDbMissingPathCount === 0 && orbitDbFlagIssueCount === 0 && orbitScanErrorCount === 0 && orbitSuspectBadCount === 0 && orbitSourceWithoutEnviCount === 0 && orbitEnviWithoutSourceCount === 0 ); const assetInventoryHealthy = Boolean(hasAssetInventory && assetInventory.ok); const orbitAssetRiskCount = toNumber(orbitAssets.parse_failed_count) + toNumber(orbitBindings.missing_count) + toNumber(orbitBindings.ambiguous_count); const productionBlockingCount = [ !status?.ok, !status?.database?.ok, !status?.database?.schema_ok, !status?.worker?.ok, consistencySummary && consistencySummary.critical > 0, hasAssetInventory && !assetInventoryHealthy, ].filter(Boolean).length; return (
运行维护
面向生产环境的系统健康、数据一致性和维护操作入口
{error &&
{en ? 'Check failed: ' : '自检失败:'}{error}
} {!status && !error && (
{en ? 'Fetching status...' : '正在获取状态...'}
)} {status && ( <>
生产就绪 {renderBadge(status.ok && productionBlockingCount === 0, productionBlockingCount > 0 ? `${productionBlockingCount} 个阻断项` : '可运行')}
最近检查 {lastChecked ? lastChecked.toLocaleString() : formatIso(status.timestamp)}
一致性异常 {renderBadge( !consistencySummary || consistencySummary.total === 0, consistencySummary ? `${consistencySummary.total} 项` : '未知' )}
{renderSignal(!!status.database?.ok && !!status.database?.schema_ok, '数据库')} {renderSignal(!!status.worker?.ok, 'Worker', `${status.worker?.worker_count ?? 0} 个`)} {renderSignal(!!status.idl?.ok, 'IDL/ENVI')} {renderSignal(!!status.nginx?.ok, 'Nginx')}

核心服务

判断系统是否具备基础生产能力:数据库、PostGIS、schema 和任务执行器。

{en ? 'Database' : '数据库'}
{en ? 'Connection' : '连接'} {renderBadge(status.database?.ok)}
PostGIS {renderBadge(status.database?.postgis_ok)}
{en ? 'Schema OK' : '结构一致'} {renderBadge(status.database?.schema_ok)}
{dbHasStructureDetails && ( <>
{en ? 'Schema Baseline' : '结构基线'} {en ? 'ORM + migrations' : 'ORM + 迁移'}
{en ? 'Required Tables' : '基线表数'} {toNumber(databaseStatus.required_table_count || dbRequiredTables.length)}
{en ? 'Current Tables' : '当前表数'} {toNumber(databaseStatus.current_table_count)}
{en ? 'Structure Issues' : '结构异常项'} {toNumber(databaseStatus.schema_issue_count || dbSchemaReasons.length)}
{en ? 'Schema details' : '结构详情'}
{en ? 'Required table list: ' : '基线表清单:'}{dbRequiredTables.join(', ')}
{dbMissingTables.length > 0 && (
{en ? 'Missing tables: ' : '缺失表:'}{dbMissingTables.join(', ')}
)} {dbExtraTables.length > 0 && (
{en ? 'Extra tables: ' : '额外表:'}{dbExtraTables.join(', ')}
)} {dbMissingColumnCount > 0 && (
{en ? 'Missing columns: ' : '缺失列:'}{formatGroupedColumns(dbMissingColumns)}
)} {dbExtraColumnCount > 0 && (
{en ? 'Extra columns: ' : '额外列:'}{formatGroupedColumns(dbExtraColumns)}
)} {dbTypeMismatches.length > 0 && (
{en ? 'Type mismatches: ' : '字段类型不一致:'} {formatMismatchList( dbTypeMismatches, (item) => `${item.table}.${item.column}(${item.expected} -> ${item.actual})` )}
)} {dbNullableMismatches.length > 0 && (
{en ? 'Nullable mismatches: ' : '可空性不一致:'} {formatMismatchList( dbNullableMismatches, (item) => `${item.table}.${item.column}(${String(item.expected)} -> ${String(item.actual)})` )}
)} {dbSchemaReasons.length > 0 ? (
{en ? 'Detected issues:' : '检测到的问题:'} {dbSchemaReasons.map((reason, index) => (
{`${index + 1}. ${reason}`}
))}
) : (
{en ? 'Schema structure matches the application baseline.' : '数据库结构与应用基线一致。'}
)}
)} {!dbHasStructureDetails && !isAdmin && (
{en ? 'Log in as admin to view schema structure details.' : '管理员登录后可查看数据库结构详情。'}
)} {status.database?.error && (
{status.database.error}
)}
{en ? 'Task Worker' : '任务执行器'}
{en ? 'Online' : '在线'} {renderBadge(status.worker?.ok)}
{en ? 'Count' : '数量'} {status.worker?.worker_count ?? 0}
{en ? 'Timeout' : '超时阈值'} {status.worker?.timeout_seconds ?? 0}s
{status.worker?.workers?.length > 0 && (
{en ? 'Last heartbeat: ' : '最近心跳:'}{formatIso(status.worker.workers[0].last_seen)}
)} {status.worker?.error && (
{status.worker.error}
)}

结果目录与生产索引

检查 D-InSAR、时序 InSAR、配对基础表和兼容视图是否能支撑结果查询与生产流转。

{en ? 'D-InSAR Result Catalog' : 'D-InSAR 结果目录'}
{en ? 'Catalog status' : '目录状态'} {renderBadge( isCatalogHealthy(dinsarResultCatalog), formatCatalogStatusLabel(dinsarResultCatalog, en) )}
{en ? 'Needs rebuild' : '需要重建'} {renderBadge( dinsarCatalogNeedsRebuild === false, dinsarCatalogNeedsRebuild === null ? (en ? 'Unknown' : '未知') : (dinsarCatalogNeedsRebuild ? (en ? 'Yes' : '是') : (en ? 'No' : '否')) )}
{en ? 'Manifest / DB' : 'Manifest / 数据库'} {toNumber(dinsarResultCatalog.manifest_count)} / {toNumber(dinsarResultCatalog.db_count)}
{en ? 'Issue count' : '问题数量'} {toNumber(dinsarResultCatalog.issue_count)}
{en ? 'Full directory details and maintenance actions remain in the D-InSAR Products workspace.' : '完整目录详情和维护操作已保留在 “D-InSAR 产物工作台”,不再在自检页展开。'}
{en ? 'Timeseries Result Catalog' : '时序 InSAR 结果目录'}
{en ? 'Catalog status' : '目录状态'} {renderBadge( isCatalogHealthy(timeseriesResultCatalog), formatCatalogStatusLabel(timeseriesResultCatalog, en) )}
{en ? 'Needs rebuild' : '需要重建'} {renderBadge( timeseriesCatalogNeedsRebuild === false, timeseriesCatalogNeedsRebuild === null ? (en ? 'Unknown' : '未知') : (timeseriesCatalogNeedsRebuild ? (en ? 'Yes' : '是') : (en ? 'No' : '否')) )}
{en ? 'Manifest / DB' : 'Manifest / 数据库'} {toNumber(timeseriesResultCatalog.manifest_count)} / {toNumber(timeseriesResultCatalog.db_count)}
{en ? 'Issue count' : '问题数量'} {toNumber(timeseriesResultCatalog.issue_count)}
{en ? 'Managed timeseries products are indexed from canonical publish manifests.' : '时序 InSAR 产物已按标准发布包 manifest 进行索引和自检。'}
{en ? 'Pairing System' : '配对系统'}
{en ? 'Overall' : '总体状态'} {renderBadge(pairingSystem.ok, pairingSystem.status || (en ? 'Unknown' : '未知'))}
{en ? 'Cache ready' : '缓存就绪'} {renderBadge( pairingSystem.cache_ready, pairingSystem.needs_rebuild ? (en ? 'Rebuild required' : '需要重建') : '' )}
{en ? 'Scenes / pairs' : '场景 / 候选对'} {toNumber(pairingSystem.scene_count)} / {toNumber(pairingSystem.pair_count)}
{en ? 'Dirty scenes' : '脏场景数'} {toNumber(pairingSystem.dirty_scene_count)}
{en ? 'Runs / edges' : '网络运行 / 边'} {toNumber(pairingSystem.network_run_count)} / {toNumber(pairingSystem.network_edge_count)}
{en ? 'Metric version' : '指标版本'} {pairingSystem.metric_version || (en ? 'Unknown' : '未知')}
{!pairingSystem.state_present ? (
{en ? 'Pairing state row has not been initialized yet.' : '配对状态行尚未初始化。'}
) : pairingIssueCount > 0 ? ( <> {toNumber(pairingSystem.duplicate_reverse_pair_count) > 0 && (
{en ? `Reverse duplicate candidate pairs: ${toNumber(pairingSystem.duplicate_reverse_pair_count)}` : `存在反向重复候选对:${toNumber(pairingSystem.duplicate_reverse_pair_count)}`}
)} {toNumber(pairingSystem.orphan_edge_count) > 0 && (
{en ? `Orphan network edges: ${toNumber(pairingSystem.orphan_edge_count)}` : `存在孤儿网络边:${toNumber(pairingSystem.orphan_edge_count)}`}
)} ) : pairingSystem.needs_rebuild ? (
{en ? 'Pairing cache is marked dirty and needs rebuild or reconcile.' : '配对缓存已标脏,需要重建或增量修复。'}
) : (
{en ? 'Pairing foundation state is healthy.' : '配对基础状态正常。'}
)} {pairingSystem.error && (
{pairingSystem.error}
)} {pairingSystem.needs_rebuild && (
{en ? 'Repair entry has moved to Pair Planning. Use the production planning page to repair or rebuild the pairing foundation.' : '配对缓存修复入口已移到“生产规划 -> 配对规划”。请在生产规划页执行配对基础修复或强制全量重建。'}
)}
{en ? 'D-InSAR Bridge' : 'D-InSAR 桥接一致性'}
{en ? 'Overall' : '总体状态'} {renderBadge( dinsarBridge.ok, `${toNumber(dinsarBridge.matched_count)} / ${toNumber(dinsarBridge.catalog_count)}` )}
{en ? 'Catalog / compat' : '目录 / 兼容表'} {toNumber(dinsarBridge.catalog_count)} / {toNumber(dinsarBridge.compat_count)}
{en ? 'Missing / orphan' : '缺失 / 孤儿记录'} {toNumber(dinsarBridge.missing_compat_count)} / {toNumber(dinsarBridge.orphan_compat_count)}
{en ? 'Duplicate / drift' : '重复 / 标注漂移'} {toNumber(dinsarBridge.duplicate_compat_product_count)} / {toNumber(dinsarBridge.annotation_drift_count)}
{en ? 'Diagnosis issues' : '诊断链路异常'} {bridgeDiagnosisIssueCount}
{bridgeIssueCount === 0 ? (
{en ? 'Catalog reads and compatibility rows are aligned.' : '目录事实源与兼容视图当前一致。'}
) : ( <> {toNumber(dinsarBridge.missing_compat_count) > 0 && (
{en ? `Catalog products missing compat rows: ${toNumber(dinsarBridge.missing_compat_count)}` : `目录产品缺少兼容行:${toNumber(dinsarBridge.missing_compat_count)}`}
)} {toNumber(dinsarBridge.orphan_compat_count) > 0 && (
{en ? `Compat rows pointing to missing products: ${toNumber(dinsarBridge.orphan_compat_count)}` : `兼容行指向不存在产品:${toNumber(dinsarBridge.orphan_compat_count)}`}
)} {toNumber(dinsarBridge.duplicate_compat_product_count) > 0 && (
{en ? `Duplicate compat product bindings: ${toNumber(dinsarBridge.duplicate_compat_product_count)}` : `兼容产品绑定重复:${toNumber(dinsarBridge.duplicate_compat_product_count)}`}
)} {toNumber(dinsarBridge.annotation_drift_count) > 0 && (
{en ? `Annotation drift between catalog and compat rows: ${toNumber(dinsarBridge.annotation_drift_count)}` : `目录与兼容行的标注出现漂移:${toNumber(dinsarBridge.annotation_drift_count)}`}
)} {bridgeDiagnosisIssueCount > 0 && (
{en ? `Diagnosis linkage anomalies: ${bridgeDiagnosisIssueCount}` : `AI 诊断关联异常:${bridgeDiagnosisIssueCount}`}
)} )} {dinsarBridge.error && (
{dinsarBridge.error}
)}

数据资产与运行时

检查受管源路径、标准结果包、WSL runtime、IDL/ENVI、D-InSAR 引擎、Ollama 和 Nginx。

{en ? 'Managed Source Roots' : '受管源路径'}
{en ? 'Overall' : '总体状态'} {renderBadge( sourceRoots.ok, `${toNumber(sourceRoots.accessible_count)} / ${toNumber(sourceRoots.configured_count)}` )}
{en ? 'Configured' : '已配置'} {toNumber(sourceRoots.configured_count)}
{en ? 'Accessible / blocked' : '可访问 / 阻塞'} {toNumber(sourceRoots.accessible_count)} / {toNumber(sourceRoots.inaccessible_count)}
{toNumber(sourceRoots.inaccessible_count) > 0 && (
{en ? 'Unified scan will remain empty until all configured source roots become accessible.' : '只要受管源路径不可访问,统一扫描就会保持空目录状态。'}
)} {sourceRootItems.length > 0 ? ( sourceRootItems.map((item, index) => (
{formatSourceRootRole(item.role, en)}: {item.accessible ? (en ? 'OK' : '正常') : (en ? 'Blocked' : '不可访问')}
{formatPathText(item.path)}
{!item.accessible && item.error && (
{item.error}
)}
)) ) : toNumber(sourceRoots.configured_count) > 0 && !isAdmin ? (
{en ? 'Log in as admin to view path-level diagnostics.' : '管理员登录后可查看路径级诊断信息。'}
) : null}
{en ? 'Product Packages' : '标准结果包'}
{en ? 'Overall' : '总体状态'} {renderBadge( productPackages.ok, `${toNumber(productPackages.valid_schema_count ?? productPackages.canonical_count)} / ${toNumber(productPackages.total_count)}` )}
{en ? 'Manifest / publish dir' : 'Manifest / 发布目录'} {toNumber(productPackages.missing_manifest_count)} / {toNumber(productPackages.missing_publish_dir_count)}
{en ? 'Processor / runtime' : '处理器 / 运行时'} {toNumber(productPackages.missing_processor_count)} / {toNumber(productPackages.missing_runtime_count)}
{en ? 'Native output trace' : '原生输出追踪'} {toNumber(productPackages.missing_native_output_count)}
{en ? 'Catalog registration now checks canonical package metadata instead of guessing engine-native directories.' : '目录登记现在直接校验标准包元数据,不再依赖猜测引擎原生目录结构。'}
{en ? 'WSL Runtime' : 'WSL 运行时'}
{en ? 'Overall' : '总体状态'} {renderBadge( wslRuntime.ok, `${toNumber(wslRuntime.healthy_runtime_count)} / ${toNumber(wslRuntime.required_runtime_count)}` )}
{en ? 'Broker root' : 'Broker 根目录'} {renderBadge(wslRuntime.broker_job_root_exists)}
{en ? 'Shared distro' : '共享发行版'} {wslRuntime.shared_distro || '-'}
{en ? 'Shared conda env' : '共享 conda 环境'} {wslRuntime.shared_conda_env_name || '-'}
{wslRuntimeItems.length > 0 && ( wslRuntimeItems.map((item) => (
{item.display_name || item.runtime_id || item.engine_code}
{(item.required ? (en ? 'Required' : '必需') : (en ? 'Reserved' : '预留'))} {' · '} {item.runner_exists ? (en ? 'Runner OK' : 'Runner 正常') : (en ? 'Runner missing' : 'Runner 缺失')}
)) )}
IDL/ENVI
{en ? 'Installed' : '已安装'} {renderBadge(status.idl?.ok)}
{en ? 'Status' : '状态'} {status.idl?.status?.is_running ? (en ? 'Open' : '已打开') : (en ? 'Closed' : '未打开')}
{/* D-InSAR 引擎专项 */}
{en ? 'D-InSAR Engines' : 'D-InSAR 引擎'} {isAdmin && ( )}
{enginesLoading ? (
{en ? 'Loading...' : '加载中...'}
) : dinsarEngines.length === 0 ? (
{en ? 'No engines registered' : '未注册引擎'}
) : ( dinsarEngines.map(engine => { const colorMap = { ok: '#22c55e', degraded: '#f59e0b', unavailable: '#ef4444', not_implemented: '#94a3b8', error: '#ef4444' }; const labelMap = { ok: en ? 'Available' : '可用', degraded: en ? 'Degraded' : '部分可用', unavailable: en ? 'Unavailable' : '不可用', not_implemented: en ? 'Reserved' : '预留', error: en ? 'Error' : '错误' }; const color = colorMap[engine.status] || '#94a3b8'; return (
{engine.engine_label} {labelMap[engine.status] || engine.status}
); }) )} {wslReport && (
{wslReport.overall_ok ? (en ? '✓ WSL OK' : '✓ WSL 正常') : (en ? '✗ WSL Issues' : '✗ WSL 存在问题')} ({wslReport.distro})
{(wslReport.checks || []).map((c, i) => (
{c.skipped ? '—' : c.ok ? '✓' : '✗'} {c.name} {c.detail}
))}
{wslReport.message}
)}
Ollama
{en ? 'Reachable' : '连通'} {renderBadge(status.ollama?.ok)}
{status.ollama?.models?.length > 0 && (
{en ? 'Models: ' : '模型:'}{status.ollama.models.join(', ')}
)} {status.ollama?.error && (
{status.ollama.error}
)}
Nginx
{en ? 'Reachable' : '连通'} {renderBadge(status.nginx?.ok)}
{status.nginx?.status_code && (
{en ? 'Status code: ' : '状态码:'}{status.nginx.status_code}
)} {status.nginx?.error && (
{status.nginx.error}
)}

数据资产与精轨

当前生产以 XML 抽取后的源产品、精轨资产和场景绑定为准;旧精轨池核对仅作为过渡诊断。

源产品资产
总体状态 {renderBadge( assetInventoryHealthy, assetInventory ? `${toNumber(sourceAssets.total_count)} 项` : '未知' )}
LT-1 / Sentinel-1 {toNumber(sourceAssets.lt1_count)} / {toNumber(sourceAssets.s1_count)}
解析异常 {toNumber(sourceAssets.parse_failed_count)}
源数据根 {toNumber(assetSourceRoots.accessible_count)} / {toNumber(assetSourceRoots.configured_count)}
需复扫 {toNumber(assetSourceRoots.needs_rescan_count)}
{toNumber(sourceAssets.parse_failed_count) > 0 ? (
存在源产品解析异常,请在“数据资产”中查看开放问题并复扫相关目录。
) : (
源产品资产已按 XML/元数据登记。
)}
精轨资产状态
总体状态 {renderBadge( orbitAssetRiskCount === 0 && assetInventoryHealthy, orbitAssetRiskCount > 0 ? `${orbitAssetRiskCount} 个风险项` : `${toNumber(orbitAssets.total_count)} 项` )}
LT-1 / Sentinel-1 {toNumber(orbitAssets.lt1_count)} / {toNumber(orbitAssets.s1_count)}
解析异常 {toNumber(orbitAssets.parse_failed_count)}
精轨根 {toNumber(assetOrbitRoots.accessible_count)} / {toNumber(assetOrbitRoots.configured_count)}
需复扫 {toNumber(assetOrbitRoots.needs_rescan_count)}
精轨可用性以资产登记和时间窗绑定结果为准,不再以 ISCE2 XML 池作为生产判断。
场景精轨绑定
已绑定场景 {toNumber(orbitBindings.matched_count)} / {toNumber(orbitBindings.scene_count)}
缺失精轨 {toNumber(orbitBindings.missing_count)}
候选歧义 {toNumber(orbitBindings.ambiguous_count)}
开放问题 {toNumber(assetIssues.open_count)}
错误 / 警告 {toNumber(assetIssues.error_count)} / {toNumber(assetIssues.warning_count)}
{orbitAssetRiskCount > 0 ? (
请在“数据资产”中复核缺失或歧义精轨;生产配对会优先使用已选定的精轨资产。
) : (
当前开放问题未显示精轨绑定风险。
)}
{en ? 'Consistency Check' : '一致性检测'}
{en ? 'Total Issues' : '异常总数'} {renderBadge( !consistencySummary || consistencySummary.total === 0, consistencySummary ? `${consistencySummary.total} ${en ? 'items' : '项'}` : (en ? 'Unknown' : '未知') )}
{consistencySummary && ( <>
{en ? 'Critical' : '严重异常'} {consistencySummary.critical}
{en ? 'Warning' : '一般异常'} {consistencySummary.warning}
{consistencySummary.issues .filter((item) => item.count > 0) .map((item) => (
{item.label}:{item.count} 条
))} {consistencySummary.issues.find(i => i.key === 'water_unregistered' && i.count > 0) && (
{syncResult &&
{syncResult}
}
)} {consistencySummary.total === 0 && (
{en ? 'No consistency issues found.' : '未发现一致性异常。'}
)} )} {consistencyError && (
{en ? 'Failed to fetch consistency stats: ' : '一致性统计获取失败:'}{consistencyError}
)}
{isAdmin && (
旧精轨池核对
该诊断仅核对源精轨目录与 ENVI/Gamma 生产 TXT 池,用于排查历史目录;生产判断以资产登记与场景绑定为准。
{orbitStatus ? ( <>
{en ? 'Precise Orbit Management' : '文件池状态'}
{en ? 'Overall' : '总体状态'} {renderBadge( orbitOverallHealthy, orbitStatus ? (en ? `${orbitMismatchCount} mismatches` : `${orbitMismatchCount} 项不一致`) : (en ? 'Unknown' : '未知') )}
{en ? 'Source / production TXT' : '源目录 / 生产 TXT'} {`${toNumber(orbitSource.total_source)} / ${toNumber(orbitPools.envi?.total)}`}
{en ? 'DB expected / missing path' : '数据库期望 / 路径缺失'} {toNumber(orbitDatabase.db_expected_stem_count)} / {orbitDbMissingPathCount}
{en ? 'Pool mismatches' : '池不一致'} {orbitMismatchCount}
{en ? 'Source-only' : '仅源存在'} {orbitSourceWithoutEnviCount}
{en ? 'Production TXT only' : '仅生产 TXT 存在'} {orbitEnviWithoutSourceCount}
{en ? 'DB missing in production TXT' : '数据库在生产 TXT 缺失'} {orbitDbMissingEnviCount}
{en ? 'Duplicate stems / scan errors' : '重复 stem / 扫描异常'} {orbitDuplicateCount} / {orbitScanErrorCount}
{en ? 'LT-1 orbit scans synchronize the production TXT pool for ENVI/SARscape and Gamma. S1 EOF files remain registered as source orbit assets.' : 'LT-1 精轨扫描会同步 ENVI/SARscape 与 Gamma 共用的生产 TXT 池;S1 EOF 只登记为源精轨资产。'}
{en ? 'Source path: ' : '源目录路径:'}{formatPathText(orbitSource.path)}
{en ? 'Production TXT pool: ' : '生产 TXT 池:'}{formatPathText(orbitPools.envi?.path)}
{orbitDuplicateCount > 0 && (
{en ? `Duplicate stems detected during scan: ${orbitDuplicateCount}` : `扫描时发现重复 stem:${orbitDuplicateCount}`}
)} {orbitDbFlagIssueCount > 0 && (
{en ? `DB flag/path anomalies: has_orbit=true but path missing ${toNumber(orbitDatabase.has_orbit_but_missing_path_count)}, has_orbit=false but path present ${toNumber(orbitDatabase.without_orbit_but_path_present_count)}` : `数据库标记异常:has_orbit_data=true 但路径缺失 ${toNumber(orbitDatabase.has_orbit_but_missing_path_count)} 条,has_orbit_data=false 但路径存在 ${toNumber(orbitDatabase.without_orbit_but_path_present_count)} 条`}
)} {orbitDbMissingPathCount > 0 && (
{en ? `DB orbit_file_path missing on disk: ${orbitDbMissingPathCount} / ${toNumber(orbitDatabase.distinct_orbit_path_count)}` : `数据库 orbit_file_path 指向不存在文件:${orbitDbMissingPathCount} / ${toNumber(orbitDatabase.distinct_orbit_path_count)}`}
)} {orbitBadSourceSampleCount > 0 && (
{en ? `Sampled source TXT with corruption signals: ${orbitBadSourceSampleCount}` : `抽样检测到损坏信号的源 TXT:${orbitBadSourceSampleCount}`}
)} {orbitDatabase.sample_missing_in_envi?.length > 0 && (
{en ? 'DB expected but ENVI pool missing: ' : '数据库期望但 ENVI 池缺失:'} {orbitDatabase.sample_missing_in_envi.slice(0, 5).join(', ')}
)} {orbitBadSourceSamples.slice(0, 5).map((item) => (
{item.name} - {item.error || (en ? 'Corruption signal detected' : '检测到损坏信号')} {renderOrbitSourceIssueDetails(item, en, formatPathText)}
))} {(orbitConsistency.mismatches || []).slice(0, 5).map((item) => (
{item.name} - {item.issue} {item.envi_path && (
{en ? 'ENVI: ' : 'ENVI:'}{formatPathText(item.envi_path)}
)}
))} {orbitConsistency.mismatch_count > 5 && (
{en ? `...and ${orbitConsistency.mismatch_count - 5} more mismatches` : `...还有 ${orbitConsistency.mismatch_count - 5} 项不一致`}
)} {(orbitDatabase.path_errors || []).slice(0, 3).map((item, index) => (
{item}
))} {(orbitSource.errors || []).slice(0, 3).map((item, index) => (
{en ? 'Source scan error: ' : '源目录扫描异常:'}{item}
))} {(orbitPools.envi?.errors || []).slice(0, 3).map((item, index) => (
{en ? 'ENVI pool scan error: ' : 'ENVI 池扫描异常:'}{item}
))}
{orbitSyncResult && (
{orbitSyncResult.error ? (
{orbitSyncResult.error}
) : ( <>
{orbitSyncResult.healthy ? (en ? 'Pools are consistent.' : '本地池一致。') : (en ? `Detected ${toNumber(orbitSyncResult.mismatch_count)} mismatches.` : `检测到 ${toNumber(orbitSyncResult.mismatch_count)} 项不一致。`)}
{en ? `TXT ${toNumber(orbitSyncResult.envi?.total)}, scan errors ${toNumber(orbitSyncResult.error_count)}` : `TXT ${toNumber(orbitSyncResult.envi?.total)} 项,扫描异常 ${toNumber(orbitSyncResult.error_count)} 项`}
{(orbitSyncResult.mismatches || []).slice(0, 5).map((item, index) => (
{item.name} - {item.issue} {item.envi_path && (
{en ? 'ENVI: ' : 'ENVI:'}{formatPathText(item.envi_path)}
)}
))} {(orbitSyncResult.errors || []).slice(0, 3).map((item, index) => (
{item}
))} )}
)} ) : (
展开后点击“加载诊断”获取旧精轨池状态。
)}
)}
{/* 系统维护 */}

维护与审计

低频维护动作与日志审计集中管理,避免与实时健康状态混杂。

{isAdmin && (

会话记录维护

清理已过期或已撤销的登录会话,不影响当前有效登录。

{cleanupResult && (
{cleanupResult.message}
)}
)} {/* 日志管理 */}
)}
); }; export default HealthCheckPanel;