Skip to content

Commit 0592206

Browse files
committed
feat: Add show VEX view
1 parent b000618 commit 0592206

File tree

4 files changed

+181
-5
lines changed

4 files changed

+181
-5
lines changed

backend/app/controllers/vex_controller.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
router = APIRouter()
3131

32-
@router.get("/vex/{user_id}")
32+
@router.get("/vex/user/{user_id}")
3333
async def get_vexs(user_id: str) -> JSONResponse:
3434
vexs = await read_user_vexs(user_id)
3535
return JSONResponse(
@@ -38,6 +38,15 @@ async def get_vexs(user_id: str) -> JSONResponse:
3838
)
3939

4040

41+
@router.get("/vex/show/{vex_id}")
42+
async def get_vex(vex_id: str) -> JSONResponse:
43+
vex = await read_vex_by_id(vex_id)
44+
return JSONResponse(
45+
status_code=status.HTTP_200_OK,
46+
content=json_encoder(vex),
47+
)
48+
49+
4150
@router.get("/vex/download/{vex_id}")
4251
async def download_vex(vex_id: str) -> FileResponse:
4352
vex = await read_vex_by_id(vex_id)

frontend/src/routes/routes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LoginPage } from '../auth/login'
77
import { SignUpPage } from '../auth/signup'
88
import { ProtectedRoute } from '../auth/protectedRoute'
99
import { VEXsPage } from '../vexsPage/vexs'
10+
import { ShowVEXPage } from '../vexsPage/showVex/vex'
1011
import Sidebar, { SidebarItem } from '../components/sidebar'
1112
import PageNotFound from '../errorPage/error'
1213

@@ -68,6 +69,10 @@ function Routes() {
6869
{
6970
path: '/vexgen',
7071
element: <VEXsPage />
72+
},
73+
{
74+
path: '/vex/:id',
75+
element: <ShowVEXPage />
7176
}
7277
]
7378
}
@@ -100,6 +105,10 @@ function Routes() {
100105
{
101106
path: '/vexgen',
102107
element: <VEXsPage />
108+
},
109+
{
110+
path: '/vex/:id',
111+
element: <ShowVEXPage />
103112
}
104113
]
105114
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import React, { useState, useEffect } from 'react'
2+
import { styled } from '@mui/material/styles'
3+
import { useParams } from 'react-router'
4+
import PropTypes from 'prop-types'
5+
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'
6+
import MuiAccordion from '@mui/material/Accordion'
7+
import MuiAccordionSummary from '@mui/material/AccordionSummary'
8+
import MuiAccordionDetails from '@mui/material/AccordionDetails'
9+
import Typography from '@mui/material/Typography'
10+
11+
const ReadMore = ({ id, text, amountOfWords = 36 }) => {
12+
const [isExpanded, setIsExpanded] = useState(false)
13+
const splittedText = text.split(' ')
14+
const itCanOverflow = splittedText.length > amountOfWords
15+
const beginText = itCanOverflow
16+
? splittedText.slice(0, amountOfWords - 1).join(' ')
17+
: text
18+
const endText = splittedText.slice(amountOfWords - 1).join(' ')
19+
20+
const handleKeyboard = (e) => {
21+
if (e.code === 'Space' || e.code === 'Enter') {
22+
setIsExpanded(!isExpanded)
23+
}
24+
}
25+
26+
return (
27+
<span id={id}>
28+
{beginText}
29+
{itCanOverflow && (
30+
<>
31+
{!isExpanded && <span>... </span>}
32+
<span
33+
className={`${!isExpanded && 'hidden'}`}
34+
aria-hidden={!isExpanded}
35+
>
36+
{endText}
37+
</span>
38+
<span
39+
className='text-violet-400 ml-2'
40+
role="button"
41+
tabIndex={0}
42+
aria-expanded={isExpanded}
43+
aria-controls={id}
44+
onKeyDown={handleKeyboard}
45+
onClick={() => setIsExpanded(!isExpanded)}
46+
>
47+
{isExpanded ? 'show less' : 'show more'}
48+
</span>
49+
</>
50+
)}
51+
</span>
52+
)
53+
}
54+
55+
ReadMore.propTypes = {
56+
id: PropTypes.string,
57+
text: PropTypes.string,
58+
amountOfWords: PropTypes.number
59+
}
60+
61+
const Accordion = styled((props) => (
62+
<MuiAccordion disableGutters elevation={0} square {...props} />
63+
))(({ theme }) => ({
64+
border: `1px solid ${theme.palette.divider}`,
65+
'&:not(:last-child)': {
66+
borderBottom: 0,
67+
},
68+
'&::before': {
69+
display: 'none',
70+
},
71+
}));
72+
73+
const AccordionSummary = styled((props) => (
74+
<MuiAccordionSummary
75+
expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />}
76+
{...props}
77+
/>
78+
))(({ theme }) => ({
79+
backgroundColor:
80+
theme.palette.mode === 'dark'
81+
? 'rgba(255, 255, 255, .05)'
82+
: 'rgba(0, 0, 0, .03)',
83+
flexDirection: 'row-reverse',
84+
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
85+
transform: 'rotate(90deg)',
86+
},
87+
'& .MuiAccordionSummary-content': {
88+
marginLeft: theme.spacing(1),
89+
},
90+
}));
91+
92+
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
93+
padding: theme.spacing(2),
94+
borderTop: '1px solid rgba(0, 0, 0, .125)',
95+
}));
96+
97+
98+
const ShowVEXPage = () => {
99+
const params = useParams()
100+
const [vex, set_vex] = useState([])
101+
const [statements, set_statments] = useState([])
102+
const [expanded, setExpanded] = useState('');
103+
104+
const handleChange = (panel) => (event, newExpanded) => {
105+
setExpanded(newExpanded ? panel : false);
106+
};
107+
108+
useEffect(() => {
109+
const access_token = localStorage.getItem('access_token')
110+
fetch('http://localhost:8000/vex/show/' + params.id, {
111+
method: 'GET',
112+
headers: {
113+
'Content-Type': 'application/json',
114+
Authorization: `Bearer ${access_token}`
115+
}
116+
})
117+
.then((r) => r.json())
118+
.then((r) => {
119+
set_vex(r)
120+
set_statments(r.vex.statements)
121+
})
122+
}, [params.id])
123+
124+
return (
125+
<div className='flex flex-col h-screen justify-center items-center m-auto w-8/12'>
126+
<p className='mb-6 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 xl:px-48 dark:text-gray-400 text-center'>
127+
{vex.owner}/{vex.name}: {vex.sbom_path}
128+
</p>
129+
{statements.map((statement, index) => (
130+
<Accordion className='w-full' key={index} expanded={expanded === 'panel'+index} onChange={handleChange('panel'+index)}>
131+
<AccordionSummary aria-controls="panel1d-content" id="panel1d-header">
132+
<Typography>Statement {index}</Typography>
133+
</AccordionSummary>
134+
<AccordionDetails>
135+
<Typography>
136+
<span>- Vulnerability:<br /></span>
137+
<span>&ensp;&ensp;&ensp;{'\u2022'} @id: {statement.vulnerability["@id"]}<br /></span>
138+
<span>&ensp;&ensp;&ensp;{'\u2022'} name: {statement.vulnerability.name}<br /></span>
139+
<span>&ensp;&ensp;&ensp;{'\u2022'} description: <ReadMore id="read-more-text" text={statement.vulnerability.description} /><br /></span>
140+
<span>- Timestamp: {statement.timestamp}<br /></span>
141+
<span>- Last Updated: {statement.last_updated}<br /></span>
142+
<span>- Status: {statement.status}<br /></span>
143+
<span>- Justification: {statement.justification}<br /></span>
144+
<span>- Supplier: {statement.supplier}</span>
145+
</Typography>
146+
</AccordionDetails>
147+
</Accordion>
148+
))}
149+
</div>
150+
)
151+
}
152+
153+
export { ShowVEXPage }

