docs: update production workflow design and runtime changes
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http://127.0.0.1:8900 http://127.0.0.1:8910 https://*.tile.openstreetmap.org https://*.tile.opentopomap.org; connect-src 'self' http://127.0.0.1:8900 http://127.0.0.1:8910; font-src 'self' data:; worker-src blob:;" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.tile.openstreetmap.org https://*.tile.opentopomap.org; connect-src 'self'; font-src 'self' data:; worker-src blob:;" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>frontend</title>
|
||||
|
||||
@@ -9,8 +9,10 @@ const DEFAULT_MONITOR_CONFIG = {
|
||||
dinsar_dirs: [],
|
||||
gf3_archive_source_dirs: [],
|
||||
gf3_source_dirs: [],
|
||||
gf3_legacy_gdal_enabled: false,
|
||||
gf3_sarscape_native_dirs: [],
|
||||
gf3_storage_dirs: [],
|
||||
gf3_sarscape_runtime_dir: '',
|
||||
gf3_sarscape_wrapper_exe: '',
|
||||
gf3_sarscape_idlrt_path: '',
|
||||
gf3_sarscape_dem_path: '',
|
||||
@@ -302,6 +304,7 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
const hasGf3SourceDirs = config.gf3_source_dirs.length > 0;
|
||||
const hasGf3SarscapeNativeDirs = config.gf3_sarscape_native_dirs.length > 0;
|
||||
const hasGf3StorageDirs = config.gf3_storage_dirs.length > 0;
|
||||
const gf3LegacyGdalEnabled = Boolean(config.gf3_legacy_gdal_enabled);
|
||||
const hasGf3SarscapeWrapper = typeof config.gf3_sarscape_wrapper_exe === 'string' && config.gf3_sarscape_wrapper_exe.trim() !== '';
|
||||
const hasGf3SarscapeDem = typeof config.gf3_sarscape_dem_path === 'string' && config.gf3_sarscape_dem_path.trim() !== '';
|
||||
|
||||
@@ -311,8 +314,8 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
const canRunS1Scan = !readOnly && configLoaded && hasS1SourceDirs;
|
||||
const canRunS1OrbitScan = !readOnly && configLoaded && hasS1OrbitDirs;
|
||||
const canRunGf3Scan = !readOnly && configLoaded && hasGf3StorageDirs;
|
||||
const canRunGf3Unpack = !readOnly && configLoaded && hasGf3ArchiveSourceDirs && hasGf3SourceDirs;
|
||||
const canRunGf3Process = !readOnly && configLoaded && hasGf3SourceDirs;
|
||||
const canRunGf3Unpack = !readOnly && configLoaded && gf3LegacyGdalEnabled && hasGf3ArchiveSourceDirs && hasGf3SourceDirs;
|
||||
const canRunGf3Process = !readOnly && configLoaded && gf3LegacyGdalEnabled && hasGf3SourceDirs;
|
||||
const canRunGf3SarscapeProduce = !readOnly && configLoaded && hasGf3ArchiveSourceDirs && hasGf3SarscapeNativeDirs && hasGf3StorageDirs && hasGf3SarscapeWrapper && hasGf3SarscapeDem;
|
||||
const canRunGf3SarscapeSync = !readOnly && configLoaded && hasGf3SarscapeNativeDirs && hasGf3StorageDirs;
|
||||
const canRunGf3SarscapeClean = !readOnly && configLoaded && hasGf3SarscapeNativeDirs && hasGf3StorageDirs;
|
||||
@@ -797,9 +800,10 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
<div style={rowStyle}><span style={labelStyle}>S1 存储</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.s1_storage_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>S1 精轨</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.s1_orbit_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 压缩包</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_archive_source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 来源</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 legacy L1A</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 原生</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_sarscape_native_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 存储</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_storage_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>GF3 runtime</span><span style={{ wordBreak: 'break-all' }}>{config.gf3_sarscape_runtime_dir || '未配置'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>D-InSAR 结果</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.dinsar_dirs)}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -888,31 +892,36 @@ const DataMonitorPanel = ({ apiEndpoint, onTaskStart, readOnly = false, enabled
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>GF3 归档预处理</div>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--color-text-primary)' }}>GF3 SARscape 生产</div>
|
||||
<div style={{ ...gridStyle, marginBottom: '8px' }}>
|
||||
<div style={rowStyle}><span style={labelStyle}>压缩包来源</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_archive_source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>L1A 来源</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_source_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>SARscape 原生</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_sarscape_native_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>L2 存储</span><span style={{ wordBreak: 'break-all' }}>{formatList(config.gf3_storage_dirs)}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>Runtime</span><span style={{ wordBreak: 'break-all' }}>{config.gf3_sarscape_runtime_dir || '未配置'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>Wrapper</span><span style={{ wordBreak: 'break-all' }}>{config.gf3_sarscape_wrapper_exe || '未配置'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>SARscape DEM</span><span style={{ wordBreak: 'break-all' }}>{config.gf3_sarscape_dem_path || '未配置'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>极化</span><span>{config.gf3_sarscape_polarizations || 'HH,HV'}</span></div>
|
||||
<div style={rowStyle}><span style={labelStyle}>Legacy GDAL</span><span>{gf3LegacyGdalEnabled ? '启用' : '关闭'}</span></div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
||||
<button
|
||||
onClick={handleGf3Unpack}
|
||||
disabled={gf3UnpackLoading || readOnly || !canRunGf3Unpack}
|
||||
style={actionBtnStyle(gf3UnpackLoading, readOnly || !canRunGf3Unpack)}
|
||||
>
|
||||
{gf3UnpackLoading ? '运行中...' : (readOnly ? '只读模式' : 'GF3 解包')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleGf3BatchProcess}
|
||||
disabled={gf3ProcessLoading || readOnly || !canRunGf3Process}
|
||||
style={actionBtnStyle(gf3ProcessLoading, readOnly || !canRunGf3Process)}
|
||||
>
|
||||
{gf3ProcessLoading ? '运行中...' : (readOnly ? '只读模式' : 'GF3 预处理')}
|
||||
</button>
|
||||
{gf3LegacyGdalEnabled && (
|
||||
<>
|
||||
<button
|
||||
onClick={handleGf3Unpack}
|
||||
disabled={gf3UnpackLoading || readOnly || !canRunGf3Unpack}
|
||||
style={actionBtnStyle(gf3UnpackLoading, readOnly || !canRunGf3Unpack)}
|
||||
>
|
||||
{gf3UnpackLoading ? '运行中...' : (readOnly ? '只读模式' : 'GF3 legacy 解包')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleGf3BatchProcess}
|
||||
disabled={gf3ProcessLoading || readOnly || !canRunGf3Process}
|
||||
style={actionBtnStyle(gf3ProcessLoading, readOnly || !canRunGf3Process)}
|
||||
>
|
||||
{gf3ProcessLoading ? '运行中...' : (readOnly ? '只读模式' : 'GF3 legacy 预处理')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={handleGf3SarscapeProduce}
|
||||
disabled={gf3SarscapeProduceLoading || readOnly || !canRunGf3SarscapeProduce}
|
||||
|
||||
@@ -38,8 +38,11 @@ export const TILE_LAYER_OPTIONS = {
|
||||
maxZoom: 16,
|
||||
maxNativeZoom: 16,
|
||||
tms: false,
|
||||
updateWhenIdle: true,
|
||||
keepBuffer: 4,
|
||||
updateWhenIdle: false,
|
||||
updateWhenZooming: false,
|
||||
keepBuffer: 2,
|
||||
reuseTiles: true,
|
||||
crossOrigin: 'anonymous',
|
||||
};
|
||||
|
||||
export const TILE_LAYER_BOUNDS = [
|
||||
|
||||
@@ -10,8 +10,8 @@ const TASK_UI_POLICIES = {
|
||||
SCAN_HAZARD: { label: '灾害点同步', featureScope: 'hazard' },
|
||||
UNPACK_ARCHIVES: { label: 'LT-1 解包', featureScope: 'data_monitor' },
|
||||
UNPACK_SENTINEL1: { label: 'Sentinel-1 解包', featureScope: 'data_monitor' },
|
||||
GF3_UNPACK: { label: 'GF3 解包', featureScope: 'data_monitor' },
|
||||
GF3_BATCH_PROCESS: { label: 'GF3 预处理', featureScope: 'data_monitor' },
|
||||
GF3_UNPACK: { label: 'GF3 legacy 解包', featureScope: 'data_monitor' },
|
||||
GF3_BATCH_PROCESS: { label: 'GF3 legacy 预处理', featureScope: 'data_monitor' },
|
||||
GF3_SARSCAPE_PRODUCE: { label: 'GF3 SARscape 生产', featureScope: 'data_monitor' },
|
||||
GF3_SARSCAPE_SYNC: { label: 'GF3 SARscape 入库', featureScope: 'data_monitor' },
|
||||
GF3_SARSCAPE_CLEAN: { label: 'GF3 中间清理', featureScope: 'data_monitor' },
|
||||
@@ -73,4 +73,3 @@ export function isTaskGloballyBlocking(taskType) {
|
||||
export function getTaskTypeLabel(taskType) {
|
||||
return getTaskUiPolicy(taskType).label;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,39 @@ import apiClient from '../api/client';
|
||||
import { getHealth } from '../api/health';
|
||||
|
||||
const HEALTH_POLL_INTERVAL_MS = 30000;
|
||||
const STARTUP_RETRY_DELAYS_MS = [750, 1500, 3000];
|
||||
|
||||
const sleep = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
|
||||
const getErrorStatus = (error) => Number(error?.response?.status || 0);
|
||||
|
||||
const isTransientApiError = (error) => {
|
||||
const status = getErrorStatus(error);
|
||||
return !status || status === 408 || status === 429 || status >= 500;
|
||||
};
|
||||
|
||||
const requestWithTransientRetry = async (
|
||||
requestFn,
|
||||
{
|
||||
delays = STARTUP_RETRY_DELAYS_MS,
|
||||
shouldRetry = isTransientApiError,
|
||||
} = {},
|
||||
) => {
|
||||
let lastError = null;
|
||||
const retryDelays = Array.isArray(delays) ? delays : [];
|
||||
for (let attempt = 0; attempt <= retryDelays.length; attempt += 1) {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (attempt >= retryDelays.length || !shouldRetry(error)) {
|
||||
throw error;
|
||||
}
|
||||
await sleep(retryDelays[attempt]);
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
};
|
||||
|
||||
export default function useAppAuthLifecycle({
|
||||
ensureCanOperate,
|
||||
@@ -33,15 +66,21 @@ export default function useAppAuthLifecycle({
|
||||
const { clearOnFailure = true } = options;
|
||||
const requestGeneration = authGenerationRef.current;
|
||||
try {
|
||||
const response = await apiClient.get('/auth/me', {
|
||||
skipAuthReset: !clearOnFailure,
|
||||
authGeneration: requestGeneration,
|
||||
});
|
||||
const response = await requestWithTransientRetry(() => (
|
||||
apiClient.get('/auth/me', {
|
||||
skipAuthReset: !clearOnFailure,
|
||||
authGeneration: requestGeneration,
|
||||
})
|
||||
));
|
||||
if (requestGeneration === authGenerationRef.current) {
|
||||
setCurrentUser(response.data || null);
|
||||
}
|
||||
} catch {
|
||||
if (clearOnFailure && requestGeneration === authGenerationRef.current) {
|
||||
} catch (error) {
|
||||
if (
|
||||
clearOnFailure
|
||||
&& getErrorStatus(error) === 401
|
||||
&& requestGeneration === authGenerationRef.current
|
||||
) {
|
||||
setCurrentUser(null);
|
||||
}
|
||||
} finally {
|
||||
@@ -88,7 +127,7 @@ export default function useAppAuthLifecycle({
|
||||
const fetchLicenseStatus = useCallback(async () => {
|
||||
try {
|
||||
setLicenseLoading(true);
|
||||
const response = await apiClient.get('/license/status');
|
||||
const response = await requestWithTransientRetry(() => apiClient.get('/license/status'));
|
||||
const data = response.data || {};
|
||||
if (!data.ok && !data.reason) {
|
||||
data.reason = '未授权';
|
||||
@@ -111,7 +150,10 @@ export default function useAppAuthLifecycle({
|
||||
setHealthLoading(true);
|
||||
}
|
||||
setHealthError('');
|
||||
const data = await getHealth(refresh ? { refresh: true } : {});
|
||||
const data = await requestWithTransientRetry(
|
||||
() => getHealth(refresh ? { refresh: true } : {}),
|
||||
{ delays: silent ? [] : STARTUP_RETRY_DELAYS_MS.slice(0, 2) },
|
||||
);
|
||||
setHealthStatus(data || null);
|
||||
} catch (error) {
|
||||
setHealthError(error.response?.data?.detail || '运维自检失败');
|
||||
|
||||
Reference in New Issue
Block a user