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;