🐛 fix(security): 完善密文升级导入覆盖与安全更新链路

- 完善连接恢复包与 legacy 导入覆盖语义及密文兼容处理

- 修复安全更新详情高亮反馈与相关前后端链路

- 补强 keyring 误判边界与安全更新回归测试
This commit is contained in:
tianqijiuyun-latiao
2026-04-11 16:53:03 +08:00
parent 070ff72ad8
commit 82e06bd94d
35 changed files with 2021 additions and 110 deletions

View File

@@ -1,3 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { Button, Empty, Modal, Tag } from 'antd';
import { SafetyCertificateOutlined } from '@ant-design/icons';
@@ -9,13 +10,30 @@ import {
getSecurityUpdateStatusMeta,
sortSecurityUpdateIssues,
} from '../utils/securityUpdatePresentation';
import {
hasSecurityUpdateRecentResult,
resolveSecurityUpdateFocusState,
type SecurityUpdateFocusState,
type SecurityUpdateSettingsFocusTarget,
} from '../utils/securityUpdateRepairFlow';
import type { OverlayWorkbenchTheme } from '../utils/overlayWorkbenchTheme';
import {
SECURITY_UPDATE_ACTION_BUTTON_CLASS,
SECURITY_UPDATE_MODAL_CLASS,
SECURITY_UPDATE_RESULT_CARD_ACTIVE_CLASS,
SECURITY_UPDATE_RESULT_CARD_CLASS,
getSecurityUpdateActionButtonStyle,
getSecurityUpdateSectionSurfaceStyle,
getSecurityUpdateShellSurfaceStyle,
} from '../utils/securityUpdateVisuals';
interface SecurityUpdateSettingsModalProps {
open: boolean;
darkMode: boolean;
overlayTheme: OverlayWorkbenchTheme;
status: SecurityUpdateStatus;
focusTarget?: SecurityUpdateSettingsFocusTarget | null;
focusRequest?: number;
onClose: () => void;
onStart: () => void;
onRetry: () => void;
@@ -23,18 +41,27 @@ interface SecurityUpdateSettingsModalProps {
onIssueAction: (issue: SecurityUpdateIssue) => void;
}
const sectionStyle = (overlayTheme: OverlayWorkbenchTheme) => ({
const sectionStyle = (
overlayTheme: OverlayWorkbenchTheme,
options?: { emphasized?: boolean },
) => ({
borderRadius: 14,
border: overlayTheme.sectionBorder,
background: overlayTheme.sectionBg,
padding: 16,
...getSecurityUpdateSectionSurfaceStyle(overlayTheme, options),
});
const EMPTY_FOCUS_STATE: SecurityUpdateFocusState = {
target: null,
pulseKey: null,
};
const SecurityUpdateSettingsModal = ({
open,
darkMode,
overlayTheme,
status,
focusTarget = null,
focusRequest = 0,
onClose,
onStart,
onRetry,
@@ -43,12 +70,53 @@ const SecurityUpdateSettingsModal = ({
}: SecurityUpdateSettingsModalProps) => {
const statusMeta = getSecurityUpdateStatusMeta(status);
const sortedIssues = sortSecurityUpdateIssues(status.issues);
const showRecentResult = hasSecurityUpdateRecentResult(status);
const showStart = status.overallStatus === 'pending' || status.overallStatus === 'postponed';
const showRetry = status.overallStatus === 'needs_attention';
const showRestart = status.overallStatus === 'needs_attention' || status.overallStatus === 'rolled_back';
const actionButtonStyle = getSecurityUpdateActionButtonStyle();
const [activeFocus, setActiveFocus] = useState<SecurityUpdateFocusState>(EMPTY_FOCUS_STATE);
const statusSectionRef = useRef<HTMLDivElement | null>(null);
const recentResultRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const nextFocus = resolveSecurityUpdateFocusState(open, focusTarget, focusRequest);
if (!nextFocus.target || !nextFocus.pulseKey) {
setActiveFocus(EMPTY_FOCUS_STATE);
return undefined;
}
const targetNode = nextFocus.target === 'recent_result'
? recentResultRef.current
: statusSectionRef.current;
if (!targetNode) {
return undefined;
}
setActiveFocus(EMPTY_FOCUS_STATE);
const animationFrame = window.requestAnimationFrame(() => {
targetNode.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
});
targetNode.focus({ preventScroll: true });
setActiveFocus(nextFocus);
});
const highlightTimer = window.setTimeout(() => {
setActiveFocus((current) => (
current.pulseKey === nextFocus.pulseKey ? EMPTY_FOCUS_STATE : current
));
}, 1800);
return () => {
window.cancelAnimationFrame(animationFrame);
window.clearTimeout(highlightTimer);
};
}, [focusRequest, focusTarget, open]);
return (
<Modal
rootClassName={SECURITY_UPDATE_MODAL_CLASS}
title={(
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
<div
@@ -80,39 +148,44 @@ const SecurityUpdateSettingsModal = ({
onCancel={onClose}
footer={[
showRetry ? (
<Button key="retry" onClick={onRetry}>
<Button key="retry" className={SECURITY_UPDATE_ACTION_BUTTON_CLASS} style={actionButtonStyle} onClick={onRetry}>
</Button>
) : null,
showRestart ? (
<Button key="restart" onClick={onRestart}>
<Button key="restart" className={SECURITY_UPDATE_ACTION_BUTTON_CLASS} style={actionButtonStyle} onClick={onRestart}>
</Button>
) : null,
showStart ? (
<Button key="start" type="primary" onClick={onStart}>
<Button
key="start"
className={SECURITY_UPDATE_ACTION_BUTTON_CLASS}
style={actionButtonStyle}
type="primary"
onClick={onStart}
>
</Button>
) : null,
<Button key="close" onClick={onClose}>
<Button key="close" className={SECURITY_UPDATE_ACTION_BUTTON_CLASS} style={actionButtonStyle} onClick={onClose}>
</Button>,
]}
width={760}
styles={{
content: {
background: overlayTheme.shellBg,
border: overlayTheme.shellBorder,
boxShadow: overlayTheme.shellShadow,
backdropFilter: overlayTheme.shellBackdropFilter,
},
content: getSecurityUpdateShellSurfaceStyle(overlayTheme),
header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 },
body: { paddingTop: 8, maxHeight: 640, overflowY: 'auto' },
footer: { background: 'transparent', borderTop: 'none', paddingTop: 10 },
}}
>
<div style={{ display: 'grid', gap: 14, padding: '12px 0' }}>
<div style={sectionStyle(overlayTheme)}>
<div
ref={statusSectionRef}
tabIndex={-1}
style={sectionStyle(overlayTheme, { emphasized: activeFocus.target === 'status' })}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div>
<div style={{ fontSize: 15, fontWeight: 700, color: overlayTheme.titleText }}>
@@ -153,8 +226,9 @@ const SecurityUpdateSettingsModal = ({
<div
key={item.label}
style={{
border: overlayTheme.sectionBorder,
borderRadius: 12,
background: darkMode ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.75)',
background: overlayTheme.sectionBg,
padding: '12px 10px',
}}
>
@@ -184,9 +258,8 @@ const SecurityUpdateSettingsModal = ({
<div
key={issue.id}
style={{
...getSecurityUpdateSectionSurfaceStyle(overlayTheme),
borderRadius: 12,
border: darkMode ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(16,24,40,0.08)',
background: darkMode ? 'rgba(255,255,255,0.02)' : 'rgba(255,255,255,0.78)',
padding: 14,
display: 'flex',
alignItems: 'flex-start',
@@ -211,6 +284,8 @@ const SecurityUpdateSettingsModal = ({
</div>
</div>
<Button
className={SECURITY_UPDATE_ACTION_BUTTON_CLASS}
style={actionButtonStyle}
type={actionMeta.emphasis === 'primary' ? 'primary' : 'default'}
onClick={() => onIssueAction(issue)}
>
@@ -223,14 +298,24 @@ const SecurityUpdateSettingsModal = ({
)}
</div>
{status.backupPath ? (
<div style={sectionStyle(overlayTheme)}>
{showRecentResult ? (
<div
ref={recentResultRef}
tabIndex={-1}
className={[
SECURITY_UPDATE_RESULT_CARD_CLASS,
activeFocus.target === 'recent_result' ? SECURITY_UPDATE_RESULT_CARD_ACTIVE_CLASS : '',
].filter(Boolean).join(' ')}
style={sectionStyle(overlayTheme, { emphasized: activeFocus.target === 'recent_result' })}
>
<div style={{ fontSize: 14, fontWeight: 700, color: overlayTheme.titleText, marginBottom: 8 }}>
</div>
<div style={{ fontSize: 13, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
<span style={{ color: overlayTheme.titleText }}>{status.backupPath}</span>
</div>
{status.backupPath ? (
<div style={{ fontSize: 13, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
<span style={{ color: overlayTheme.titleText }}>{status.backupPath}</span>
</div>
) : null}
{status.lastError ? (
<div style={{ marginTop: 8, fontSize: 13, color: '#ff7875', lineHeight: 1.7 }}>
{status.lastError}