Apply current workspace changes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import './App.css'; // 复用现有样式
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import './App.css';
|
||||
import { useI18n } from './i18n/I18nContext';
|
||||
|
||||
const DEFAULT_MONITOR_CONFIG = {
|
||||
@@ -7,18 +7,42 @@ const DEFAULT_MONITOR_CONFIG = {
|
||||
orbit_dir: '',
|
||||
dinsar_dirs: [],
|
||||
gf3_source_dirs: [],
|
||||
gf3_storage_dirs: []
|
||||
gf3_storage_dirs: [],
|
||||
};
|
||||
|
||||
const DEFAULT_UNPACK_CONFIG = {
|
||||
source_dirs: [],
|
||||
insar_storage_dirs: [],
|
||||
min_disk_space_gb: 50,
|
||||
max_files_per_run: 0,
|
||||
max_runtime_minutes: 0,
|
||||
delete_archive: true,
|
||||
tmp_suffix: '.unpack_tmp',
|
||||
archive_exts: []
|
||||
archive_exts: [],
|
||||
};
|
||||
|
||||
const toArray = (value) => (Array.isArray(value) ? value : []);
|
||||
|
||||
const createUnpackRunOptions = (config = DEFAULT_UNPACK_CONFIG) => ({
|
||||
max_files_per_run: String(config?.max_files_per_run ?? 0),
|
||||
max_runtime_minutes: String(config?.max_runtime_minutes ?? 0),
|
||||
});
|
||||
|
||||
const parseUnpackRunValue = (rawValue, label) => {
|
||||
const value = String(rawValue ?? '').trim();
|
||||
if (!value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed < 0) {
|
||||
throw new Error(`${label}必须是大于等于 0 的整数`);
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const formatList = (list) => (Array.isArray(list) && list.length ? list.join('; ') : '未配置');
|
||||
|
||||
const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled = true }) => {
|
||||
const { t } = useI18n();
|
||||
const [config, setConfig] = useState(DEFAULT_MONITOR_CONFIG);
|
||||
@@ -30,10 +54,15 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
const [message, setMessage] = useState('');
|
||||
const [unpackLoading, setUnpackLoading] = useState(false);
|
||||
const [unpackMessage, setUnpackMessage] = useState('');
|
||||
const [showUnpackDialog, setShowUnpackDialog] = useState(false);
|
||||
const [unpackRunOptions, setUnpackRunOptions] = useState(() => createUnpackRunOptions());
|
||||
const [unpackDialogError, setUnpackDialogError] = useState('');
|
||||
const [unpackTaskId, setUnpackTaskId] = useState('');
|
||||
const [unpackTaskTerminal, setUnpackTaskTerminal] = useState(false);
|
||||
const [gf3Loading, setGf3Loading] = useState(false);
|
||||
const [gf3Message, setGf3Message] = useState('');
|
||||
const logEndRef = useRef(null);
|
||||
const toArray = (value) => (Array.isArray(value) ? value : []);
|
||||
|
||||
const parseJsonSafe = async (response, fallback) => {
|
||||
try {
|
||||
return await response.json();
|
||||
@@ -41,12 +70,13 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
const displayLogs = toArray(logs);
|
||||
const displayActiveTasks = toArray(activeTasks);
|
||||
const unpackActiveTask = displayActiveTasks.find((task) =>
|
||||
task.task_id === unpackTaskId || task.task_type === 'UNPACK_ARCHIVES'
|
||||
);
|
||||
|
||||
const formatList = (list) => (list && list.length ? list.join('; ') : '未配置');
|
||||
|
||||
// 获取初始状态
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
setConfig(DEFAULT_MONITOR_CONFIG);
|
||||
@@ -63,21 +93,25 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.then(data => {
|
||||
if (canceled) return;
|
||||
.then((data) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
setConfig({
|
||||
...DEFAULT_MONITOR_CONFIG,
|
||||
...data,
|
||||
radar_dirs: toArray(data?.radar_dirs),
|
||||
dinsar_dirs: toArray(data?.dinsar_dirs),
|
||||
gf3_source_dirs: toArray(data?.gf3_source_dirs),
|
||||
gf3_storage_dirs: toArray(data?.gf3_storage_dirs)
|
||||
gf3_storage_dirs: toArray(data?.gf3_storage_dirs),
|
||||
});
|
||||
setConfigLoaded(true);
|
||||
})
|
||||
.catch(err => {
|
||||
if (canceled) return;
|
||||
console.error("Failed to fetch monitor status:", err);
|
||||
.catch((err) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to fetch monitor status:', err);
|
||||
setConfigLoaded(false);
|
||||
});
|
||||
|
||||
@@ -89,6 +123,9 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
setUnpackConfig(DEFAULT_UNPACK_CONFIG);
|
||||
setShowUnpackDialog(false);
|
||||
setUnpackRunOptions(createUnpackRunOptions());
|
||||
setUnpackDialogError('');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,18 +138,22 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.then(data => {
|
||||
if (canceled) return;
|
||||
.then((data) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
setUnpackConfig({
|
||||
...DEFAULT_UNPACK_CONFIG,
|
||||
...data,
|
||||
source_dirs: toArray(data?.source_dirs),
|
||||
insar_storage_dirs: toArray(data?.insar_storage_dirs)
|
||||
insar_storage_dirs: toArray(data?.insar_storage_dirs),
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (canceled) return;
|
||||
console.error("Failed to fetch unpack config:", err);
|
||||
.catch((err) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to fetch unpack config:', err);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -120,11 +161,12 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
};
|
||||
}, [apiEndpoint, enabled]);
|
||||
|
||||
// 轮询日志
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
setLogs([]);
|
||||
setActiveTasks([]);
|
||||
setUnpackTaskId('');
|
||||
setUnpackTaskTerminal(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,59 +175,121 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
try {
|
||||
const [logsRes, tasksRes] = await Promise.all([
|
||||
fetch(`${apiEndpoint}/monitor/logs`, { credentials: 'include' }),
|
||||
fetch(`${apiEndpoint}/tasks/active`, { credentials: 'include' })
|
||||
fetch(`${apiEndpoint}/tasks/active`, { credentials: 'include' }),
|
||||
]);
|
||||
const [logsData, tasksData] = await Promise.all([
|
||||
parseJsonSafe(logsRes, {}),
|
||||
parseJsonSafe(tasksRes, [])
|
||||
parseJsonSafe(tasksRes, []),
|
||||
]);
|
||||
|
||||
if (canceled) return;
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
setLogs(logsRes.ok ? toArray(logsData?.logs) : []);
|
||||
setActiveTasks(tasksRes.ok ? toArray(tasksData) : []);
|
||||
} catch (err) {
|
||||
if (canceled) return;
|
||||
console.error("Failed to fetch logs or tasks:", err);
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to fetch logs or tasks:', err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLogsAndTasks();
|
||||
const intervalId = setInterval(fetchLogsAndTasks, 2000);
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [apiEndpoint, enabled]);
|
||||
|
||||
// 自动滚动到底部
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
setUnpackTaskId('');
|
||||
setUnpackTaskTerminal(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unpackActiveTask) {
|
||||
if (!unpackTaskId) {
|
||||
setUnpackTaskId(unpackActiveTask.task_id);
|
||||
}
|
||||
setUnpackTaskTerminal(false);
|
||||
setUnpackMessage(
|
||||
`运行中 (${unpackActiveTask.progress || 0}%): ${unpackActiveTask.message || '正在解包 LT-1 归档...'}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unpackTaskId || unpackTaskTerminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canceled = false;
|
||||
fetch(`${apiEndpoint}/tasks/${unpackTaskId}`, { credentials: 'include' })
|
||||
.then(async (res) => {
|
||||
const data = await parseJsonSafe(res, null);
|
||||
if (!res.ok) {
|
||||
throw new Error(data?.detail || `HTTP ${res.status}`);
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.then((task) => {
|
||||
if (canceled || !task) {
|
||||
return;
|
||||
}
|
||||
if (task.status === 'COMPLETED') {
|
||||
setUnpackMessage(task.message || 'LT-1 解包已完成。');
|
||||
setUnpackTaskTerminal(true);
|
||||
} else if (task.status === 'FAILED') {
|
||||
setUnpackMessage(`失败:${task.message || 'LT-1 解包任务失败'}`);
|
||||
setUnpackTaskTerminal(true);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to fetch unpack task status:', err);
|
||||
});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [apiEndpoint, enabled, unpackActiveTask, unpackTaskId, unpackTaskTerminal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logEndRef.current) {
|
||||
logEndRef.current.scrollIntoView({ behavior: "smooth" });
|
||||
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [logs]);
|
||||
|
||||
const hasRadarDirs = Array.isArray(config.radar_dirs) && config.radar_dirs.length > 0;
|
||||
const hasRadarDirs = config.radar_dirs.length > 0;
|
||||
const hasOrbitDir = typeof config.orbit_dir === 'string' && config.orbit_dir.trim() !== '';
|
||||
const hasDinsarDirs = Array.isArray(config.dinsar_dirs) && config.dinsar_dirs.length > 0;
|
||||
const hasGf3SourceDirs = Array.isArray(config.gf3_source_dirs) && config.gf3_source_dirs.length > 0;
|
||||
const hasGf3StorageDirs = Array.isArray(config.gf3_storage_dirs) && config.gf3_storage_dirs.length > 0;
|
||||
const hasDinsarDirs = config.dinsar_dirs.length > 0;
|
||||
const hasGf3SourceDirs = config.gf3_source_dirs.length > 0;
|
||||
const hasGf3StorageDirs = config.gf3_storage_dirs.length > 0;
|
||||
|
||||
const canRunRadar = !readOnly && configLoaded && hasRadarDirs;
|
||||
const canRunOrbit = !readOnly && configLoaded && hasOrbitDir;
|
||||
const canRunDinsar = !readOnly && configLoaded && hasDinsarDirs;
|
||||
const canRunGf3Scan = !readOnly && configLoaded && hasGf3StorageDirs;
|
||||
const canRunGf3Process = !readOnly && configLoaded && hasGf3SourceDirs;
|
||||
const canOpenUnpackDialog = !readOnly && unpackConfig.source_dirs.length > 0;
|
||||
|
||||
const handleRunNow = async (target) => {
|
||||
if (readOnly) {
|
||||
setMessage('当前账号为只读模式,无法触发扫描。');
|
||||
setMessage('当前账户为只读模式,无法触发扫描。');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const targetMap = {
|
||||
'radar': 'LT-1 数据',
|
||||
'orbit': '精轨数据',
|
||||
'dinsar': 'D-InSAR 结果',
|
||||
'gf3': 'GF3 数据'
|
||||
radar: 'LT-1 数据',
|
||||
orbit: '精轨数据',
|
||||
dinsar: 'D-InSAR 结果',
|
||||
gf3: 'GF3 数据',
|
||||
};
|
||||
setMessage(`正在触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
|
||||
@@ -193,12 +297,14 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
const url = target ? `${apiEndpoint}/monitor/run-now?target=${target}` : `${apiEndpoint}/monitor/run-now`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await parseJsonSafe(res, {});
|
||||
if (res.ok) {
|
||||
setMessage(data.message);
|
||||
if (onTaskStart) onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
setMessage(data.message || '扫描任务已启动。');
|
||||
if (onTaskStart) {
|
||||
onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
}
|
||||
} else {
|
||||
setMessage(`触发失败: ${data.detail || '未知错误'}`);
|
||||
}
|
||||
@@ -209,27 +315,83 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnpackRun = async () => {
|
||||
const handleOpenUnpackDialog = () => {
|
||||
if (readOnly) {
|
||||
setUnpackMessage('当前账号为只读模式,无法触发解包任务。');
|
||||
setUnpackMessage('当前账户为只读模式,无法触发解包任务。');
|
||||
return;
|
||||
}
|
||||
setUnpackDialogError('');
|
||||
setUnpackRunOptions(createUnpackRunOptions(unpackConfig));
|
||||
setShowUnpackDialog(true);
|
||||
};
|
||||
|
||||
const handleCloseUnpackDialog = () => {
|
||||
if (unpackLoading) {
|
||||
return;
|
||||
}
|
||||
setShowUnpackDialog(false);
|
||||
setUnpackDialogError('');
|
||||
};
|
||||
|
||||
const handleUnpackOptionChange = (field, value) => {
|
||||
setUnpackRunOptions((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleUnpackRun = async () => {
|
||||
if (readOnly) {
|
||||
setUnpackMessage('当前账户为只读模式,无法触发解包任务。');
|
||||
return;
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = {
|
||||
max_files_per_run: parseUnpackRunValue(unpackRunOptions.max_files_per_run, '单次解包数量'),
|
||||
max_runtime_minutes: parseUnpackRunValue(unpackRunOptions.max_runtime_minutes, '最长运行时间'),
|
||||
};
|
||||
} catch (err) {
|
||||
const errorText = err instanceof Error ? err.message : '解包参数校验失败';
|
||||
setUnpackDialogError(errorText);
|
||||
setUnpackMessage(`失败:${errorText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
setUnpackDialogError('');
|
||||
setUnpackLoading(true);
|
||||
setUnpackMessage('LT-1 解包任务启动中...');
|
||||
try {
|
||||
const res = await fetch(`${apiEndpoint}/unpack/run`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await parseJsonSafe(res, {});
|
||||
if (res.ok) {
|
||||
setShowUnpackDialog(false);
|
||||
setUnpackTaskId(data.task_id || '');
|
||||
setUnpackTaskTerminal(false);
|
||||
setUnpackMessage(data.message || 'LT-1 解包任务已启动');
|
||||
if (onTaskStart) onTaskStart(data.task_id, 'LT-1 解包任务已启动。');
|
||||
if (onTaskStart) {
|
||||
onTaskStart(data.task_id, 'LT-1 解包任务已启动。', {
|
||||
nonBlocking: true,
|
||||
taskType: 'UNPACK_ARCHIVES',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setUnpackMessage(`失败:${data.detail || '未知错误'}`);
|
||||
const errorText = data.detail || '未知错误';
|
||||
setUnpackDialogError(errorText);
|
||||
setUnpackMessage(`失败:${errorText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setUnpackMessage(`失败:${err.message}`);
|
||||
const errorText = err.message || '未知错误';
|
||||
setUnpackDialogError(errorText);
|
||||
setUnpackMessage(`失败:${errorText}`);
|
||||
} finally {
|
||||
setUnpackLoading(false);
|
||||
}
|
||||
@@ -237,7 +399,7 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
|
||||
const handleGf3BatchProcess = async () => {
|
||||
if (readOnly) {
|
||||
setGf3Message('当前账号为只读模式,无法触发 GF3 处理。');
|
||||
setGf3Message('当前账户为只读模式,无法触发 GF3 处理。');
|
||||
return;
|
||||
}
|
||||
setGf3Loading(true);
|
||||
@@ -245,12 +407,14 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
try {
|
||||
const res = await fetch(`${apiEndpoint}/monitor/gf3-process`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await parseJsonSafe(res, {});
|
||||
if (res.ok) {
|
||||
setGf3Message(data.message || 'GF3 批量处理任务已启动');
|
||||
if (onTaskStart) onTaskStart(data.task_id, 'GF3 L1A→L2 批量处理已启动。');
|
||||
if (onTaskStart) {
|
||||
onTaskStart(data.task_id, 'GF3 L1A→L2 批量处理已启动。');
|
||||
}
|
||||
} else {
|
||||
setGf3Message(`失败:${data.detail || '未知错误'}`);
|
||||
}
|
||||
@@ -261,7 +425,14 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
}
|
||||
};
|
||||
|
||||
const sectionStyle = { marginBottom: '12px', padding: '10px 12px', borderRadius: '10px', background: 'var(--color-panel-bg)', border: '1px solid var(--color-border)', boxShadow: 'var(--shadow-soft)' };
|
||||
const sectionStyle = {
|
||||
marginBottom: '12px',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '10px',
|
||||
background: 'var(--color-panel-bg)',
|
||||
border: '1px solid var(--color-border)',
|
||||
boxShadow: 'var(--shadow-soft)',
|
||||
};
|
||||
const labelStyle = { minWidth: '100px', color: 'var(--color-text-muted)', flexShrink: 0 };
|
||||
const rowStyle = { display: 'flex', gap: '8px' };
|
||||
const gridStyle = { display: 'grid', rowGap: '6px', fontSize: '0.9em', color: 'var(--color-text-secondary)' };
|
||||
@@ -273,33 +444,52 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: (loading || !canRun) ? 'not-allowed' : 'pointer',
|
||||
fontSize: '0.85em'
|
||||
cursor: loading || !canRun ? 'not-allowed' : 'pointer',
|
||||
fontSize: '0.85em',
|
||||
});
|
||||
|
||||
const actionBtnStyle = (isLoading, isReadOnly) => ({
|
||||
const actionBtnStyle = (isLoading, isDisabled) => ({
|
||||
padding: '6px 10px',
|
||||
backgroundColor: 'var(--color-accent)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: (isLoading || isReadOnly) ? 'not-allowed' : 'pointer',
|
||||
fontSize: '0.85em'
|
||||
cursor: isLoading || isDisabled ? 'not-allowed' : 'pointer',
|
||||
fontSize: '0.85em',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="monitor-panel" style={{ padding: '15px', backgroundColor: 'var(--color-panel-bg)', borderTop: '1px solid var(--color-border)', display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<div
|
||||
className="monitor-panel"
|
||||
style={{
|
||||
padding: '15px',
|
||||
backgroundColor: 'var(--color-panel-bg)',
|
||||
borderTop: '1px solid var(--color-border)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginTop: 0, marginBottom: '8px', fontSize: '1.1em', flexShrink: 0 }}>数据监控面板</h3>
|
||||
|
||||
{/* 可滚动内容区 */}
|
||||
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0, paddingRight: '4px' }}>
|
||||
<div style={{ margin: '0 0 12px', padding: '10px 12px', borderRadius: '8px', background: 'linear-gradient(90deg, var(--color-accent-soft) 0%, #fff 70%)', border: '1px solid #c7ddff', color: 'var(--color-accent-strong)', fontSize: '0.9em' }}>
|
||||
<div
|
||||
style={{
|
||||
margin: '0 0 12px',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '8px',
|
||||
background: 'linear-gradient(90deg, var(--color-accent-soft) 0%, #fff 70%)',
|
||||
border: '1px solid #c7ddff',
|
||||
color: 'var(--color-accent-strong)',
|
||||
fontSize: '0.9em',
|
||||
}}
|
||||
>
|
||||
{configLoaded
|
||||
? '仅手动模式。路径从 .env 读取;如需修改请更新 .env 并重启后端。'
|
||||
: '未加载到监控状态,请检查后端 /api/monitor/status。'}
|
||||
</div>
|
||||
|
||||
{/* 路径摘要 — 按卫星分组 */}
|
||||
<div style={sectionStyle}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>路径摘要</div>
|
||||
<div style={gridStyle}>
|
||||
@@ -311,28 +501,34 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* LT-1 归档解包 */}
|
||||
<div style={sectionStyle}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>LT-1 归档解包</div>
|
||||
<div style={{ ...gridStyle, marginBottom: '8px' }}>
|
||||
<div style={rowStyle}><span style={labelStyle}>来源目录</span><span style={{ wordBreak: 'break-all' }}>{formatList(unpackConfig.source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>LT-1 存储</span><span style={{ wordBreak: 'break-all' }}>{formatList(unpackConfig.insar_storage_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>单次上限</span><span>{unpackConfig.max_files_per_run > 0 ? `${unpackConfig.max_files_per_run} 个压缩包` : '不限'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>最长运行</span><span>{unpackConfig.max_runtime_minutes > 0 ? `${unpackConfig.max_runtime_minutes} 分钟` : '不限'}</span></div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<button
|
||||
onClick={handleUnpackRun}
|
||||
disabled={unpackLoading || readOnly}
|
||||
style={actionBtnStyle(unpackLoading, readOnly)}
|
||||
onClick={handleOpenUnpackDialog}
|
||||
disabled={unpackLoading || !canOpenUnpackDialog}
|
||||
style={actionBtnStyle(unpackLoading, !canOpenUnpackDialog)}
|
||||
>
|
||||
{unpackLoading ? '运行中...' : (readOnly ? '只读模式' : 'LT-1 解包')}
|
||||
</button>
|
||||
<div style={{ fontSize: '0.85em', color: unpackMessage.includes('失败') ? 'var(--color-danger)' : 'var(--color-text-muted)', alignSelf: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '0.85em',
|
||||
color: unpackMessage.includes('失败') ? 'var(--color-danger)' : 'var(--color-text-muted)',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{unpackMessage}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* GF3 L1A→L2 处理 */}
|
||||
<div style={sectionStyle}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>GF3 L1A → L2 处理</div>
|
||||
<div style={{ ...gridStyle, marginBottom: '8px' }}>
|
||||
@@ -347,20 +543,25 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
>
|
||||
{gf3Loading ? '运行中...' : (readOnly ? '只读模式' : 'GF3 L1A→L2')}
|
||||
</button>
|
||||
<div style={{ fontSize: '0.85em', color: gf3Message.includes('失败') ? 'var(--color-danger)' : 'var(--color-text-muted)', alignSelf: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '0.85em',
|
||||
color: gf3Message.includes('失败') ? 'var(--color-danger)' : 'var(--color-text-muted)',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{gf3Message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 活动任务 */}
|
||||
<div style={sectionStyle}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>活动任务</div>
|
||||
{displayActiveTasks.length === 0 ? (
|
||||
<div style={{ fontSize: '0.85em', color: 'var(--color-text-muted)' }}>当前无活动任务。</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', rowGap: '8px' }}>
|
||||
{displayActiveTasks.slice(0, 4).map(task => (
|
||||
{displayActiveTasks.slice(0, 4).map((task) => (
|
||||
<div key={task.task_id} style={{ fontSize: '0.85em', color: 'var(--color-text-secondary)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '4px' }}>
|
||||
<span>{task.task_type}</span>
|
||||
@@ -376,19 +577,20 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 实时日志 */}
|
||||
<div style={{ marginBottom: '4px' }}>
|
||||
<h4 style={{ margin: '0 0 5px 0', fontSize: '1em' }}>实时日志</h4>
|
||||
<div style={{
|
||||
height: '160px',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: '#0f172a',
|
||||
color: '#22c55e',
|
||||
padding: '10px',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.85em',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '160px',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: '#0f172a',
|
||||
color: '#22c55e',
|
||||
padding: '10px',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.85em',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
{displayLogs.length === 0 ? (
|
||||
<div style={{ color: 'var(--color-text-muted)' }}>暂无日志...</div>
|
||||
) : (
|
||||
@@ -401,7 +603,6 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 扫描按钮 — 固定在底部 */}
|
||||
<div style={{ flexShrink: 0, borderTop: '1px solid var(--color-border)', paddingTop: '10px', marginTop: '6px' }}>
|
||||
<div style={{ display: 'flex', gap: '8px', marginBottom: '6px' }}>
|
||||
<button onClick={() => handleRunNow('radar')} disabled={loading || !canRunRadar} style={scanBtnStyle(canRunRadar)}>扫描 LT-1</button>
|
||||
@@ -411,6 +612,86 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
</div>
|
||||
{message && <div style={{ color: message.includes('失败') ? 'red' : 'green', fontSize: '0.9em' }}>{message}</div>}
|
||||
</div>
|
||||
|
||||
{showUnpackDialog && (
|
||||
<div className="modal-overlay visible" onClick={handleCloseUnpackDialog}>
|
||||
<div className="modal-content" onClick={(event) => event.stopPropagation()}>
|
||||
<h3 style={{ marginTop: 0 }}>LT-1 解包任务参数</h3>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
handleUnpackRun();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '14px',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '8px',
|
||||
background: 'var(--color-panel-muted)',
|
||||
border: '1px solid var(--color-border)',
|
||||
fontSize: '0.9em',
|
||||
color: 'var(--color-text-secondary)',
|
||||
lineHeight: 1.7,
|
||||
}}
|
||||
>
|
||||
本次填写的参数只覆盖当前这一次解包任务,不会修改 `.env` 默认值。输入 `0` 表示不限。
|
||||
</div>
|
||||
|
||||
<div style={{ ...gridStyle, marginBottom: '14px' }}>
|
||||
<div style={rowStyle}><span style={labelStyle}>来源目录</span><span style={{ wordBreak: 'break-all' }}>{formatList(unpackConfig.source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>LT-1 存储</span><span style={{ wordBreak: 'break-all' }}>{formatList(unpackConfig.insar_storage_dirs)}</span></div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>单次最多解包数量</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={unpackRunOptions.max_files_per_run}
|
||||
onChange={(event) => handleUnpackOptionChange('max_files_per_run', event.target.value)}
|
||||
disabled={unpackLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>最长运行时间(分钟)</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={unpackRunOptions.max_runtime_minutes}
|
||||
onChange={(event) => handleUnpackOptionChange('max_runtime_minutes', event.target.value)}
|
||||
disabled={unpackLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{unpackDialogError && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '6px',
|
||||
color: 'var(--color-danger)',
|
||||
fontSize: '0.9em',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{unpackDialogError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="modal-actions">
|
||||
<button type="button" onClick={handleCloseUnpackDialog} disabled={unpackLoading}>
|
||||
取消
|
||||
</button>
|
||||
<button type="submit" disabled={unpackLoading}>
|
||||
{unpackLoading ? '启动中...' : '启动解包任务'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user