mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-03 07:13:26 +08:00
@@ -11,7 +11,11 @@
|
||||
*/
|
||||
|
||||
import request, { ContentType, RequestParams } from "./httpClient";
|
||||
import { DomainCodeFiles, DomainIndexResult, V1CliCreateParams } from "./types";
|
||||
import {
|
||||
DomainIndexResult,
|
||||
V1CliCreateParams,
|
||||
V1CliCreatePayload,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description 运行monkeycode-cli命令
|
||||
@@ -26,14 +30,14 @@ import { DomainCodeFiles, DomainIndexResult, V1CliCreateParams } from "./types";
|
||||
|
||||
export const v1CliCreate = (
|
||||
{ command, ...query }: V1CliCreateParams,
|
||||
codeFiles: DomainCodeFiles,
|
||||
fileMetas: V1CliCreatePayload,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<DomainIndexResult[]>({
|
||||
path: `/api/v1/cli/${command}`,
|
||||
method: "POST",
|
||||
query: query,
|
||||
body: codeFiles,
|
||||
body: fileMetas,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
|
||||
50
ui/src/api/CodeSnippet.ts
Normal file
50
ui/src/api/CodeSnippet.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
DomainCodeSnippet,
|
||||
InternalCodesnippetHandlerHttpV1GetContextReq,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description 为IDE端提供代码片段上下文检索功能,使用API Key认证。支持单个查询和批量查询。
|
||||
*
|
||||
* @tags CodeSnippet
|
||||
* @name PostGetContext
|
||||
* @summary IDE端上下文检索
|
||||
* @request POST:/api/v1/ide/codesnippet/context
|
||||
* @secure
|
||||
* @response `200` `(WebResp & {
|
||||
data?: (DomainCodeSnippet)[],
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const postGetContext = (
|
||||
request: InternalCodesnippetHandlerHttpV1GetContextReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainCodeSnippet[];
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/ide/codesnippet/context`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
secure: true,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
@@ -101,6 +101,25 @@ export const postEmbeddings = (params: RequestParams = {}) =>
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 固定回包 `{"code": 0, "data": "MonkeyCode"}`
|
||||
*
|
||||
* @tags OpenAIV1
|
||||
* @name GetHealth
|
||||
* @summary 健康检查
|
||||
* @request GET:/v1/health
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const getHealth = (params: RequestParams = {}) =>
|
||||
request<WebResp>({
|
||||
path: `/v1/health`,
|
||||
method: "GET",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 模型列表
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './Admin'
|
||||
export * from './Billing'
|
||||
export * from './Cli'
|
||||
export * from './CodeSnippet'
|
||||
export * from './Dashboard'
|
||||
export * from './Model'
|
||||
export * from './OpenAiv1'
|
||||
|
||||
@@ -246,8 +246,47 @@ export interface DomainCheckModelReq {
|
||||
type: "llm" | "coder" | "embedding" | "rerank";
|
||||
}
|
||||
|
||||
export interface DomainCodeFiles {
|
||||
files?: DomainFileMeta[];
|
||||
export interface DomainCodeSnippet {
|
||||
/** 结构化信息 */
|
||||
definition?: Record<string, any>;
|
||||
/** 定义文本 */
|
||||
definitionText?: string;
|
||||
/** 依赖项 */
|
||||
dependencies?: string[];
|
||||
/** 结束列号 */
|
||||
endColumn?: number;
|
||||
/** 结束行号 */
|
||||
endLine?: number;
|
||||
/** 容器名称 */
|
||||
field?: string;
|
||||
/** 内容哈希 */
|
||||
fileHash?: string;
|
||||
/** 文件路径 */
|
||||
filePath?: string;
|
||||
/** 代码片段ID */
|
||||
id?: string;
|
||||
/** 编程语言 */
|
||||
language?: string;
|
||||
/** 代码片段名称 */
|
||||
name?: string;
|
||||
/** 命名空间 */
|
||||
namespace?: string;
|
||||
/** 参数列表 */
|
||||
parameters?: Record<string, any>[];
|
||||
/** 代码片段内容 */
|
||||
rangeText?: string;
|
||||
/** 作用域信息 */
|
||||
scope?: string[];
|
||||
/** 函数签名 */
|
||||
signature?: string;
|
||||
/** 起始列号 */
|
||||
startColumn?: number;
|
||||
/** 起始行号 */
|
||||
startLine?: number;
|
||||
/** 代码片段类型 */
|
||||
type?: string;
|
||||
/** 关联的workspace file ID */
|
||||
workspace_file_id?: string;
|
||||
}
|
||||
|
||||
export interface DomainCompletionData {
|
||||
@@ -357,6 +396,7 @@ export interface DomainCreateModelReq {
|
||||
export interface DomainCreateSecurityScanningReq {
|
||||
/** 扫描语言 */
|
||||
language?: ConstsSecurityScanningLanguage;
|
||||
user_id?: string;
|
||||
/** 项目目录 */
|
||||
workspace?: string;
|
||||
}
|
||||
@@ -506,7 +546,7 @@ export interface DomainIndexResult {
|
||||
language?: string;
|
||||
name?: string;
|
||||
rangeText?: string;
|
||||
scope?: Record<string, any>[];
|
||||
scope?: unknown;
|
||||
signature?: string;
|
||||
startLine?: number;
|
||||
type?: string;
|
||||
@@ -564,19 +604,11 @@ export interface DomainListSecurityScanningReq {
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -796,6 +828,8 @@ export interface DomainSecurityScanningResult {
|
||||
id?: string;
|
||||
/** 扫描任务 */
|
||||
name?: string;
|
||||
/** 项目路径 */
|
||||
path?: string;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/** 风险结果 */
|
||||
@@ -809,17 +843,26 @@ export interface DomainSecurityScanningResult {
|
||||
export interface DomainSecurityScanningRiskDetail {
|
||||
/** 风险描述 */
|
||||
desc?: string;
|
||||
/** 风险代码行结束位置 */
|
||||
end?: GithubComChaitinMonkeyCodeBackendEntTypesPosition;
|
||||
/** 风险文件名 */
|
||||
filename?: string;
|
||||
/** 修复建议 */
|
||||
fix?: string;
|
||||
/** 风险id */
|
||||
id?: string;
|
||||
/** 风险等级 */
|
||||
level?: ConstsSecurityScanningRiskLevel;
|
||||
/** 风险代码行 */
|
||||
lines?: string;
|
||||
/** 风险代码行开始位置 */
|
||||
start?: GithubComChaitinMonkeyCodeBackendEntTypesPosition;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningRiskResult {
|
||||
/** 高危数 */
|
||||
critical_count?: number;
|
||||
id?: string;
|
||||
/** 严重数 */
|
||||
severe_count?: number;
|
||||
/** 建议数 */
|
||||
@@ -1130,6 +1173,32 @@ export interface DomainWorkspaceFile {
|
||||
workspace_id?: string;
|
||||
}
|
||||
|
||||
export interface GithubComChaitinMonkeyCodeBackendEntTypesPosition {
|
||||
col?: number;
|
||||
line?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface InternalCodesnippetHandlerHttpV1GetContextReq {
|
||||
/** 返回结果数量限制,默认10 */
|
||||
limit?: number;
|
||||
/** 批量查询参数 */
|
||||
queries?: InternalCodesnippetHandlerHttpV1Query[];
|
||||
/** 单个查询参数 */
|
||||
query?: InternalCodesnippetHandlerHttpV1Query;
|
||||
/** 工作区路径(必填) */
|
||||
workspacePath?: string;
|
||||
}
|
||||
|
||||
export interface InternalCodesnippetHandlerHttpV1Query {
|
||||
/** 编程语言(可选) */
|
||||
language?: string;
|
||||
/** 代码片段名称(可选) */
|
||||
name?: string;
|
||||
/** 代码片段类型(可选) */
|
||||
snippetType?: string;
|
||||
}
|
||||
|
||||
export interface WebResp {
|
||||
code?: number;
|
||||
data?: unknown;
|
||||
@@ -1144,34 +1213,18 @@ export interface DeleteDeleteAdminParams {
|
||||
export interface GetListAdminUserParams {
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetAdminLoginHistoryParams {
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -1189,17 +1242,9 @@ export interface GetListChatRecordParams {
|
||||
language?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
/** 工作模式 */
|
||||
work_mode?: string;
|
||||
@@ -1219,22 +1264,17 @@ export interface GetListCompletionRecordParams {
|
||||
language?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
/** 工作模式 */
|
||||
work_mode?: string;
|
||||
}
|
||||
|
||||
/** 代码文件信息 */
|
||||
export type V1CliCreatePayload = DomainFileMeta[];
|
||||
|
||||
export interface V1CliCreateParams {
|
||||
/** 标志 */
|
||||
flag?: string;
|
||||
@@ -1370,19 +1410,11 @@ export interface GetSecurityScanningListParams {
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -1405,17 +1437,9 @@ export interface GetUserListChatRecordParams {
|
||||
language?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
/** 工作模式 */
|
||||
work_mode?: string;
|
||||
@@ -1435,17 +1459,9 @@ export interface GetUserListCompletionRecordParams {
|
||||
language?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
/** 工作模式 */
|
||||
work_mode?: string;
|
||||
@@ -1493,34 +1509,18 @@ export interface DeleteDeleteUserParams {
|
||||
export interface GetListUserParams {
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetLoginHistoryParams {
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -1550,19 +1550,11 @@ export interface GetUserSecurityScanningListParams {
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -1576,19 +1568,11 @@ export interface GetListWorkspaceFilesParams {
|
||||
language?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/** 搜索关键词(文件路径) */
|
||||
search?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
/** 用户ID */
|
||||
user_id?: string;
|
||||
|
||||
157
ui/src/components/codescan/taskDetail.tsx
Normal file
157
ui/src/components/codescan/taskDetail.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import Card from '@/components/card';
|
||||
import { Ellipsis, Modal } from '@c-x/ui';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DomainSecurityScanningResult, DomainSecurityScanningRiskDetail } from '@/api/types';
|
||||
import { getSecurityScanningDetail } from '@/api';
|
||||
import { Box, CircularProgress, List, ListItem, ListItemButton, Stack } from '@mui/material';
|
||||
|
||||
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 (
|
||||
<Box sx={{
|
||||
backgroundColor: config.color,
|
||||
color: '#fff',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
width: '80px',
|
||||
minWidth: '80px',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px'
|
||||
}}>
|
||||
{config.text}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const TaskDetail = ({
|
||||
task,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
task?: DomainSecurityScanningResult;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [vulns, setVulns] = useState<DomainSecurityScanningRiskDetail[]>([]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
const resp = await getSecurityScanningDetail({
|
||||
id: task?.id as string
|
||||
});
|
||||
setVulns(resp);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(task)
|
||||
if (open) {
|
||||
fetchData();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [task, open]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<Ellipsis
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
lineHeight: '22px',
|
||||
width: 700,
|
||||
}}
|
||||
>
|
||||
{task?.name} / {task?.project_name}
|
||||
</Ellipsis>
|
||||
}
|
||||
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',
|
||||
}}>
|
||||
{vuln.filename}:{vuln?.start?.line}
|
||||
</Box>
|
||||
</Stack>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))
|
||||
)}
|
||||
</List>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetail;
|
||||
343
ui/src/components/codescan/taskList.tsx
Normal file
343
ui/src/components/codescan/taskList.tsx
Normal file
@@ -0,0 +1,343 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Table } from '@c-x/ui';
|
||||
import { getSecurityScanningList } from '@/api/SecurityScanning';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import Card from '@/components/card';
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
Stack,
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
|
||||
import { ColumnsType } from '@c-x/ui/dist/Table';
|
||||
import { DomainSecurityScanningResult, DomainSecurityScanningRiskResult, DomainUser } from '@/api/types';
|
||||
import User from '@/components/user';
|
||||
import TaskDetail from './taskDetail';
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||
import AutoModeIcon from '@mui/icons-material/AutoMode';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import { getUserSecurityScanningList } from '@/api';
|
||||
|
||||
const CodeScanTaskList = ({
|
||||
admin,
|
||||
users
|
||||
}: {
|
||||
admin: boolean,
|
||||
users: DomainUser[]
|
||||
}) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(20);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataSource, setDataSource] = useState<DomainSecurityScanningResult[]>([]);
|
||||
const [detail, setDetail] = useState<DomainSecurityScanningResult | undefined>();
|
||||
const [filterUser, setFilterUser] = useState('');
|
||||
|
||||
const fetchData = async (params: {
|
||||
page?: number;
|
||||
size?: number;
|
||||
work_mode?: string;
|
||||
author?: string;
|
||||
}) => {
|
||||
setLoading(true);
|
||||
const res = await (admin ? getSecurityScanningList : getUserSecurityScanningList)({
|
||||
page: params.page || page,
|
||||
size: params.size || size,
|
||||
author: params.author || filterUser,
|
||||
});
|
||||
setLoading(false);
|
||||
setTotal(res.total_count || 0);
|
||||
setDataSource(res.items || []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
fetchData({
|
||||
page: 1,
|
||||
author: filterUser
|
||||
});
|
||||
}, [filterUser]);
|
||||
|
||||
const columns: ColumnsType<DomainSecurityScanningResult> = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '扫描任务',
|
||||
width: 240,
|
||||
render: (project_name, record) => {
|
||||
return (
|
||||
<Stack direction='column'>
|
||||
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{record?.name}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
color: 'text.tertiary',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
mt: '4px'
|
||||
}}>
|
||||
{(record.status === 'pending') && <Stack direction={'row'}>
|
||||
<AutoModeIcon sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
color: 'info.main'
|
||||
}} />
|
||||
<Box sx={{
|
||||
lineHeight: '16px',
|
||||
ml: '4px'
|
||||
}}>等待扫描</Box>
|
||||
</Stack>}
|
||||
{(record.status === 'running') && <Stack direction={'row'}>
|
||||
<AutoModeIcon sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
color: 'info.main',
|
||||
animation: 'spin 1s linear infinite',
|
||||
'@keyframes spin': {
|
||||
'0%': {
|
||||
transform: 'rotate(0deg)',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'rotate(360deg)',
|
||||
},
|
||||
},
|
||||
}} />
|
||||
<Box sx={{
|
||||
lineHeight: '16px',
|
||||
ml: '4px'
|
||||
}}>正在扫描</Box>
|
||||
</Stack>}
|
||||
{(record.status === 'success') && <Stack direction={'row'}>
|
||||
<CheckCircleOutlineIcon sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
color: 'success.main'
|
||||
}} />
|
||||
<Box sx={{
|
||||
lineHeight: '16px',
|
||||
ml: '4px'
|
||||
}}>扫描完成</Box>
|
||||
</Stack>}
|
||||
{(record.status === 'failed') && <Stack direction={'row'}>
|
||||
<ErrorOutlineIcon sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
color: 'error.main'
|
||||
}} />
|
||||
<Box sx={{
|
||||
lineHeight: '16px',
|
||||
ml: '4px'
|
||||
}}>扫描失败</Box>
|
||||
</Stack>}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'project_name',
|
||||
render: (project_name, record) => {
|
||||
return (
|
||||
<Stack direction='column'>
|
||||
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{record?.project_name}
|
||||
</Box>
|
||||
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{record?.path}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'risk',
|
||||
title: '扫描结果',
|
||||
width: 260,
|
||||
render(risk: DomainSecurityScanningRiskResult, record) {
|
||||
const hasNoRisk = record.status !== 'pending' &&
|
||||
(!risk.severe_count || risk.severe_count <= 0) &&
|
||||
(!risk.critical_count || risk.critical_count <= 0) &&
|
||||
(!risk.suggest_count || risk.suggest_count <= 0);
|
||||
|
||||
const tip = []
|
||||
if (risk.severe_count && risk.severe_count > 0) {
|
||||
tip.push(`严重安全告警 ${risk.severe_count} 个`)
|
||||
}
|
||||
if (risk.critical_count && risk.critical_count > 0) {
|
||||
tip.push(`高风险安全提醒 ${risk.critical_count} 个`)
|
||||
}
|
||||
if (risk.suggest_count && risk.suggest_count > 0) {
|
||||
tip.push(`低风险安全提醒 ${risk.suggest_count} 个`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={ hasNoRisk ? '暂无风险' : tip.join(', ')}>
|
||||
<Stack direction='row'
|
||||
onClick={() => {
|
||||
if (!hasNoRisk) {
|
||||
setDetail(record)
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
color: '#fff',
|
||||
fontSize: '12px',
|
||||
width: '200px',
|
||||
height: '24px',
|
||||
lineHeight: '24px',
|
||||
background: (record.status === 'pending' || record.status === 'running') ? 'repeating-linear-gradient(45deg, #f0f0f0, #f0f0f0 10px, #e0e0e0 10px, #e0e0e0 20px)' : '#F1F2F8',
|
||||
backgroundSize: '30px 30px',
|
||||
animation: 'stripes 1s linear infinite',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
transition: 'box-shadow 0.3s ease',
|
||||
userSelect: 'none',
|
||||
'&:hover': {
|
||||
cursor: "pointer",
|
||||
boxShadow: hasNoRisk ? '' : '0 0px 8px #FFCF62',
|
||||
},
|
||||
'@keyframes stripes': {
|
||||
'0%': {
|
||||
backgroundPosition: '0 0',
|
||||
},
|
||||
'100%': {
|
||||
backgroundPosition: '30px 0',
|
||||
},
|
||||
},
|
||||
}}>
|
||||
{((record.status === 'success' || record.status === 'failed') && hasNoRisk) ? (
|
||||
// 如果没有风险,显示"无风险"
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
color: 'disabled.main',
|
||||
}}>
|
||||
暂无风险
|
||||
</Box>
|
||||
) : (
|
||||
// 否则,显示原有的风险条
|
||||
<>
|
||||
{!!risk.severe_count && risk.severe_count > 0 && <Box sx={{
|
||||
backgroundColor: 'risk.severe',
|
||||
minWidth: '30px',
|
||||
width: (risk.severe_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
|
||||
textAlign: 'center'
|
||||
}}>{risk.severe_count}</Box>}
|
||||
{!!risk.critical_count && risk.critical_count > 0 && <Box sx={{
|
||||
backgroundColor: 'risk.critical',
|
||||
minWidth: '30px',
|
||||
width: (risk.critical_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
|
||||
textAlign: 'center'
|
||||
}}>{risk.critical_count}</Box>}
|
||||
{!!risk.suggest_count && risk.suggest_count > 0 && <Box sx={{
|
||||
backgroundColor: 'risk.suggest',
|
||||
minWidth: '30px',
|
||||
width: (risk.suggest_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
|
||||
textAlign: 'center'
|
||||
}}>{risk.suggest_count}</Box>}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'user',
|
||||
title: '成员',
|
||||
width: 200,
|
||||
render(value: DomainUser) {
|
||||
return (
|
||||
<User
|
||||
id={value.id!}
|
||||
username={value.username!}
|
||||
email={value.email!}
|
||||
avatar={value.avatar_url!}
|
||||
deleted={value.is_deleted!}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '扫描时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 160,
|
||||
render: (text) => {
|
||||
return (
|
||||
<Stack direction='column'>
|
||||
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{dayjs.unix(text).format('YYYY-MM-DD')}
|
||||
</Box>
|
||||
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{dayjs.unix(text).format('HH:mm:ss')}
|
||||
</Box>
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card sx={{ flex: 1, height: '100%' }}>
|
||||
{admin && <Stack direction='row' spacing={2} sx={{ mb: 2 }}>
|
||||
<Autocomplete
|
||||
size='small'
|
||||
sx={{ minWidth: 220 }}
|
||||
options={users || []}
|
||||
getOptionLabel={(option) => option.username || ''}
|
||||
value={
|
||||
users?.find((item) => item.username === filterUser) ||
|
||||
null
|
||||
}
|
||||
onChange={(_, newValue) =>
|
||||
setFilterUser(newValue ? newValue.username! : '')
|
||||
}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.username === value.username
|
||||
}
|
||||
renderInput={(params) => <TextField {...params} label='成员' />}
|
||||
clearOnEscape
|
||||
/>
|
||||
</Stack>}
|
||||
<Table
|
||||
height={admin ? 'calc(100% - 52px)' : '100%'}
|
||||
sx={{ mx: -2 }}
|
||||
PaginationProps={{
|
||||
sx: {
|
||||
pt: 2,
|
||||
mx: 2,
|
||||
},
|
||||
}}
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
rowKey='id'
|
||||
pagination={{
|
||||
page,
|
||||
pageSize: size,
|
||||
total,
|
||||
onChange: (page: number, size: number) => {
|
||||
setPage(page);
|
||||
setSize(size);
|
||||
fetchData({
|
||||
page: page,
|
||||
size: size,
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TaskDetail
|
||||
open={!!detail}
|
||||
onClose={() => setDetail(undefined)}
|
||||
task={detail}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeScanTaskList;
|
||||
@@ -7,6 +7,7 @@ const ADMIN_BREADCRUMB_MAP: Record<string, { title: string; to: string }> = {
|
||||
dashboard: { title: '仪表盘', to: '/' },
|
||||
chat: { title: '对话记录', to: '/chat' },
|
||||
completion: { title: '补全记录', to: '/completion' },
|
||||
codescan: { title: '代码安全', to: '/codescan' },
|
||||
model: { title: '模型管理', to: '/model' },
|
||||
'member-management': { title: '成员管理', to: '/member-management' },
|
||||
admin: { title: '管理员', to: '/admin' },
|
||||
@@ -16,6 +17,7 @@ const USER_BREADCRUMB_MAP: Record<string, { title: string; to: string }> = {
|
||||
dashboard: { title: '仪表盘', to: '/user/dashboard' },
|
||||
chat: { title: '对话记录', to: '/user/chat' },
|
||||
completion: { title: '补全记录', to: '/user/completion' },
|
||||
codescan: { title: '代码安全', to: '/user/codescan' },
|
||||
};
|
||||
|
||||
const Bread = () => {
|
||||
|
||||
@@ -43,9 +43,8 @@ const Header = () => {
|
||||
>
|
||||
下载客户端
|
||||
</Button>
|
||||
<Tooltip title='退出登录' arrow>
|
||||
<Tooltip title='退出登录'>
|
||||
<IconButton
|
||||
title='退出登录'
|
||||
size='small'
|
||||
sx={{
|
||||
bgcolor: '#fff',
|
||||
|
||||
@@ -9,9 +9,9 @@ const StyledLabel = styled('div')<StyledLabelProps>(
|
||||
// 获取颜色值
|
||||
const getColor = (colorProp: string) => {
|
||||
// 如果是主题预设颜色
|
||||
if (['success', 'warning', 'error', 'info'].includes(colorProp)) {
|
||||
if (['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)) {
|
||||
return theme.palette[
|
||||
colorProp as 'success' | 'warning' | 'error' | 'info'
|
||||
colorProp as 'success' | 'warning' | 'error' | 'info' | 'disabled'
|
||||
].main;
|
||||
}
|
||||
// 如果是 default,使用灰色
|
||||
@@ -26,10 +26,10 @@ const StyledLabel = styled('div')<StyledLabelProps>(
|
||||
|
||||
// 获取背景颜色(淡化版本)
|
||||
const getBackgroundColor = (colorProp: string) => {
|
||||
if (['success', 'warning', 'error', 'info'].includes(colorProp)) {
|
||||
if (['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)) {
|
||||
// 使用主题的 light 版本,如果没有则使用 alpha 透明度
|
||||
const palette =
|
||||
theme.palette[colorProp as 'success' | 'warning' | 'error' | 'info'];
|
||||
theme.palette[colorProp as 'success' | 'warning' | 'error' | 'info' | 'disabled'];
|
||||
return alpha(palette.main, 0.15);
|
||||
}
|
||||
// 如果是 default,使用淡灰色背景
|
||||
|
||||
@@ -35,8 +35,8 @@ const ADMIN_MENUS = [
|
||||
},
|
||||
{
|
||||
label: '代码安全',
|
||||
value: '/code-security',
|
||||
pathname: 'code-security',
|
||||
value: '/codescan',
|
||||
pathname: 'codescan',
|
||||
icon: 'icon-daimaanquan1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
@@ -92,6 +92,14 @@ const USER_MENUS = [
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '代码安全',
|
||||
value: '/user/codescan',
|
||||
pathname: '/user/codescan',
|
||||
icon: 'icon-daimaanquan1',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
// {
|
||||
// label: '设置',
|
||||
// value: '/user/setting',
|
||||
|
||||
19
ui/src/pages/codescan/index.tsx
Normal file
19
ui/src/pages/codescan/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useRequest } from 'ahooks';
|
||||
import { getListUser } from '@/api/User';
|
||||
import CodeScanTaskList from '../../components/codescan/taskList';
|
||||
|
||||
const AdminCodeScanTaskList = (
|
||||
) => {
|
||||
const res = useRequest(() => {
|
||||
return getListUser({
|
||||
page: 1,
|
||||
size: 9999,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<CodeScanTaskList admin={true} users={res.data?.users || []}/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminCodeScanTaskList;
|
||||
@@ -108,7 +108,7 @@ const CardServiceSettings = () => {
|
||||
<Box sx={{
|
||||
mt: 1,
|
||||
fontSize: '0.75rem',
|
||||
color: '#FFA500',
|
||||
color: 'warning.main',
|
||||
fontWeight: 'normal'
|
||||
}}>
|
||||
用于解决 VSCode 插件无法连接 MonkeyCode 服务的问题
|
||||
|
||||
@@ -194,9 +194,7 @@ const ModelItem = ({
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<Stack direction='row' alignItems='center'>
|
||||
{data.is_active && (
|
||||
<StyledLabel color='success'>正在使用</StyledLabel>
|
||||
)}
|
||||
<StyledLabel color={data.is_active ? 'success' : 'disabled'}>{data.is_active ? '正在使用' : '未激活'}</StyledLabel>
|
||||
</Stack>
|
||||
<Stack direction='row' sx={{ button: { minWidth: 0 } }} gap={2}>
|
||||
{!data.is_active && (
|
||||
@@ -204,6 +202,9 @@ const ModelItem = ({
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'success.main',
|
||||
'&:hover': {
|
||||
fontWeight: 700
|
||||
},
|
||||
}}
|
||||
onClick={onActiveModel}
|
||||
>
|
||||
@@ -216,6 +217,9 @@ const ModelItem = ({
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'info.main',
|
||||
'&:hover': {
|
||||
fontWeight: 700
|
||||
},
|
||||
}}
|
||||
onClick={() => onEdit(data)}
|
||||
>
|
||||
@@ -228,6 +232,9 @@ const ModelItem = ({
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
'&:hover': {
|
||||
fontWeight: 700
|
||||
},
|
||||
}}
|
||||
onClick={onInactiveModel}
|
||||
>
|
||||
@@ -240,6 +247,9 @@ const ModelItem = ({
|
||||
disableRipple
|
||||
sx={{
|
||||
color: 'error.main',
|
||||
'&:hover': {
|
||||
fontWeight: 700
|
||||
},
|
||||
}}
|
||||
onClick={onRemoveModel}
|
||||
>
|
||||
|
||||
10
ui/src/pages/user/codescan/index.tsx
Normal file
10
ui/src/pages/user/codescan/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import CodeScanTaskList from '../../../components/codescan/taskList';
|
||||
|
||||
const AdminCodeScanTaskList = (
|
||||
) => {
|
||||
return (
|
||||
<CodeScanTaskList admin={false} users={[]}/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminCodeScanTaskList;
|
||||
@@ -40,6 +40,8 @@ const Login = LazyLoadable(lazy(() => import('@/pages/login')));
|
||||
const UserLogin = LazyLoadable(lazy(() => import('@/pages/user/login')));
|
||||
const Expectation = LazyLoadable(lazy(() => import('@/pages/expectation')));
|
||||
const UserChat = LazyLoadable(lazy(() => import('@/pages/user/chat')));
|
||||
const AdminCodeScan = LazyLoadable(lazy(() => import('@/pages/codescan')));
|
||||
const UserCodeScan = LazyLoadable(lazy(() => import('@/pages/user/codescan')));
|
||||
const UserCompletion = LazyLoadable(
|
||||
lazy(() => import('@/pages/user/completion'))
|
||||
);
|
||||
@@ -67,8 +69,8 @@ const routerConfig = [
|
||||
element: <Chat />,
|
||||
},
|
||||
{
|
||||
path: 'code-security',
|
||||
element: <Expectation />,
|
||||
path: 'codescan',
|
||||
element: <AdminCodeScan/>,
|
||||
},
|
||||
{
|
||||
path: 'completion',
|
||||
@@ -109,6 +111,10 @@ const routerConfig = [
|
||||
path: 'completion',
|
||||
element: <UserCompletion />,
|
||||
},
|
||||
{
|
||||
path: 'codescan',
|
||||
element: <UserCodeScan />,
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
element: <UserSetting />,
|
||||
|
||||
@@ -25,9 +25,17 @@ const lightTheme = createTheme(
|
||||
dark: '#229A16',
|
||||
contrastText: '#fff',
|
||||
},
|
||||
warning: {
|
||||
main: '#FFA500',
|
||||
},
|
||||
info: {
|
||||
main: '#3248F2',
|
||||
},
|
||||
risk: {
|
||||
severe: '#FF6262',
|
||||
critical: '#FFA762',
|
||||
suggest: '#FFCF62'
|
||||
},
|
||||
disabled: {
|
||||
main: '#666',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user