mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-02 14:53:55 +08:00
@@ -11,11 +11,7 @@
|
||||
*/
|
||||
|
||||
import { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
DomainCodeSnippet,
|
||||
InternalCodesnippetHandlerHttpV1GetContextReq,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
import { DomainCodeSnippet, V1GetContextReq, WebResp } from "./types";
|
||||
|
||||
/**
|
||||
* @description 为IDE端提供代码片段上下文检索功能,使用API Key认证。支持单个查询和批量查询。
|
||||
@@ -32,7 +28,7 @@ import {
|
||||
*/
|
||||
|
||||
export const postGetContext = (
|
||||
request: InternalCodesnippetHandlerHttpV1GetContextReq,
|
||||
request: V1GetContextReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
|
||||
@@ -11,11 +11,7 @@
|
||||
*/
|
||||
|
||||
import request, { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
GithubComChaitinMonkeyCodeBackendProDomainLicenseResp,
|
||||
V1LicenseCreatePayload,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
import { DomainLicenseResp, V1LicenseCreatePayload, WebResp } from "./types";
|
||||
|
||||
/**
|
||||
* @description Get license
|
||||
@@ -25,7 +21,7 @@ import {
|
||||
* @summary Get license
|
||||
* @request GET:/api/v1/license
|
||||
* @response `200` `(WebResp & {
|
||||
data?: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp,
|
||||
data?: DomainLicenseResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
@@ -33,7 +29,7 @@ import {
|
||||
export const v1LicenseList = (params: RequestParams = {}) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp;
|
||||
data?: DomainLicenseResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/license`,
|
||||
@@ -51,7 +47,7 @@ export const v1LicenseList = (params: RequestParams = {}) =>
|
||||
* @summary Upload license
|
||||
* @request POST:/api/v1/license
|
||||
* @response `200` `(WebResp & {
|
||||
data?: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp,
|
||||
data?: DomainLicenseResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
@@ -62,7 +58,7 @@ export const v1LicenseCreate = (
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp;
|
||||
data?: DomainLicenseResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/license`,
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export enum DomainLicenseEdition {
|
||||
LicenseEditionFree = 0,
|
||||
LicenseEditionContributor = 1,
|
||||
LicenseEditionEnterprise = 2,
|
||||
}
|
||||
|
||||
export enum DomainCodeLanguageType {
|
||||
CodeLanguageTypeGo = "go",
|
||||
CodeLanguageTypePython = "python",
|
||||
@@ -557,6 +563,13 @@ export interface DomainInviteResp {
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface DomainLicenseResp {
|
||||
edition?: DomainLicenseEdition;
|
||||
expired_at?: number;
|
||||
started_at?: number;
|
||||
state?: number;
|
||||
}
|
||||
|
||||
export interface DomainListAdminLoginHistoryResp {
|
||||
has_next_page?: boolean;
|
||||
login_histories?: DomainAdminLoginHistory[];
|
||||
@@ -824,6 +837,8 @@ export interface DomainSecurityScanningBrief {
|
||||
export interface DomainSecurityScanningResult {
|
||||
/** 扫描开始时间 */
|
||||
created_at?: number;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
/** 扫描任务id */
|
||||
id?: string;
|
||||
/** 扫描任务 */
|
||||
@@ -841,10 +856,12 @@ export interface DomainSecurityScanningResult {
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningRiskDetail {
|
||||
/** 代码内容 */
|
||||
content?: string;
|
||||
/** 风险描述 */
|
||||
desc?: string;
|
||||
/** 风险代码行结束位置 */
|
||||
end?: GithubComChaitinMonkeyCodeBackendEntTypesPosition;
|
||||
end?: TypesPosition;
|
||||
/** 风险文件名 */
|
||||
filename?: string;
|
||||
/** 修复建议 */
|
||||
@@ -856,7 +873,7 @@ export interface DomainSecurityScanningRiskDetail {
|
||||
/** 风险代码行 */
|
||||
lines?: string;
|
||||
/** 风险代码行开始位置 */
|
||||
start?: GithubComChaitinMonkeyCodeBackendEntTypesPosition;
|
||||
start?: TypesPosition;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningRiskResult {
|
||||
@@ -1173,31 +1190,24 @@ export interface DomainWorkspaceFile {
|
||||
workspace_id?: string;
|
||||
}
|
||||
|
||||
export interface GithubComChaitinMonkeyCodeBackendEntTypesPosition {
|
||||
export interface TypesPosition {
|
||||
col?: number;
|
||||
line?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface GithubComChaitinMonkeyCodeBackendProDomainLicenseResp {
|
||||
edition?: number;
|
||||
expired_at?: number;
|
||||
started_at?: number;
|
||||
state?: number;
|
||||
}
|
||||
|
||||
export interface InternalCodesnippetHandlerHttpV1GetContextReq {
|
||||
export interface V1GetContextReq {
|
||||
/** 返回结果数量限制,默认10 */
|
||||
limit?: number;
|
||||
/** 批量查询参数 */
|
||||
queries?: InternalCodesnippetHandlerHttpV1Query[];
|
||||
queries?: V1Query[];
|
||||
/** 单个查询参数 */
|
||||
query?: InternalCodesnippetHandlerHttpV1Query;
|
||||
query?: V1Query;
|
||||
/** 工作区路径(必填) */
|
||||
workspacePath?: string;
|
||||
}
|
||||
|
||||
export interface InternalCodesnippetHandlerHttpV1Query {
|
||||
export interface V1Query {
|
||||
/** 编程语言(可选) */
|
||||
language?: string;
|
||||
/** 代码片段名称(可选) */
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import Card from '@/components/card';
|
||||
import { Ellipsis, Modal } from '@c-x/ui';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { DomainSecurityScanningResult, DomainSecurityScanningRiskDetail } from '@/api/types';
|
||||
import { getSecurityScanningDetail, getUserSecurityScanningDetail } from '@/api';
|
||||
import { Box, CircularProgress, List, ListItem, ListItemButton, Stack } from '@mui/material';
|
||||
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';
|
||||
@@ -46,6 +51,57 @@ const RiskLevelBox = ({ level }: RiskLevelBoxProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getLanguageByFilename = (filename: string = ''): string => {
|
||||
const extension = filename.split('.').pop()?.toLowerCase() || '';
|
||||
const languageMap: Record<string, string> = {
|
||||
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,
|
||||
@@ -59,6 +115,8 @@ const TaskDetail = ({
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [vulns, setVulns] = useState<DomainSecurityScanningRiskDetail[]>([]);
|
||||
const [vulDetail, setVulDetail] = useState<DomainSecurityScanningRiskDetail | undefined>(undefined);
|
||||
const [viewMode, setViewMode] = useState<'list' | 'detail'>('detail');
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
@@ -70,87 +128,221 @@ const TaskDetail = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(task)
|
||||
setVulns([]);
|
||||
setVulDetail(undefined);
|
||||
setViewMode('detail');
|
||||
console.log(!!vulDetail)
|
||||
if (open) {
|
||||
fetchData();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [task, open]);
|
||||
|
||||
// 保存编辑器实例的引用
|
||||
const editorRef = useRef<Monaco.editor.IStandaloneCodeEditor | null>(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 (
|
||||
<Modal
|
||||
title={
|
||||
<Modal title={
|
||||
<Stack direction={'row'} >
|
||||
{vulDetail && <ToggleButtonGroup
|
||||
exclusive
|
||||
value={viewMode}
|
||||
size="small"
|
||||
onChange={(e, value) => {
|
||||
setViewMode(value)
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="list">
|
||||
<ListAltIcon />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="detail" disabled={!vulDetail}>
|
||||
<ViewSidebarOutlinedIcon sx={{ transform: 'rotate(180deg)' }} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>}
|
||||
<Ellipsis
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
lineHeight: '22px',
|
||||
lineHeight: '40px',
|
||||
width: 700,
|
||||
ml: '20px'
|
||||
}}
|
||||
>
|
||||
{task?.name} / {task?.project_name}
|
||||
</Ellipsis>
|
||||
</Stack>
|
||||
}
|
||||
sx={{
|
||||
'.MuiDialog-paper': {
|
||||
maxWidth: 1300,
|
||||
},
|
||||
}}
|
||||
width={1200}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
>
|
||||
<Card sx={{ p: 0, background: 'transparent', boxShadow: 'none' }}>
|
||||
<List>
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
) : (
|
||||
vulns.map((vuln) => (
|
||||
<ListItem key={vuln.id} sx={{
|
||||
padding: 0,
|
||||
width: '100%'
|
||||
}}>
|
||||
<ListItemButton sx={{
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'background.paper',
|
||||
fontSize: '14px',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Stack direction={"column"} sx={{
|
||||
width: '100%'
|
||||
}}>
|
||||
<Stack direction={"row"}>
|
||||
<RiskLevelBox level={vuln.level as 'severe' | 'critical' | 'suggest'} />
|
||||
<Box sx={{
|
||||
fontSize: '14px',
|
||||
ml: '20px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: '20px'
|
||||
}}>{vuln.desc}</Box>
|
||||
</Stack>
|
||||
<Box sx={{
|
||||
color: 'text.tertiary',
|
||||
fontSize: '14px',
|
||||
mt: '6px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
<Grid container sx={{ height: '70vh' }}>
|
||||
<Grid size={viewMode === 'detail' && !!vulDetail ? 5 : 12} sx={{
|
||||
height: '100%',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<List>
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
) : (
|
||||
vulns.map((vuln) => (
|
||||
<ListItem key={vuln.id}
|
||||
sx={{
|
||||
padding: 0,
|
||||
width: '100%'
|
||||
}}>
|
||||
{vuln.filename}:{vuln?.start?.line}
|
||||
</Box>
|
||||
</Stack>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
<ListItemButton
|
||||
selected={vulDetail?.id === vuln.id}
|
||||
onClick={() => {
|
||||
setVulDetail(vuln);
|
||||
}}
|
||||
sx={{
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'background.paper',
|
||||
fontSize: '14px',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Stack direction={"column"} sx={{
|
||||
width: '100%'
|
||||
}}>
|
||||
<Stack direction={"row"}>
|
||||
<RiskLevelBox level={vuln.level as 'severe' | 'critical' | 'suggest'} />
|
||||
<Box sx={{
|
||||
fontSize: '14px',
|
||||
ml: '20px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: '20px'
|
||||
}}>{vuln.desc}</Box>
|
||||
</Stack>
|
||||
<Box sx={{
|
||||
color: 'text.tertiary',
|
||||
fontSize: '14px',
|
||||
mt: '6px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{vuln.filename}:{vuln?.start?.line}
|
||||
</Box>
|
||||
|
||||
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
|
||||
fontSize: '12px',
|
||||
mt: '6px',
|
||||
}}>关键代码位于第 {vulDetail?.start?.line} 行{vulDetail?.start?.line !== vulDetail?.end?.line && `至第 ${vulDetail?.end?.line} 行`}</Box>}
|
||||
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
|
||||
fontSize: '12px',
|
||||
mt: '6px',
|
||||
}}>
|
||||
<pre style={{
|
||||
backgroundColor: '#fff',
|
||||
padding: '10px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #ccc',
|
||||
overflow: 'hidden',
|
||||
}}>{vulDetail?.lines}</pre>
|
||||
</Box>}
|
||||
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
|
||||
fontSize: '12px',
|
||||
mt: '6px',
|
||||
}}>修改建议:{vulDetail?.fix}</Box>}
|
||||
</Stack>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
</Grid>
|
||||
{viewMode === 'detail' && !!vulDetail && <Grid size={7} sx={{
|
||||
height: '100%',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}>
|
||||
<style> {`.monaco-editor.vs-dark .highlighted-code { background-color: rgba(255, 0, 0, 0.3) !important; }`} </style>
|
||||
<MonacoEditor
|
||||
height="100%"
|
||||
value={vulDetail?.content || ''}
|
||||
theme='vs-dark'
|
||||
language={getLanguageByFilename(vulDetail?.filename)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
></MonacoEditor>
|
||||
</Box>
|
||||
</Grid>}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import dayjs from 'dayjs';
|
||||
import { useState } from 'react';
|
||||
import { Ellipsis, Modal } from '@c-x/ui';
|
||||
import { Box, Button, Link, Stack } from '@mui/material';
|
||||
import { GithubComChaitinMonkeyCodeBackendProDomainLicenseResp } from '@/api/types';
|
||||
import { DomainLicenseResp } from '@/api/types';
|
||||
import ChangeLicense from './changeLicense';
|
||||
|
||||
interface LicenseModalProps {
|
||||
@@ -10,7 +10,7 @@ interface LicenseModalProps {
|
||||
onClose: () => void;
|
||||
curVersion: string;
|
||||
latestVersion: string;
|
||||
license: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp | undefined;
|
||||
license: DomainLicenseResp | undefined;
|
||||
}
|
||||
|
||||
const AboutModal = ({
|
||||
|
||||
@@ -2,7 +2,7 @@ import dayjs from 'dayjs';
|
||||
import { useState } from 'react';
|
||||
import { Ellipsis, message, Modal } from '@c-x/ui';
|
||||
import { Box, Button, Link, Stack, TextField } from '@mui/material';
|
||||
import { GithubComChaitinMonkeyCodeBackendProDomainLicenseResp } from '@/api/types';
|
||||
import { DomainLicenseResp } from '@/api/types';
|
||||
import { v1LicenseCreate } from '@/api';
|
||||
|
||||
interface LicenseModalProps {
|
||||
@@ -27,7 +27,7 @@ const ChangeLicense = ({
|
||||
license_type: 'code',
|
||||
license_code: code,
|
||||
license_file: '' as any
|
||||
}).then((resp: GithubComChaitinMonkeyCodeBackendProDomainLicenseResp) => {
|
||||
}).then((resp: DomainLicenseResp) => {
|
||||
message.success("切换授权成功");
|
||||
console.log(resp)
|
||||
setVerifing(false);
|
||||
|
||||
Reference in New Issue
Block a user