From 461c5f6482f6338a2d480d7063bd5c94ac03076b Mon Sep 17 00:00:00 2001 From: Monster <389264167@qq.com> Date: Fri, 8 Aug 2025 17:17:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=89=AB=E6=8F=8F=E6=9F=A5=E7=9C=8B=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/api/CodeSnippet.ts | 8 +- ui/src/api/License.ts | 14 +- ui/src/api/types.ts | 38 ++- ui/src/components/codescan/taskDetail.tsx | 310 ++++++++++++++++++---- 4 files changed, 282 insertions(+), 88 deletions(-) diff --git a/ui/src/api/CodeSnippet.ts b/ui/src/api/CodeSnippet.ts index befc14d..542e693 100644 --- a/ui/src/api/CodeSnippet.ts +++ b/ui/src/api/CodeSnippet.ts @@ -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< diff --git a/ui/src/api/License.ts b/ui/src/api/License.ts index 554846d..c45b6f4 100644 --- a/ui/src/api/License.ts +++ b/ui/src/api/License.ts @@ -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`, diff --git a/ui/src/api/types.ts b/ui/src/api/types.ts index 94710b3..b918c53 100644 --- a/ui/src/api/types.ts +++ b/ui/src/api/types.ts @@ -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; /** 代码片段名称(可选) */ diff --git a/ui/src/components/codescan/taskDetail.tsx b/ui/src/components/codescan/taskDetail.tsx index 162fff5..e3f50c5 100644 --- a/ui/src/components/codescan/taskDetail.tsx +++ b/ui/src/components/codescan/taskDetail.tsx @@ -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 = { + 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([]); + const [vulDetail, setVulDetail] = useState(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(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} + } - sx={{ - '.MuiDialog-paper': { - maxWidth: 1300, - }, - }} width={1200} open={open} onCancel={onClose} footer={null} > - - {loading ? ( -
- -
- ) : ( - vulns.map((vuln) => ( - - - - - - {vuln.desc} - - + + + {loading ? ( +
+ +
+ ) : ( + vulns.map((vuln) => ( + - {vuln.filename}:{vuln?.start?.line} -
-
-
-
- )) - )} -
+ { + 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 && + + + + + } +
); From 7dfdf90267b08c8325d3a62a799a1878dfc66ae7 Mon Sep 17 00:00:00 2001 From: Monster <389264167@qq.com> Date: Fri, 8 Aug 2025 17:23:43 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86license=E7=9A=84?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/components/sidebar/aboutModal.tsx | 4 ++-- ui/src/components/sidebar/changeLicense.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/sidebar/aboutModal.tsx b/ui/src/components/sidebar/aboutModal.tsx index 8a53b04..275dac0 100644 --- a/ui/src/components/sidebar/aboutModal.tsx +++ b/ui/src/components/sidebar/aboutModal.tsx @@ -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 = ({ diff --git a/ui/src/components/sidebar/changeLicense.tsx b/ui/src/components/sidebar/changeLicense.tsx index 23fa232..50a96f3 100644 --- a/ui/src/components/sidebar/changeLicense.tsx +++ b/ui/src/components/sidebar/changeLicense.tsx @@ -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);