Merge pull request #222 from safe1ine/main

增加了代码安全扫描记录的页面
This commit is contained in:
safe1ine
2025-08-07 21:19:51 +08:00
committed by GitHub
17 changed files with 760 additions and 140 deletions

View File

@@ -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
View 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,
});

View File

@@ -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 模型列表
*

View File

@@ -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'

View File

@@ -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;

View 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;

View 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;

View File

@@ -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 = () => {

View File

@@ -43,9 +43,8 @@ const Header = () => {
>
</Button>
<Tooltip title='退出登录' arrow>
<Tooltip title='退出登录'>
<IconButton
title='退出登录'
size='small'
sx={{
bgcolor: '#fff',

View File

@@ -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使用淡灰色背景

View File

@@ -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',

View 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;

View File

@@ -108,7 +108,7 @@ const CardServiceSettings = () => {
<Box sx={{
mt: 1,
fontSize: '0.75rem',
color: '#FFA500',
color: 'warning.main',
fontWeight: 'normal'
}}>
VSCode MonkeyCode

View File

@@ -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}
>

View File

@@ -0,0 +1,10 @@
import CodeScanTaskList from '../../../components/codescan/taskList';
const AdminCodeScanTaskList = (
) => {
return (
<CodeScanTaskList admin={false} users={[]}/>
);
};
export default AdminCodeScanTaskList;

View File

@@ -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 />,

View File

@@ -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',
},