import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline'; import { t } from 'i18next'; import { useEffect, useState } from 'react'; import { toast, Toaster } from 'sonner'; import PrimaryButton from '../../../components/button/PrimaryButton'; import SelectDropdown from '../../../components/dropdown/SelectDropdown'; import SimpleInput from '../../../components/input/SimpleInput'; import Modal from '../../../components/modal/Modal'; import RichText from '../../../components/text/RichText'; import TextArea from '../../../components/text/TextArea'; import GetCWENameByLanguage from '../../../services/getCWEName'; import { postDescriptionCWE, postVulnerability, } from '../../../services/vulnerabilities'; import CVSSCalculator from '../components/CVSSCalculator'; import MultiCheckboxButton from '../components/MultiCheckboxButton'; type CWEData = { priority: number; label: string; score: number; }; type CWERelated = { cwe: string; cweParent?: string; cweGrandParent?: string; }; type Details = { locale: string; title: string; vulnType: string; description: string; observation: string; remediation: string; cwes: string[]; references: string[]; customFields: string[]; }; type AddVulnerabilityData = { priority: number | ''; remediationComplexity: number | ''; details: Details[]; category: string | null; }; type ListItem = { id: number; value: string; label?: string; locale?: string; }; type AddVulnerabilityProps = { isOpen: boolean; handlerIsOpen: React.Dispatch>; categoryVuln: ListItem | null; currentLang: ListItem | null; languages: ListItem[]; types: ListItem[]; refreshVulns: () => void; handleOnSuccess: (message: string) => void; }; type PostDescription = { vuln: string; }; const AddVulnerability: React.FC = ({ isOpen, handlerIsOpen, categoryVuln, currentLang, languages, types, refreshVulns, handleOnSuccess, }) => { const [openModal, setOpenModal] = useState(false); const [changed, setChanged] = useState(false); const [language, setLanguage] = useState( currentLang ?? languages[0], ); const [selectedType, setSelectedType] = useState(null); const [typeListFiltered, setTypeListFiltered] = useState( types.filter(typeIter => typeIter.locale === language.value), ); const [complexity, setComplexity] = useState(null); const [priority, setPriority] = useState(null); const [cweLoading, setCweLoading] = useState(false); const [cweRecommendationSelected, setCweRecommendationSelected] = useState< string[] >([]); const [cweRecommended, setCweRecommended] = useState([]); const [cvssVector, setCvssVector] = useState(''); const [titleRequiredAlert, setTitleRequiredAlert] = useState(false); const complexityOptions = [ { id: 1, value: t('easy') }, { id: 2, value: t('medium') }, { id: 3, value: t('complex') }, ]; const priorityOptions = [ { id: 1, value: t('low') }, { id: 2, value: t('medium') }, { id: 3, value: t('high') }, { id: 4, value: t('urgent') }, ]; const [data, setData] = useState({ remediationComplexity: '', details: [], category: categoryVuln ? categoryVuln.value : '', priority: '', }); const [details, setDetails] = useState([]); const [detail, setDetail] = useState
({ locale: language.value, title: '', vulnType: '', description: '', observation: '', remediation: '', cwes: [], references: [], customFields: [], }); const closeSlidingPage = () => { setOpenModal(false); handlerIsOpen(false); }; const addNewDetail = (localeValue: string) => { const newDetail: Details = { locale: localeValue, title: '', vulnType: '', description: '', observation: '', remediation: '', cwes: [], references: [], customFields: [], }; setDetail(newDetail); }; const handleTypeChange = (item: ListItem) => { if (changed === false) { setChanged(true); } setDetail(prevDetail => ({ ...prevDetail, vulnType: item.value, })); setSelectedType(item); }; const handleLanguageChange = (item: ListItem) => { if (changed === false) { setChanged(true); } setCweRecommendationSelected([]); setCweRecommended([]); const findLangItem = details.findIndex( detailLocale => detailLocale.locale === item.value, ); if (findLangItem !== -1) { setDetail(details[findLangItem]); setSelectedType( types.find( typeIter => typeIter.locale === details[findLangItem].locale, ) ?? null, ); } else { addNewDetail(item.value); setSelectedType(null); } setLanguage(item); setTypeListFiltered( types.filter(typeIter => typeIter.locale === item.value), ); }; const handleDropdownChange = (name: string, item: ListItem) => { if (changed === false) { setChanged(true); } setData(prevData => ({ ...prevData, [name]: item.id, })); name === 'priority' ? setPriority(item) : setComplexity(item); }; const handleInputChange = (field: string, value: string) => { if (changed === false) { setChanged(true); } setDetail(prevDetail => ({ ...prevDetail, [field]: field === 'cwes' || field === 'references' ? value.split('\n') : value, })); }; const handleCWERecomendation = async () => { if (detail.description === '' || detail.description === '


') { toast.error(t('err.descriptionRequired')); // Cambiar el estado de required return; } const descriptionCWE: PostDescription = { vuln: detail.description, }; try { setCweLoading(true); setCweRecommendationSelected([]); const responseCWE = await postDescriptionCWE(descriptionCWE); const sortedResult = responseCWE.result.sort( (a: CWEData, b: CWEData) => b.score - a.score, ); const recommendedCWEs = GetCWENameByLanguage(detail.locale, sortedResult); setCweRecommended(recommendedCWEs); } catch (error) { console.error('Error:', error); toast.error(t('err.failedCWERecommendation')); } finally { setCweLoading(false); } }; const handleCvssRecomendation = () => { if (detail.description === '' || detail.description === '


') { toast.error(t('err.descriptionRequired')); return ''; } else { return detail.description; } }; const handlerSubmitCWE = () => { setDetail(prevDetail => ({ ...prevDetail, cwes: [...cweRecommendationSelected, ...prevDetail.cwes], })); setCweRecommendationSelected([]); setCweRecommended([]); }; useEffect(() => { const findLang = details.findIndex( detailLocale => detailLocale.locale === detail.locale, ); if (findLang !== -1) { const updatedDetails = [...details]; if (JSON.stringify(updatedDetails[findLang]) !== JSON.stringify(detail)) { updatedDetails[findLang] = { ...detail }; setDetails(updatedDetails); } } else { details.push(detail); } }, [detail, details]); const handleSubmitNewVulnerability = async () => { if (!details.some(detail => detail.title !== '')) { toast.error(t('err.titleRequired')); setTitleRequiredAlert(true); return; } const newVuln = { cvssv3: cvssVector !== 'CVSS:3.1/' ? cvssVector : '', remediationComplexity: data.remediationComplexity, details: [...details], category: data.category ?? null, priority: data.priority, }; try { const response = await postVulnerability([newVuln]); if (response.status === 'success') { handleOnSuccess(t('msg.vulnerabilityCreatedOk')); } } catch (error) { if ( error instanceof Error && error.message === 'Vulnerability title already exists' ) { toast.error(t('vulnerabilityTitleAlreadyExists')); } else { toast.error(t('err.failedUpdateVulnerability')); } console.error('Error:', error); return; } refreshVulns(); handlerIsOpen(!isOpen); }; return ( <>