Spaces:
Runtime error
Runtime error
import { t } from 'i18next'; | |
import { useCallback, useEffect, useState } from 'react'; | |
import { useNavigate } from 'react-router-dom'; | |
import { toast, Toaster } from 'sonner'; | |
import Card from '../../components/card/Card'; | |
import Modal from '../../components/modal/Modal'; | |
import UITable from '../../components/table/UITable'; | |
import { useSortableTable } from '../../hooks/useSortableTable'; | |
import { useTableFiltering } from '../../hooks/useTableFiltering'; | |
import { | |
deleteVulnerability, | |
getCategories, | |
getLanguages, | |
getTypes, | |
getVulnerabilities, | |
} from '../../services/vulnerabilities'; | |
import AddVulnerability from './add/addVulnerability'; | |
import VulnerabilityButtons from './components/vulnerabilityButtons'; | |
import EditVulnerability from './edit/editVulnerability'; | |
import MergeVulnerabilities from './merge/mergeVulnerabilities'; | |
type Details = { | |
locale: string; | |
title?: string; | |
vulnType?: string; | |
description?: string; | |
observation?: string; | |
remediation?: string; | |
cwes: string[]; | |
references: string[]; | |
customFields: string[]; | |
}; | |
type VulnerabilityData = { | |
_id: string; | |
cvssv3: string | null; | |
priority?: number | ''; | |
remediationComplexity?: number | ''; | |
details: Details[]; | |
status?: number; | |
category?: string | null; | |
__v: number; | |
createdAt?: string; | |
updatedAt?: string; | |
}; | |
type LanguageData = { | |
language: string; | |
locale: string; | |
}; | |
type CategoryData = { | |
_id: string; | |
name: string; | |
sortValue: string; | |
sortOrder: string; | |
sortAuto: boolean; | |
}; | |
type TypeData = { | |
name: string; | |
locale: string; | |
}; | |
type ListItem = { | |
id: number; | |
value: string; | |
label?: string; | |
locale?: string; | |
}; | |
type ListItemCategory = { | |
id: number; | |
value: string; | |
label?: string; | |
isNull?: boolean; | |
hrEnabled?: boolean; | |
onClick: (item: ListItem) => void; | |
}; | |
type TableData = { | |
_id: string; | |
title: string; | |
category?: string; | |
type?: string; | |
}; | |
export const Vulnerabilities = () => { | |
const navigate = useNavigate(); | |
const [vulnerabilities, setVulnerabilities] = useState<VulnerabilityData[]>( | |
[], | |
); | |
const [tableInfo, setTableInfo] = useState<TableData[]>([]); | |
const [languagesList, setLanguagesList] = useState<ListItem[]>([]); | |
const [currentLanguage, setCurrentLanguage] = useState<ListItem | null>(null); | |
const [loadingLanguage, setLoadingLanguage] = useState<boolean>(true); | |
const [categoriesList, setCategoriesList] = useState<ListItemCategory[]>([]); | |
const [typesList, setTypesList] = useState<ListItem[]>([]); | |
const [isOpenAddVuln, setIsOpenAddVuln] = useState(false); | |
const openAddVulnSlidingPage = () => setIsOpenAddVuln(true); | |
const [isOpenEditVuln, setIsOpenEditVuln] = useState<boolean>(false); | |
const [editVuln, setEditVuln] = useState<VulnerabilityData>(); | |
const [selectedCategoryAddVuln, setSelectedCategoryAddVuln] = | |
useState<ListItem | null>(null); | |
const handleSuccessToast = (message: string) => { | |
toast.success(message); | |
}; | |
const [openModalDeleteVuln, setOpenModalDeleteVuln] = useState(false); | |
const columns = [ | |
{ header: t('title'), accessor: 'title', sortable: true, filterable: true }, | |
{ | |
header: t('category'), | |
accessor: 'category', | |
sortable: true, | |
filterable: true, | |
}, | |
{ header: t('type'), accessor: 'type', sortable: true, filterable: true }, | |
]; | |
const [tableData, handleSorting, setTableData] = useSortableTable<TableData>( | |
tableInfo, | |
columns, | |
); | |
const fetchVulnerabilities = useCallback(async () => { | |
try { | |
const dataVulnerability = await getVulnerabilities(); | |
setVulnerabilities(dataVulnerability.datas); | |
const vulnDataTable = dataVulnerability.datas.map( | |
(item: VulnerabilityData) => ({ | |
_id: item._id, | |
title: item.details[0].title ?? '', | |
category: item.category ?? t('noCategory'), | |
type: item.details[0].vulnType ?? t('undefined'), | |
}), | |
); | |
setTableData(vulnDataTable); | |
setTableInfo(vulnDataTable); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
}, [setTableData]); | |
const fetchLanguages = async () => { | |
try { | |
const dataLanguage = await getLanguages(); | |
const languageNames = dataLanguage.datas.map( | |
(item: LanguageData, index: number) => ({ | |
id: index, | |
value: item.locale, | |
label: item.language, | |
}), | |
); | |
setLanguagesList(languageNames); | |
setCurrentLanguage(languageNames[0]); | |
setLoadingLanguage(false); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
}; | |
const fetchTypes = async () => { | |
try { | |
const dataType = await getTypes(); | |
const typeNames = dataType.datas.map((item: TypeData, index: number) => ({ | |
id: index + 1, | |
value: item.name, | |
label: item.name, | |
locale: item.locale, | |
})); | |
setTypesList([...typeNames]); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
}; | |
const fetchCategories = useCallback(async () => { | |
try { | |
const dataCategory = await getCategories(); | |
const categoryNames = dataCategory.datas.map( | |
(item: CategoryData, index: number) => ({ | |
id: index + 1, | |
value: item.name, | |
label: item.name, | |
onClick: (item2: ListItem) => { | |
setSelectedCategoryAddVuln(item2); | |
openAddVulnSlidingPage(); | |
}, | |
}), | |
); | |
setCategoriesList([ | |
{ | |
id: 0, | |
label: t('noCategory'), | |
value: '', | |
isNull: true, | |
hrEnabled: true, | |
onClick: (item2: ListItem) => { | |
setSelectedCategoryAddVuln(item2); | |
openAddVulnSlidingPage(); | |
}, | |
}, | |
...categoryNames, | |
]); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
}, []); | |
useEffect(() => { | |
void fetchVulnerabilities(); | |
void fetchLanguages(); | |
void fetchTypes(); | |
void fetchCategories(); | |
}, [fetchCategories, fetchVulnerabilities]); | |
// | |
//// Workaround para dropdown de filtrado, al parecer funciona bien btw | |
useEffect(() => { | |
const filtered = vulnerabilities | |
.filter(vulnIter => | |
vulnIter.details.some( | |
detail => detail.locale === currentLanguage?.value, | |
), | |
) | |
.map(vulnIter => { | |
const index = vulnIter.details.findIndex( | |
detail => detail.locale === currentLanguage?.value, | |
); | |
return { ...vulnIter, matchingDetailIndex: index }; | |
}); | |
const vulnDataTable: TableData[] = filtered.map(item => ({ | |
_id: item._id, | |
title: item.details[item.matchingDetailIndex].title ?? 'noTitle', | |
category: item.category ?? t('noCategory'), | |
type: item.details[item.matchingDetailIndex].vulnType ?? t('undefined'), | |
})); | |
setTableData(vulnDataTable); | |
setTableInfo(vulnDataTable); | |
}, [vulnerabilities, currentLanguage, setTableData]); | |
const [isOpenMerge, setIsOpenMerge] = useState(false); | |
const [itemDelete, setItemDelete] = useState<TableData>(); | |
const confirmDeleteVulnerability = async () => { | |
try { | |
const response = await deleteVulnerability(itemDelete?._id ?? ''); | |
if (response.status === 'success') { | |
handleSuccessToast(t('msg.vulnerabilityDeletedOk')); | |
} | |
} catch (error) { | |
toast.error(t('err.failedDeleteVulnerability')); | |
console.error('Error:', error); | |
} | |
setOpenModalDeleteVuln(false); | |
void fetchVulnerabilities(); | |
}; | |
const deleteRegisterConfirmation = (item: TableData) => { | |
setItemDelete(item); | |
setOpenModalDeleteVuln(true); | |
}; | |
const editRegister = (item: TableData) => { | |
const vuln = vulnerabilities.find(vuln => vuln._id === item._id); | |
setEditVuln(vuln); | |
setIsOpenEditVuln(true); | |
}; | |
const findRegister = (item: TableData) => { | |
const sanitizedQueryParam = encodeURIComponent(item.title.trim()); | |
navigate(`/audits?findingTitle=${sanitizedQueryParam}`); | |
}; | |
const keyExtractor = (item: TableData) => item._id; | |
const rowActions = [ | |
{ | |
label: 'Edit', | |
onClick: (item: TableData) => editRegister(item), | |
}, | |
{ | |
label: 'FindAudit', | |
onClick: (item: TableData) => findRegister(item), | |
}, | |
{ | |
label: 'Delete', | |
onClick: (item: TableData) => deleteRegisterConfirmation(item), | |
}, | |
]; | |
const [filters, handleFilterChange] = useTableFiltering<TableData>( | |
tableInfo, | |
columns, | |
setTableData, | |
); | |
return ( | |
<div className="max-w-screen-2xl mx-auto my-6"> | |
<div className="fixed z-20"> | |
<Modal | |
cancelText={t('btn.stay')} | |
disablehr | |
isOpen={openModalDeleteVuln} | |
onCancel={() => setOpenModalDeleteVuln(false)} | |
onSubmit={confirmDeleteVulnerability} | |
submitText={t('btn.confirm')} | |
title={t('msg.confirmSuppression')} | |
> | |
<span className="ml-3">{t('msg.vulnerabilityWillBeDeleted')}</span> | |
</Modal> | |
</div> | |
<Toaster /> | |
<Card title={t('nav.vulnerabilities')}> | |
<> | |
<div className="fixed z-20"> | |
{isOpenAddVuln ? ( | |
<AddVulnerability | |
categoryVuln={selectedCategoryAddVuln} | |
currentLang={currentLanguage} | |
handleOnSuccess={handleSuccessToast} | |
handlerIsOpen={setIsOpenAddVuln} | |
isOpen={isOpenAddVuln} | |
languages={languagesList} | |
refreshVulns={fetchVulnerabilities} | |
types={typesList} | |
/> | |
) : null} | |
</div> | |
<div className="fixed z-20"> | |
{isOpenEditVuln && editVuln ? ( | |
<EditVulnerability | |
categories={categoriesList} | |
currentVuln={editVuln} | |
handleOnSuccess={handleSuccessToast} | |
handlerIsOpen={setIsOpenEditVuln} | |
isOpen={isOpenEditVuln} | |
languages={languagesList} | |
refreshVulns={fetchVulnerabilities} | |
types={typesList} | |
/> | |
) : null} | |
</div> | |
<div className="fixed z-20"> | |
{isOpenMerge ? ( | |
<MergeVulnerabilities | |
handleOnSuccess={handleSuccessToast} | |
handlerIsOpen={setIsOpenMerge} | |
isOpen={isOpenMerge} | |
languages={languagesList} | |
refreshVulns={fetchVulnerabilities} | |
vulnerabilities={vulnerabilities} | |
/> | |
) : null} | |
</div> | |
<UITable | |
columns={columns} | |
data={tableData} | |
emptyState={<div>{t('err.noMatchingRecords')}</div>} | |
filters={filters} | |
keyExtractor={keyExtractor} | |
onFilter={handleFilterChange} | |
onSort={handleSorting} | |
rowActions={rowActions} | |
> | |
<div className="mb-4 w-full"> | |
<VulnerabilityButtons | |
categoriesList={categoriesList} | |
currentLanguage={currentLanguage} | |
languagesList={languagesList} | |
loadingLanguage={loadingLanguage} | |
setCurrentLanguage={setCurrentLanguage} | |
setOpenMerge={setIsOpenMerge} | |
/> | |
</div> | |
</UITable> | |
</> | |
</Card> | |
</div> | |
); | |
}; | |