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, updateVulnerability, } from '../../../services/vulnerabilities'; import CVSSCalculator from '../components/CVSSCalculator'; import MultiCheckboxButton from '../components/MultiCheckboxButton'; import { useVulnState } from '../hooks/useVulnState'; type CWEData = { priority: number; label: string; score: number; }; 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 VulnerabilityDataProps = { priority?: number | ''; remediationComplexity?: number | ''; category?: string | null; }; type ListItem = { id: number; value: string; label?: string; locale?: string; }; type ListItemCategory = { id: number; value: string; label?: string; isNull?: boolean; }; type EditVulnerabilityProps = { isOpen: boolean; handlerIsOpen: React.Dispatch>; categories: ListItemCategory[]; languages: ListItem[]; types: ListItem[]; refreshVulns: () => void; currentVuln: VulnerabilityData; handleOnSuccess: (message: string) => void; }; type PostDescription = { vuln: string; }; const EditVulnerability: React.FC = ({ isOpen, handlerIsOpen, categories, languages, types, refreshVulns, currentVuln, handleOnSuccess, }) => { const { openModal, setOpenModal, changed, setChanged, categorySelected, setCategorySelected, categoryChanged, setCategoryChanged, selectedLanguage, setSelectedLanguage, selectedType, setSelectedType, typesFiltered, setTypesFiltered, cweLoading, setCweLoading, cweRecommendationSelected, setCweRecommendationSelected, cweRecommended, setCweRecommended, } = useVulnState({ currentVuln, languages, types }); 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 [complexity, setComplexity] = useState( currentVuln.remediationComplexity ? complexityOptions[currentVuln.remediationComplexity - 1] : null, ); const [priority, setPriority] = useState( currentVuln.priority ? priorityOptions[currentVuln.priority - 1] : null, ); const [data, setData] = useState({ remediationComplexity: currentVuln.remediationComplexity ?? '', category: currentVuln.category ?? null, priority: currentVuln.priority ?? '', }); const [details, setDetails] = useState( currentVuln.details.map(detailIter => ({ locale: detailIter.locale, title: detailIter.title ?? '', vulnType: detailIter.vulnType ?? '', description: detailIter.description ?? '', observation: detailIter.observation ?? '', remediation: detailIter.remediation ?? '', cwes: detailIter.cwes, references: detailIter.references, customFields: detailIter.customFields, })), ); const [detail, setDetail] = useState
(details[0]); const [cvssStringInitial, setCvssStringInitial] = useState( currentVuln.cvssv3 ?? '', ); const closeSlidingPage = () => { setOpenModal(false); handlerIsOpen(false); }; const checkChanged = () => { if (changed === false) { setChanged(true); } }; const addNewDetail = (localeValue: string) => { const newDetail: Details = { locale: localeValue, title: '', vulnType: '', description: '', observation: '', remediation: '', cwes: [], references: [], customFields: [], }; setDetail(newDetail); }; const handlerCategoryChange = (item: ListItem) => { setCategorySelected(item); setCategoryChanged(true); checkChanged(); }; const handleTypeChange = (item: ListItem) => { checkChanged(); setDetail(prevDetail => ({ ...prevDetail, vulnType: item.value, })); setSelectedType(item); }; const handleLanguageChange = (item: ListItem) => { checkChanged(); 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); } setSelectedLanguage(item); setTypesFiltered(types.filter(typeIter => typeIter.locale === item.value)); }; const handleDropdownChange = (name: string, item: ListItem) => { checkChanged(); setData(prevData => ({ ...prevData, [name]: item.id, })); name === 'priority' ? setPriority(item) : setComplexity(item); }; const handleInputChange = (field: string, value: string) => { checkChanged(); 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 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 updaterVulnData = () => { const filteredDetails: Details[] = details.map( ({ locale, cwes, references, customFields, ...rest }) => { const filteredRest = Object.fromEntries( Object.entries(rest).filter(([_, value]) => value !== ''), ); return { locale, ...filteredRest, cwes, references, customFields, }; }, ); const editvulnData: VulnerabilityData = { _id: currentVuln._id, __v: currentVuln.__v || 0, cvssv3: cvssStringInitial !== 'CVSS:3.1/' ? cvssStringInitial : null, status: currentVuln.status ?? 0, details: [...filteredDetails], ...(data.remediationComplexity && { remediationComplexity: data.remediationComplexity, }), ...(data.priority && { priority: data.priority, }), createdAt: currentVuln.createdAt ?? new Date().toISOString(), updatedAt: new Date().toISOString(), ...(categoryChanged ? categorySelected?.isNull === true ? { category: null } : { category: categorySelected?.value } : data.category ? { category: data.category } : {}), }; return editvulnData; }; const handleSubmitUpdateVulnerability = async () => { if (!details.some(detail => detail.title !== '')) { toast.error(t('err.titleRequired')); setTitleRequiredAlert(true); return; } const editvulnData = updaterVulnData(); try { const response = await updateVulnerability(editvulnData); if (response.status === 'success') { handleOnSuccess(t('msg.vulnerabilityUpdatedOk')); } } catch (error) { if ( error instanceof Error && error.message === 'Vulnerability title already exists' ) { toast.error(t('vulnerabilityTitleAlreadyExists')); } else { toast.error(t('failedCreateVulnerability')); } console.error('Error:', error); return; } refreshVulns(); handlerIsOpen(!isOpen); }; const handleCvssRecomendation = () => { if (detail.description === '' || detail.description === '


') { toast.error(t('err.descriptionRequired')); return ''; } else { return detail.description ?? ''; } }; return ( <>