feat(web): add phased security rollout ladder

This commit is contained in:
Your Name
2026-05-24 14:49:27 +08:00
parent 22faebcabc
commit a9119561bd
8 changed files with 231 additions and 1 deletions

View File

@@ -44,6 +44,26 @@ const routeRoleItems = [
{ key: 'awooopApprovals', href: '/awooop/approvals', route: 'AwoooP', icon: Lock, color: '#6f6d66' },
] as const
const rolloutPhaseItems = [
{ key: 'observe', phase: '01', state: '現在', icon: Radar, color: '#d97757' },
{ key: 'evidence', phase: '02', state: '補證', icon: ClipboardCheck, color: '#1f7a4d' },
{ key: 'humanReview', phase: '03', state: '人控', icon: CheckCircle2, color: '#1f7a4d' },
{ key: 'runtimeGate', phase: '04', state: '批准後', icon: ShieldCheck, color: '#6f6d66' },
{ key: 'tightening', phase: '05', state: '收斂', icon: Lock, color: '#6f6d66' },
] as const
const rolloutBoundaries = [
'security_compliance_rollout_phase_count=5',
'security_compliance_rollout_current_phase=observe_first',
'security_compliance_rollout_runtime_phase_enabled=false',
'security_compliance_rollout_enforcement_enabled=false',
'security_compliance_rollout_action_buttons_allowed=false',
'runtime_execution_authorized=false',
'active_runtime_gate_count=0',
'action_buttons_allowed=false',
'not_authorization=true',
] as const
function SecurityComplianceFrontStage({ locale }: { locale: string }) {
const t = useTranslations('securityCompliance.frontStage')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
@@ -167,6 +187,76 @@ function SecurityComplianceFrontStage({ locale }: { locale: string }) {
})}
</div>
</div>
<div
data-testid="security-compliance-low-friction-rollout-ladder"
style={{
background: '#fff',
border: '0.5px solid #eee9dd',
borderRadius: 8,
marginTop: 14,
padding: 12,
...textWrap,
}}
>
<div style={{ marginBottom: 10 }}>
<h2 style={{ fontSize: 14, margin: 0, color: '#141413' }}>{t('rolloutTitle')}</h2>
<p style={{ fontSize: 11, color: '#6f6d66', lineHeight: 1.55, margin: '5px 0 0' }}>
{t('rolloutSubtitle')}
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(152px, 1fr))', gap: 8 }}>
{rolloutPhaseItems.map(item => {
const Icon = item.icon
return (
<div
key={item.key}
style={{
display: 'grid',
alignContent: 'start',
gap: 6,
minHeight: 128,
border: '0.5px solid #e7e1d3',
borderRadius: 8,
padding: 10,
background: '#fffdf8',
...textWrap,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontSize: 10, color: '#87867f' }}>{t('phaseLabel')} {item.phase}</span>
<Icon size={15} color={item.color} />
</div>
<div style={{ fontSize: 18, lineHeight: 1, fontWeight: 700, color: item.color }}>{item.state}</div>
<h3 style={{ fontSize: 13, lineHeight: 1.25, margin: 0, color: '#141413' }}>
{t(`rolloutPhases.${item.key}.title` as never)}
</h3>
<p style={{ fontSize: 11, color: '#6f6d66', lineHeight: 1.45, margin: 0 }}>
{t(`rolloutPhases.${item.key}.body` as never)}
</p>
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: 6, marginTop: 10 }}>
{rolloutBoundaries.map(boundary => (
<code
key={boundary}
style={{
background: '#f7f5ee',
border: '0.5px solid #e7e1d3',
borderRadius: 8,
padding: '7px 9px',
color: '#4f4c45',
fontSize: 11,
lineHeight: 1.45,
...textWrap,
}}
>
{boundary}
</code>
))}
</div>
</div>
</div>
<div