feat(web): 新增 iwooos rollout 驗收結果分流

This commit is contained in:
Your Name
2026-05-25 08:47:12 +08:00
parent 53581c8cae
commit f2ffbf2ffa
8 changed files with 471 additions and 3 deletions

View File

@@ -1268,6 +1268,55 @@ const iwooosProductRolloutAcceptanceGateBoundaries = [
'gitea_disablement_authorized=false',
] as const
const iwooosProductRolloutAcceptanceOutcomeSummary = [
{ key: 'outcomes', value: '7', icon: ListChecks, tone: 'steady' },
{ key: 'accepted', value: '0', icon: CheckCircle2, tone: 'locked' },
{ key: 'quarantine', value: '0', icon: FileWarning, tone: 'warn' },
{ key: 'runtime', value: '0', icon: Lock, tone: 'locked' },
] as const
const iwooosProductRolloutAcceptanceOutcomes = [
{ key: 'keepReadOnly', lane: 'R1', state: '維持只讀', icon: ShieldCheck, tone: 'steady' },
{ key: 'returnEvidence', lane: 'R2', state: '退回補證', icon: FileText, tone: 'warn' },
{ key: 'quarantineSensitive', lane: 'R3', state: '隔離敏感', icon: FileWarning, tone: 'warn' },
{ key: 'sourceControlHold', lane: 'R4', state: '版本待證', icon: GitBranch, tone: 'warn' },
{ key: 'hostSafetyHold', lane: 'R5', state: '主機暫停', icon: Activity, tone: 'locked' },
{ key: 'humanReviewCandidate', lane: 'R6', state: '待人工審', icon: ClipboardCheck, tone: 'warn' },
{ key: 'runtimeDenied', lane: 'R7', state: 'runtime 未開', icon: Lock, tone: 'locked' },
] as const
const iwooosProductRolloutAcceptanceOutcomeBoundaries = [
'iwooos_product_rollout_acceptance_outcome_lane_count=7',
'iwooos_product_rollout_acceptance_outcome_current_stage=read_only_outcome_routing',
'iwooos_product_rollout_acceptance_outcome_keep_read_only_lane_count=1',
'iwooos_product_rollout_acceptance_outcome_returned_for_evidence_count=0',
'iwooos_product_rollout_acceptance_outcome_quarantined_count=0',
'iwooos_product_rollout_acceptance_outcome_source_control_hold_count=0',
'iwooos_product_rollout_acceptance_outcome_host_safety_hold_count=0',
'iwooos_product_rollout_acceptance_outcome_human_review_candidate_count=0',
'iwooos_product_rollout_acceptance_outcome_runtime_candidate_count=0',
'iwooos_product_rollout_acceptance_outcome_owner_response_received_count=0',
'iwooos_product_rollout_acceptance_outcome_owner_response_accepted_count=0',
'iwooos_product_rollout_acceptance_outcome_redacted_evidence_accepted_count=0',
'iwooos_product_rollout_acceptance_outcome_active_runtime_gate_count=0',
'iwooos_product_rollout_acceptance_outcome_runtime_gate_open=false',
'iwooos_product_rollout_acceptance_outcome_runtime_wave_count=0',
'iwooos_product_rollout_acceptance_outcome_enforcement_wave_count=0',
'iwooos_product_rollout_acceptance_outcome_public_secret_exposure_allowed=false',
'iwooos_product_rollout_acceptance_outcome_kali_execution_authorized=false',
'iwooos_product_rollout_acceptance_outcome_source_control_mutation_authorized=false',
'runtime_execution_authorized=false',
'active_runtime_gate_count=0',
'action_buttons_allowed=false',
'not_authorization=true',
'secret_value_collection_allowed=false',
'repo_creation_authorized=false',
'refs_sync_authorized=false',
'workflow_modification_authorized=false',
'github_primary_switch_authorized=false',
'gitea_disablement_authorized=false',
] as const
const iwooosFirstProgressUnlockPathSteps = [
{ key: 'ownerResponseScope', step: '01', state: '待收件', icon: ClipboardCheck, tone: 'warn' },
{ key: 'redactedEvidencePointer', step: '02', state: '待補證', icon: FileText, tone: 'warn' },
@@ -5044,6 +5093,118 @@ function IwoooSProductRolloutAcceptanceGatesBoard() {
)
}
function IwoooSProductRolloutAcceptanceOutcomesBoard() {
const t = useTranslations('iwooos.productRolloutAcceptanceOutcomes')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
return (
<section style={{ marginBottom: 14 }} data-testid="iwooos-product-rollout-acceptance-outcomes-board">
<div style={{ ...band, padding: 16, background: '#fbf7f2' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 318px), 1fr))', gap: 16 }}>
<div>
<div style={{ marginBottom: 14 }}>
<h2 style={{ fontSize: 17, margin: 0 }}>{t('title')}</h2>
<p style={{ fontSize: 12, color: '#6b6259', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
{t('subtitle')}
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(132px, 1fr))', gap: 8, marginBottom: 14 }}>
{iwooosProductRolloutAcceptanceOutcomeSummary.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #eadbcf', borderRadius: 8, padding: 12, background: '#fff' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontSize: 11, color: '#796d63' }}>{t(`summary.${item.key}.label` as never)}</span>
<Icon size={16} color={toneColors[item.tone]} />
</div>
<div style={{ fontSize: 20, fontWeight: 700, lineHeight: 1.1, marginTop: 8, color: toneColors[item.tone] }}>
{item.value}
</div>
<p style={{ fontSize: 11, color: '#6b6259', lineHeight: 1.45, margin: '8px 0 0', ...textWrap }}>
{t(`summary.${item.key}.detail` as never)}
</p>
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(226px, 1fr))', gap: 10 }}>
{iwooosProductRolloutAcceptanceOutcomes.map(item => {
const Icon = item.icon
return (
<div
key={item.key}
style={{
display: 'grid',
gap: 8,
minHeight: 226,
border: '0.5px solid #eadbcf',
borderRadius: 8,
background: '#fff',
padding: 13,
color: '#141413',
...textWrap,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>
{t('laneLabel')} {item.lane}
</span>
<Icon size={18} color={toneColors[item.tone]} />
</div>
<div style={{ fontSize: 15, fontWeight: 700, color: toneColors[item.tone], lineHeight: 1.2 }}>
{item.state}
</div>
<h3 style={{ fontSize: 14, margin: 0, color: '#141413', lineHeight: 1.3 }}>
{t(`items.${item.key}.title` as never)}
</h3>
<p style={{ fontSize: 11, color: '#6b6259', lineHeight: 1.45, margin: 0, ...textWrap }}>
<strong>{t('whyLabel')}</strong>{t(`items.${item.key}.why` as never)}
</p>
<p style={{ fontSize: 11, color: '#4f5f52', lineHeight: 1.45, margin: 0, ...textWrap }}>
<strong>{t('nextLabel')}</strong>{t(`items.${item.key}.next` as never)}
</p>
<p style={{ fontSize: 11, color: '#694f4f', lineHeight: 1.45, margin: 0, ...textWrap }}>
<strong>{t('blockedLabel')}</strong>{t(`items.${item.key}.blocked` as never)}
</p>
</div>
)
})}
</div>
</div>
<div style={{ ...band, padding: 14, background: '#f4ede7', alignSelf: 'stretch' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
<ListChecks size={16} color={toneColors.steady} />
<h3 style={{ fontSize: 14, margin: 0 }}>{t('boundaryTitle')}</h3>
</div>
<p style={{ fontSize: 12, color: '#6b6259', lineHeight: 1.55, margin: '0 0 10px', ...textWrap }}>
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gap: 7 }}>
{iwooosProductRolloutAcceptanceOutcomeBoundaries.map(item => (
<span
key={item}
style={{
border: '0.5px solid #eadbcf',
borderRadius: 8,
padding: '7px 9px',
color: '#5b4f46',
fontSize: 11,
lineHeight: 1.4,
background: '#fff',
overflowWrap: 'anywhere',
}}
>
{item}
</span>
))}
</div>
</div>
</div>
</div>
</section>
)
}
function IwoooSFirstProgressUnlockPathBoard() {
const t = useTranslations('iwooos.firstProgressUnlockPath')
const summaryItems = [
@@ -9785,6 +9946,8 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
<IwoooSProductRolloutAcceptanceGatesBoard />
<IwoooSProductRolloutAcceptanceOutcomesBoard />
<IwoooSFirstProgressUnlockPathBoard />
<IwoooSFirstUnlockEvidencePacketBoard />