feat: 切换 ui

This commit is contained in:
Gavan
2025-09-16 14:50:13 +08:00
parent 26fff20e6c
commit 747f0912be
52 changed files with 1715 additions and 1073 deletions

View File

@@ -1,19 +1,17 @@
import { useState, useEffect } from 'react';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/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 { Autocomplete, Box, Stack, TextField, Tooltip } from '@mui/material';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { DomainSecurityScanningResult, DomainSecurityScanningRiskResult, DomainUser } from '@/api/types';
import { ColumnsType } from '@ctzhian/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';
@@ -23,17 +21,21 @@ import { getUserSecurityScanningList } from '@/api';
const CodeScanTaskList = ({
admin,
users
users,
}: {
admin: boolean,
users: DomainUser[]
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 [dataSource, setDataSource] = useState<DomainSecurityScanningResult[]>(
[]
);
const [detail, setDetail] = useState<
DomainSecurityScanningResult | undefined
>();
const [filterUser, setFilterUser] = useState('');
const fetchData = async (params: {
@@ -43,7 +45,9 @@ const CodeScanTaskList = ({
author?: string;
}) => {
setLoading(true);
const res = await (admin ? getSecurityScanningList : getUserSecurityScanningList)({
const res = await (admin
? getSecurityScanningList
: getUserSecurityScanningList)({
page: params.page || page,
size: params.size || size,
author: params.author || filterUser,
@@ -57,7 +61,7 @@ const CodeScanTaskList = ({
setPage(1);
fetchData({
page: 1,
author: filterUser
author: filterUser,
});
}, [filterUser]);
@@ -69,71 +73,111 @@ const CodeScanTaskList = ({
render: (project_name, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<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') && <Tooltip title={record.error}>
<Box
sx={{
color: 'text.tertiary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
mt: '4px',
}}
>
{record.status === 'pending' && (
<Stack direction={'row'}>
<ErrorOutlineIcon sx={{
width: '16px',
height: '16px',
color: 'error.main'
}} />
<Box sx={{
lineHeight: '16px',
ml: '4px'
}}></Box>
<AutoModeIcon
sx={{
width: '16px',
height: '16px',
color: 'info.main',
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
</Tooltip>}
)}
{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' && (
<Tooltip title={record.error}>
<Stack direction={'row'}>
<ErrorOutlineIcon
sx={{
width: '16px',
height: '16px',
color: 'error.main',
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
</Tooltip>
)}
</Box>
</Stack>
);
@@ -145,10 +189,23 @@ const CodeScanTaskList = ({
render: (project_name, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.project_name}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.path}
</Box>
</Stack>
@@ -160,28 +217,30 @@ const CodeScanTaskList = ({
title: '扫描结果',
width: 260,
render(risk: DomainSecurityScanningRiskResult, record) {
const hasNoRisk = record.status !== 'pending' &&
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 = []
const tip = [];
if (risk.severe_count && risk.severe_count > 0) {
tip.push(`严重安全告警 ${risk.severe_count}`)
}
tip.push(`严重安全告警 ${risk.severe_count}`);
}
if (risk.critical_count && risk.critical_count > 0) {
tip.push(`高风险安全提醒 ${risk.critical_count}`)
tip.push(`高风险安全提醒 ${risk.critical_count}`);
}
if (risk.suggest_count && risk.suggest_count > 0) {
tip.push(`低风险安全提醒 ${risk.suggest_count}`)
tip.push(`低风险安全提醒 ${risk.suggest_count}`);
}
return (
<Tooltip title={ hasNoRisk ? '暂无风险' : tip.join(', ')}>
<Stack direction='row'
<Tooltip title={hasNoRisk ? '暂无风险' : tip.join(', ')}>
<Stack
direction='row'
onClick={() => {
if (!hasNoRisk) {
setDetail(record)
setDetail(record);
}
}}
sx={{
@@ -190,7 +249,10 @@ const CodeScanTaskList = ({
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',
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',
@@ -198,7 +260,7 @@ const CodeScanTaskList = ({
transition: 'box-shadow 0.3s ease',
userSelect: 'none',
'&:hover': {
cursor: "pointer",
cursor: 'pointer',
boxShadow: hasNoRisk ? '' : '0 0px 8px #FFCF62',
},
'@keyframes stripes': {
@@ -209,45 +271,82 @@ const CodeScanTaskList = ({
backgroundPosition: '30px 0',
},
},
}}>
{((record.status === 'success' || record.status === 'failed') && hasNoRisk) ? (
}}
>
{(record.status === 'success' || record.status === 'failed') &&
hasNoRisk ? (
// 如果没有风险,显示"无风险"
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
color: 'disabled.main',
}}>
<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>}
{!!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>
)
);
},
},
{
@@ -273,39 +372,50 @@ const CodeScanTaskList = ({
render: (text) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(text).format('YYYY-MM-DD')}
</Box>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<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>}
{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 }}