增加了代码安全扫描的前端功能

This commit is contained in:
Monster
2025-08-07 21:18:06 +08:00
parent 47b9a46c20
commit 9bc8abfede
10 changed files with 565 additions and 286 deletions

View File

@@ -843,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;
/** 建议数 */
@@ -1164,6 +1173,12 @@ export interface DomainWorkspaceFile {
workspace_id?: string;
}
export interface GithubComChaitinMonkeyCodeBackendEntTypesPosition {
col?: number;
line?: number;
offset?: number;
}
export interface InternalCodesnippetHandlerHttpV1GetContextReq {
/** 返回结果数量限制默认10 */
limit?: number;

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

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

@@ -1,279 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Table } from '@c-x/ui';
import { getSecurityScanningList, getSecurityScanningDetail } 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 { useRequest } from 'ahooks';
import { getListUser } from '@/api/User';
import { DomainSecurityScanningResult, DomainSecurityScanningRiskResult, DomainUser } from '@/api/types';
import User from '@/components/user';
const StatusText = {
pending: '等待扫描',
running: '正在扫描',
success: '扫描完成',
failed: '扫描失败',
}
const Chat = () => {
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 [filterUser, setFilterUser] = useState('');
const { data: userOptions = { users: [] } } = useRequest(() =>
getListUser({
page: 1,
size: 9999,
})
);
const fetchData = async (params: {
page?: number;
size?: number;
work_mode?: string;
author?: string;
}) => {
setLoading(true);
const res = await getSecurityScanningList({
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: 180,
render: (project_name, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{record?.name}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{record?.status ? StatusText[record.status] : '未知状态'}
</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: '扫描结果',
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' sx={{
color: '#fff',
fontSize: '12px',
width: '200px',
height: '24px',
lineHeight: '24px',
background: record.status === 'pending' ? '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',
},
},
}}>
{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: '24px',
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: '24px',
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: '24px',
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%' }}>
<Stack direction='row' spacing={2} sx={{ mb: 2 }}>
<Autocomplete
size='small'
sx={{ minWidth: 220 }}
options={userOptions.users || []}
getOptionLabel={(option) => option.username || ''}
value={
userOptions.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='calc(100% - 52px)'
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,
});
},
}}
/>
</Card>
);
};
export default Chat;

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

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

View File

@@ -40,7 +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 CodeSecurity = LazyLoadable(lazy(() => import('@/pages/codeSecurity')));
const AdminCodeScan = LazyLoadable(lazy(() => import('@/pages/codescan')));
const UserCodeScan = LazyLoadable(lazy(() => import('@/pages/user/codescan')));
const UserCompletion = LazyLoadable(
lazy(() => import('@/pages/user/completion'))
);
@@ -68,8 +69,8 @@ const routerConfig = [
element: <Chat />,
},
{
path: 'code-security',
element: <CodeSecurity />,
path: 'codescan',
element: <AdminCodeScan/>,
},
{
path: 'completion',
@@ -110,6 +111,10 @@ const routerConfig = [
path: 'completion',
element: <UserCompletion />,
},
{
path: 'codescan',
element: <UserCodeScan />,
},
{
path: 'setting',
element: <UserSetting />,