frontend/src/vexsPage/vexs.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { useState, useEffect } from 'react'
2+
import { useNavigate } from "react-router-dom"
23
import PropTypes from 'prop-types'
34
import { Eye, ArrowBigDownDash } from 'lucide-react'
4-
import { useTheme } from '@mui/material/styles';
5+
import { useTheme } from '@mui/material/styles'
56
import Box from '@mui/material/Box'
67
import Button from '@mui/material/Button'
78
import MenuItem from '@mui/material/MenuItem'
@@ -94,7 +95,7 @@ const VEXsPage = () => {
9495
const [vexs, set_vexs] = useState([])
9596
const [page, setPage] = React.useState(0);
9697
const [rowsPerPage, setRowsPerPage] = React.useState(5);
97-
98+
const navigate = useNavigate()
9899
const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - vexs.length) : 0;
99100

100101
const handleChangePage = (event, newPage) => {
@@ -110,7 +111,7 @@ const VEXsPage = () => {
110111
const access_token = localStorage.getItem('access_token')
111112
const user_id = localStorage.getItem('user_id')
112113
const fetch_vexs= () => {
113-
fetch('http://localhost:8000/vex/' + user_id, {
114+
fetch('http://localhost:8000/vex/user/' + user_id, {
114115
method: 'GET',
115116
headers: {
116117
'Content-Type': 'application/json',
@@ -201,6 +202,10 @@ const VEXsPage = () => {
201202
})
202203
}
203204

205+
const show_vex = (vex_id) => {
206+
navigate('/vex/' + vex_id)
207+
}
208+
204209
return (
205210
<div className='flex flex-col h-screen justify-center items-center m-auto'>
206211
<p className='mb-6 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 xl:px-48 dark:text-gray-400 text-center'>
@@ -277,7 +282,7 @@ const VEXsPage = () => {
277282
<TableCell align="center">{vex.owner}</TableCell>
278283
<TableCell align="center">{vex.name}</TableCell>
279284
<TableCell align="center">{vex.sbom_path}</TableCell>
280-
<TableCell align="center"><Button size="small" variant="contained" onClick={() => download_vex(vex._id)}><Eye /></Button></TableCell>
285+
<TableCell align="center"><Button size="small" variant="contained" onClick={() => show_vex(vex._id)}><Eye /></Button></TableCell>
281286
<TableCell align="center"><Button size="small" variant="contained" onClick={() => download_vex(vex._id)}><ArrowBigDownDash /></Button></TableCell>
282287
</TableRow>
283288
))}

0 commit comments

Comments
 (0)