import type { ComponentType } from 'react'; import { ArrowDownToLine, Braces, CheckCircle2, Code2, FileInput, KeyRound, RefreshCw, Send, Settings2, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import type { BrowserTransformDirectionName, BrowserTransformExecution, BrowserTransformExplanationOwner, BrowserTransformExplanationStage, BrowserTransformProfile, BrowserTransformValueSummary, } from '@/types/models'; const OWNER_LABELS: Record = { webfuzzer: 'Web Fuzzer', extension: '浏览器扩展', page: '目标页面', yak: 'Yak 引擎', }; const STAGE_ICONS: Record> = { input: FileInput, prerequisite: RefreshCw, 'page-call': Code2, builtin: Braces, output: ArrowDownToLine, session: KeyRound, transport: Send, }; function proofLabel(stage: BrowserTransformExplanationStage): string { if (stage.proof === 'observed') return '录制实证'; if (stage.proof === 'supported') return '关联支持'; return '配置规则'; } function valueSummary(value?: BrowserTransformValueSummary): string { if (!value) return '不存在'; if (value.type === 'string' || value.type === 'bytes') { return `${value.type === 'string' ? '文本' : '字节'}${value.byteLength === undefined ? '' : ` · ${value.byteLength} B`}`; } if (value.type === 'array' || value.type === 'object') { return `${value.type === 'array' ? '数组' : '对象'}${value.itemCount === undefined ? '' : ` · ${value.itemCount} 项`}`; } const labels: Record = { null: 'null', undefined: 'undefined', boolean: '布尔值', number: '数字', }; return labels[value.type] || value.type; } function operationLabel(operation: BrowserTransformExplanationStage['operations'][number]): string { if (!operation.crypto) return operation.operation; const crypto = operation.crypto; const details = [ crypto.algorithm || crypto.operation, crypto.mode, crypto.padding, crypto.outputEncoding && `输出 ${crypto.outputEncoding}`, ].filter(Boolean); return details.join(' · '); } export function TransformDataFlowView({ profile, direction, execution, onDirectionChange, onConfigure, }: { profile: BrowserTransformProfile; direction: BrowserTransformDirectionName; execution?: BrowserTransformExecution; onDirectionChange: (direction: BrowserTransformDirectionName) => void; onConfigure: () => void; }) { const explained = profile.explanation?.directions.find((item) => item.direction === direction); const currentExecution = execution?.direction === direction ? execution : undefined; const availableDirections = profile.explanation?.directions.map((item) => item.direction) || []; if (!explained) return
这个方向还没有可解释的数据流保存当前配置后会生成语义步骤与执行证据。
; return
明文网关 · {direction === 'request' ? '请求方向' : '响应方向'} {direction === 'request' ? '明文如何成为线上请求' : '线上响应如何还原为明文'}

{explained.summary}

{availableDirections.length > 1 &&
{availableDirections.map((item) => )}
}
{(Object.entries(OWNER_LABELS) as Array<[BrowserTransformExplanationOwner, string]>).map(([owner, label]) => ( {label} ))} {currentExecution ? `最近回放 · ${currentExecution.durationMs.toFixed(1)} ms` : '等待本地回放'}
{explained.stages.map((stage, index) => { const Icon = STAGE_ICONS[stage.kind]; const traces = currentExecution?.nodeTrace.filter((trace) => stage.nodeIds.includes(trace.nodeId)) || []; const stageDuration = traces.reduce((total, trace) => total + trace.durationMs, 0); const hasDetails = Boolean(stage.inputPaths.length || stage.outputPaths.length || stage.operations.length || stage.evidence.length || stage.network || stage.source || traces.length); return
{index < explained.stages.length - 1 && } {OWNER_LABELS[stage.owner]}{proofLabel(stage)} {stage.title} {stage.summary} {traces.length ? <> : hasDetails ? 详情 : null} {hasDetails &&
{stage.network &&
网络边界
{stage.network.method} {stage.network.route}
{stage.network.statusCode &&
录制响应
{stage.network.statusCode}
}
} {stage.operations.length > 0 &&
处理
    {stage.operations.map((operation, operationIndex) =>
  • {operationLabel(operation)}{operation.destination && → {operation.destination}}
  • )}
} {(stage.inputPaths.length > 0 || stage.outputPaths.length > 0) &&
{stage.inputPaths.length > 0 &&
输入

{stage.inputPaths.map((path) => {path})}

} {stage.outputPaths.length > 0 &&
输出

{stage.outputPaths.map((path) => {path})}

}
} {stage.source && (stage.source.functionName || stage.source.url) &&
页面来源{[stage.source.functionName, stage.source.url, stage.source.lineNumber].filter((item) => item !== undefined).join(' · ')}
} {stage.evidence.length > 0 &&
关联证据
    {stage.evidence.map((evidence, evidenceIndex) =>
  • {evidence.label}
  • )}
} {traces.length > 0 &&
本次执行
    {traces.map((trace) =>
  1. {trace.name}{valueSummary(trace.output)}
  2. )}
}
}
; })}
{currentExecution &&
报文变化仅记录字段路径、类型与长度
{currentExecution.fieldChanges.length} 项
{currentExecution.fieldChanges.length ?
    {currentExecution.fieldChanges.map((change) =>
  • {change.change === 'added' ? '+' : change.change === 'removed' ? '-' : '→'} {change.path} {change.before && `${valueSummary(change.before)} → `}{valueSummary(change.after)}
  • )}
:

本次回放没有改变可观察的报文字段。

}
}
; }