diff --git a/frontend/src/DinsarProductionPanel.jsx b/frontend/src/DinsarProductionPanel.jsx
index 26ad791..a25b1b0 100644
--- a/frontend/src/DinsarProductionPanel.jsx
+++ b/frontend/src/DinsarProductionPanel.jsx
@@ -310,6 +310,28 @@ function hasRunCounts(run) {
return [completed, failed, skipped, total].some(value => value != null);
}
+function hasMixedRunOutcome(run) {
+ const { completed, failed, skipped } = getRunCounts(run);
+ return Number(failed || 0) > 0 && (Number(completed || 0) > 0 || Number(skipped || 0) > 0);
+}
+
+function getDisplayRunStatus(run) {
+ const rawStatus = String(run?.raw_status || '').toUpperCase();
+ const status = String(run?.status || '').toLowerCase();
+ if (rawStatus === 'PARTIAL_SUCCESS' || status === 'partial_success' || hasMixedRunOutcome(run)) {
+ return 'partial_success';
+ }
+ return run?.status;
+}
+
+function getDisplayTaskStatus(task, relatedRun = null) {
+ if (!task) return '';
+ if (String(task?.status || '').toUpperCase() === 'PARTIAL_SUCCESS' || hasMixedRunOutcome(relatedRun)) {
+ return 'PARTIAL_SUCCESS';
+ }
+ return task.status;
+}
+
function formatRunCounts(run) {
const { completed, failed, skipped, total } = getRunCounts(run);
const parts = [];
@@ -443,10 +465,11 @@ function RunPathBlock({ run }) {
}
function RunStatusBlock({ run }) {
+ const displayStatus = getDisplayRunStatus(run);
return (
-
- {formatStatus(run?.status)}
+
+ {formatStatus(displayStatus)}
{hasRunCounts(run) && (
@@ -832,6 +855,10 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
recentLimit: 1,
});
const latestRunWithTask = runs.find(run => run?.task_id) || null;
+ const relatedMonitoredRun = runs.find(run => {
+ const taskId = String(run?.task_id || '').trim();
+ return taskId && taskId === String(taskMonitor.latestTask?.task_id || '').trim();
+ }) || latestRunWithTask || null;
const monitoredTask = taskMonitor.latestTask || (
latestRunWithTask
? {
@@ -842,7 +869,10 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
: latestRunWithTask.engine === 'landsar'
? (latestRunWithTask.mode === 'cluster' ? 'LANDSAR_CLUSTER_RUN' : 'LANDSAR_RUN')
: 'IDL_RUN_DINSAR',
- status: latestRunWithTask.raw_status || latestRunWithTask.status,
+ status: getDisplayTaskStatus(
+ { status: latestRunWithTask.raw_status || latestRunWithTask.status },
+ latestRunWithTask,
+ ),
progress: ['COMPLETED', 'PARTIAL_SUCCESS'].includes(String(latestRunWithTask.raw_status || '').toUpperCase())
|| ['success', 'partial_success'].includes(String(latestRunWithTask.status || '').toLowerCase())
? 100
@@ -851,13 +881,14 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
}
: null
);
+ const monitoredTaskDisplayStatus = getDisplayTaskStatus(monitoredTask, relatedMonitoredRun);
const logTaskId = monitoredTask?.task_id || '';
const showingRecentTask = !taskMonitor.isBusy && !!monitoredTask;
const activeRunCount = runs.filter(run => ['running', 'pending'].includes(String(run.status || '').toLowerCase())).length;
const issueRunCount = runs.filter(run => {
- const status = String(run.status || '').toLowerCase();
+ const displayStatus = String(getDisplayRunStatus(run) || '').toLowerCase();
const rawStatus = String(run.raw_status || '').toUpperCase();
- return ['failed', 'partial_success'].includes(status)
+ return ['failed', 'partial_success'].includes(displayStatus)
|| ['FAILED', 'PARTIAL_SUCCESS'].includes(rawStatus);
}).length;
const productionReady = !!currentEngineObj?.available && !!rootDir.trim() && !pyintPreviewBlocksSubmit && !readOnly;
@@ -1998,34 +2029,79 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
{monitoredTask && (
+ (() => {
+ const isPartial = String(monitoredTaskDisplayStatus || '').toUpperCase() === 'PARTIAL_SUCCESS';
+ const isFailedStatus = String(monitoredTaskDisplayStatus || '').toUpperCase() === 'FAILED';
+ const panelTone = isPartial
+ ? {
+ background: '#fffbeb',
+ border: '#fcd34d',
+ title: '#92400e',
+ text: '#78350f',
+ track: '#fde68a',
+ fill: '#d97706',
+ }
+ : isFailedStatus
+ ? {
+ background: '#fef2f2',
+ border: '#fecaca',
+ title: '#b91c1c',
+ text: '#7f1d1d',
+ track: '#fee2e2',
+ fill: '#dc2626',
+ }
+ : showingRecentTask
+ ? {
+ background: '#eff6ff',
+ border: '#bfdbfe',
+ title: '#1d4ed8',
+ text: '#1e40af',
+ track: '#dbeafe',
+ fill: '#3b82f6',
+ }
+ : {
+ background: '#fefce8',
+ border: '#fde68a',
+ title: '#92400e',
+ text: '#78350f',
+ track: '#fde68a',
+ fill: '#f59e0b',
+ };
+ const monitoredCountsText = hasRunCounts(relatedMonitoredRun) ? formatRunCounts(relatedMonitoredRun) : '';
+ return (
{showingRecentTask ? '最近一次任务' : '当前任务'}
-
- {monitoredTask.task_id} - {formatTaskType(monitoredTask.task_type)} - {formatStatus(monitoredTask.status)} - {monitoredTask.message}
+
+ {monitoredTask.task_id} - {formatTaskType(monitoredTask.task_type)} - {formatStatus(monitoredTaskDisplayStatus)} - {monitoredTask.message}
+ {monitoredCountsText && (
+
+ 运行情况:{monitoredCountsText}
+
+ )}
{monitoredTask.progress != null && (
-
{monitoredTask.progress}%
+
{monitoredTask.progress}%
)}
@@ -2046,7 +2122,7 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
style={{
marginTop: 8,
background: '#fff',
- border: `1px solid ${showingRecentTask ? '#bfdbfe' : '#fde68a'}`,
+ border: `1px solid ${panelTone.border}`,
borderRadius: 6,
padding: '8px 10px',
}}
@@ -2127,6 +2203,8 @@ export default function DinsarProductionPanel({ readOnly = false, onJobQueued })
)}
+ );
+ })()
)}
运行记录(已加载 {runs.length} 条)