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( [], ); const [tableInfo, setTableInfo] = useState([]); const [languagesList, setLanguagesList] = useState([]); const [currentLanguage, setCurrentLanguage] = useState(null); const [loadingLanguage, setLoadingLanguage] = useState(true); const [categoriesList, setCategoriesList] = useState([]); const [typesList, setTypesList] = useState([]); const [isOpenAddVuln, setIsOpenAddVuln] = useState(false); const openAddVulnSlidingPage = () => setIsOpenAddVuln(true); const [isOpenEditVuln, setIsOpenEditVuln] = useState(false); const [editVuln, setEditVuln] = useState(); const [selectedCategoryAddVuln, setSelectedCategoryAddVuln] = useState(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( 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(); 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( tableInfo, columns, setTableData, ); return (
setOpenModalDeleteVuln(false)} onSubmit={confirmDeleteVulnerability} submitText={t('btn.confirm')} title={t('msg.confirmSuppression')} > {t('msg.vulnerabilityWillBeDeleted')}
<>
{isOpenAddVuln ? ( ) : null}
{isOpenEditVuln && editVuln ? ( ) : null}
{isOpenMerge ? ( ) : null}
{t('err.noMatchingRecords')}
} filters={filters} keyExtractor={keyExtractor} onFilter={handleFilterChange} onSort={handleSorting} rowActions={rowActions} >
); };