import Card from '@/components/card'; import { Ellipsis, Modal } from '@c-x/ui'; import { useEffect, useRef, useState } from 'react'; import { DomainSecurityScanningResult, DomainSecurityScanningRiskDetail } from '@/api/types'; import { getSecurityScanningDetail, getUserSecurityScanningDetail } from '@/api'; import { Box, CircularProgress, Grid2 as Grid, List, ListItem, ListItemButton, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material'; import ListAltIcon from '@mui/icons-material/ListAlt'; import ViewSidebarOutlinedIcon from '@mui/icons-material/ViewSidebarOutlined'; import MonacoEditor from '@monaco-editor/react'; import type * as Monaco from 'monaco-editor'; interface RiskLevelBoxProps { level: 'severe' | 'critical' | 'suggest'; } const RiskLevelBox = ({ level }: RiskLevelBoxProps) => { const riskConfig = { severe: { text: '严重', color: 'risk.severe', }, critical: { text: '高风险', color: 'risk.critical', }, suggest: { text: '低风险', color: 'risk.suggest', }, }; const config = riskConfig[level]; if (!config) return null; return ( {config.text} ); }; const getLanguageByFilename = (filename: string = ''): string => { const extension = filename.split('.').pop()?.toLowerCase() || ''; const languageMap: Record = { js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript', json: 'json', html: 'html', htm: 'html', css: 'css', scss: 'scss', sass: 'sass', less: 'less', md: 'markdown', markdown: 'markdown', py: 'python', pyw: 'python', java: 'java', c: 'c', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', h: 'c', hpp: 'cpp', cs: 'csharp', php: 'php', php3: 'php', php4: 'php', php5: 'php', phtml: 'php', rb: 'ruby', go: 'go', rs: 'rust', swift: 'swift', kt: 'kotlin', kts: 'kotlin', scala: 'scala', sh: 'shell', bash: 'shell', sql: 'sql', yaml: 'yaml', yml: 'yaml', xml: 'xml', vue: 'vue', svelte: 'svelte', }; return languageMap[extension] || 'plaintext'; }; const TaskDetail = ({ task, open, onClose, admin, }: { task?: DomainSecurityScanningResult; open: boolean; onClose: () => void; admin: boolean }) => { const [loading, setLoading] = useState(true); const [vulns, setVulns] = useState([]); const [vulDetail, setVulDetail] = useState(undefined); const [viewMode, setViewMode] = useState<'list' | 'detail'>('detail'); const fetchData = async () => { setLoading(true); const resp = await (admin ? getSecurityScanningDetail : getUserSecurityScanningDetail)({ id: task?.id as string }); setVulns(resp); setLoading(false); }; useEffect(() => { setVulns([]); setVulDetail(undefined); setViewMode('detail'); console.log(!!vulDetail) if (open) { fetchData(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [task, open]); // 保存编辑器实例的引用 const editorRef = useRef(null); // 监听 vulDetail 的变化,重新高亮显示 useEffect(() => { if (editorRef.current) { highlightVulnerability(editorRef.current, vulDetail); } }, [vulDetail]); // 高亮显示漏洞代码 const highlightVulnerability = (editor: Monaco.editor.IStandaloneCodeEditor, vulDetail: DomainSecurityScanningRiskDetail | undefined) => { // 清除之前的装饰器 editor.deltaDecorations(editor.getModel()?.getAllDecorations().map(d => d.id) || [], []); // 如果有 start 和 end 位置信息,则设置选区 if (vulDetail?.start && vulDetail?.end) { const startLine = vulDetail.start.line ?? 1; const startColumn = vulDetail.start.col ?? 1; const endLine = vulDetail.end.line ?? startLine; const endColumn = vulDetail.end.col ?? startColumn; // 设置选区 const selection = { startLineNumber: startLine, startColumn: startColumn, endLineNumber: endLine, endColumn: endColumn }; editor.setSelection(selection); // 添加装饰器以增强高亮效果 editor.deltaDecorations([], [ { range: selection, options: { isWholeLine: false, className: 'highlighted-code', inlineClassName: 'highlighted-code-inline', overviewRuler: { color: 'rgba(255, 255, 0, 0.5)', position: 1 // Monaco.Editor.OverviewRulerLane.Center } } } ]); // 滚动到选区 editor.revealLineInCenter(startLine); } }; const handleEditorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor) => { // 保存编辑器实例 editorRef.current = editor; // 初始高亮 highlightVulnerability(editor, vulDetail); }; return ( {vulDetail && { setViewMode(value) }} > } {task?.name} / {task?.project_name} } width={1200} open={open} onCancel={onClose} footer={null} > {loading ? (
) : ( vulns.map((vuln) => ( { setVulDetail(vuln); }} sx={{ borderBottomWidth: '1px', borderBottomStyle: 'solid', borderBottomColor: 'background.paper', fontSize: '14px', width: '100%' }}> {vuln.desc} {vuln.filename}:{vuln?.start?.line} {!!vulDetail && vulDetail?.id === vuln.id && 关键代码位于第 {vulDetail?.start?.line} 行{vulDetail?.start?.line !== vulDetail?.end?.line && `至第 ${vulDetail?.end?.line} 行`}} {!!vulDetail && vulDetail?.id === vuln.id &&
{vulDetail?.lines}
} {!!vulDetail && vulDetail?.id === vuln.id && 修改建议:{vulDetail?.fix}}
)) )}
{viewMode === 'detail' && !!vulDetail && }
); }; export default TaskDetail;