Add LandSAR cluster worker deployment

This commit is contained in:
2026-06-24 14:10:58 +08:00
parent 71c524967c
commit da09ba05cb
88 changed files with 9300 additions and 3011 deletions
+8 -114
View File
@@ -6,8 +6,6 @@ import {
getDinsarProductDetail,
getDinsarProductCleanupPlan,
listDinsarProductPairs,
queueDinsarCatalogRebuild,
queueDinsarProductPublish,
} from '../api/dinsarProducts';
import {
DINSAR_ENGINE_ALL,
@@ -52,15 +50,6 @@ function formatBytes(value) {
return `${next.toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
}
function parseDirectoryList(value) {
return [...new Set(
String(value || '')
.split(/[\r\n,;]+/)
.map((item) => item.trim())
.filter(Boolean)
)];
}
function getMessageTone(message) {
return /失败|error|Error|ERROR/.test(String(message || '')) ? 'error' : 'success';
}
@@ -91,8 +80,6 @@ function MetaField({ label, value, multiline = false }) {
export default function DinsarCatalogPanel({
readOnly = false,
compact = false,
initialSourceDir = '',
onTaskQueued,
}) {
const [catalogStatus, setCatalogStatus] = useState(null);
const [products, setProducts] = useState([]);
@@ -104,27 +91,14 @@ export default function DinsarCatalogPanel({
const [cleanupPlanLoading, setCleanupPlanLoading] = useState(false);
const [loading, setLoading] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [actionLoading, setActionLoading] = useState(false);
const [actionMessage, setActionMessage] = useState('');
const [sourceDirectoriesText, setSourceDirectoriesText] = useState(initialSourceDir || '');
const [publishRoot, setPublishRoot] = useState('');
const [engineFilter, setEngineFilter] = useState(DINSAR_ENGINE_ALL);
const [queryDraft, setQueryDraft] = useState('');
const [queryApplied, setQueryApplied] = useState('');
const listLimit = compact ? 8 : 24;
const listLimit = compact ? 8 : 18;
const previewBaseUrl = apiClient.defaults.baseURL || '/api';
useEffect(() => {
if (!initialSourceDir) return;
setSourceDirectoriesText((current) => (current.trim() ? current : initialSourceDir));
}, [initialSourceDir]);
const sourceDirectories = useMemo(
() => parseDirectoryList(sourceDirectoriesText),
[sourceDirectoriesText]
);
const engineOptions = useMemo(
() => buildDinsarEngineOptions([], { includeKnown: true }),
[]
@@ -210,6 +184,11 @@ export default function DinsarCatalogPanel({
}
}, []);
const handleSelectProduct = useCallback((pairKey, productId) => {
setSelectedPairKey(pairKey);
setSelectedProductId((current) => (current === productId ? current : productId || null));
}, []);
const loadCleanupPlan = useCallback(async () => {
if (!selectedProductId) return;
setCleanupPlanLoading(true);
@@ -243,45 +222,6 @@ export default function DinsarCatalogPanel({
setQueryApplied('');
}, []);
const handleQueuePublish = async () => {
if (readOnly || sourceDirectories.length === 0) return;
setActionLoading(true);
setActionMessage('');
try {
const result = await queueDinsarProductPublish({
source_directories: sourceDirectories,
publish_root: publishRoot.trim() || null,
rebuild_catalog: true,
});
setActionMessage(`结果包发布任务已入队:${result.task_id}`);
onTaskQueued?.(result.task_id);
await loadCatalog();
} catch (error) {
setActionMessage(`结果包发布失败:${error?.response?.data?.detail || error.message}`);
} finally {
setActionLoading(false);
}
};
const handleQueueRebuild = async () => {
if (readOnly) return;
setActionLoading(true);
setActionMessage('');
try {
const result = await queueDinsarCatalogRebuild({
publish_root: publishRoot.trim() || null,
full_rebuild: true,
});
setActionMessage(`结果目录重建任务已入队:${result.task_id}`);
onTaskQueued?.(result.task_id);
await loadCatalog();
} catch (error) {
setActionMessage(`结果目录重建失败:${error?.response?.data?.detail || error.message}`);
} finally {
setActionLoading(false);
}
};
const catalogTone = STATUS_TONE_MAP[catalogStatus?.status] || 'neutral';
const actionTone = getMessageTone(actionMessage);
const selectedIssues = Array.isArray(selectedProduct?.issues) ? selectedProduct.issues : [];
@@ -311,7 +251,7 @@ export default function DinsarCatalogPanel({
{selectedEngineMeta.shortLabel}
</span>
)}
<button onClick={loadCatalog} disabled={loading || actionLoading}>
<button onClick={loadCatalog} disabled={loading}>
{loading ? '刷新中...' : '刷新'}
</button>
</div>
@@ -352,49 +292,6 @@ export default function DinsarCatalogPanel({
</div>
)}
{!compact && (
<div className="dinsar-catalog-manage">
<div className="dinsar-catalog-manage-copy">
<strong>手动发布与目录重建</strong>
<p>
这里用于把既有结果目录重新发布为标准结果包,并按最新规则重建目录索引。
同一对影像的 ENVI/SARscape、LandSAR、Gamma/PyINT 结果会按任务聚合展示,底层仍依赖 `engine_code` 与 `run_key` 分别登记。
</p>
</div>
<div className="dinsar-catalog-manage-form">
<textarea
value={sourceDirectoriesText}
onChange={(event) => setSourceDirectoriesText(event.target.value)}
placeholder="输入一个或多个结果源目录,支持换行、逗号或分号分隔"
disabled={readOnly || actionLoading}
/>
<input
value={publishRoot}
onChange={(event) => setPublishRoot(event.target.value)}
placeholder="可选:自定义标准结果包根目录,留空使用系统配置"
disabled={readOnly || actionLoading}
/>
<div className="dinsar-catalog-manage-actions">
<button
type="button"
className="primary"
onClick={handleQueuePublish}
disabled={readOnly || actionLoading || sourceDirectories.length === 0}
>
{actionLoading ? '处理中...' : '发布结果包并重建'}
</button>
<button
type="button"
onClick={handleQueueRebuild}
disabled={readOnly || actionLoading}
>
仅重建目录
</button>
</div>
</div>
</div>
)}
<div className={`dinsar-catalog-workspace ${compact ? 'compact' : ''}`}>
<aside className="dinsar-catalog-list-card">
<div className="dinsar-catalog-card-head">
@@ -456,10 +353,7 @@ export default function DinsarCatalogPanel({
key={rowKey}
type="button"
className={`dinsar-catalog-list-item ${selectedPairKey === rowKey ? 'active' : ''}`}
onClick={() => {
setSelectedPairKey(rowKey);
setSelectedProductId(item.primary_product_id || null);
}}
onClick={() => handleSelectProduct(rowKey, item.primary_product_id)}
>
<div className="dinsar-catalog-list-item-top">
<strong>{item.task_alias || item.task_name || item.pair_key || '未命名任务'}</strong>