diff --git a/frontend/src/i18n/en-US/index.ts b/frontend/src/i18n/en-US/index.ts index 63e799e8..5b205af7 100644 --- a/frontend/src/i18n/en-US/index.ts +++ b/frontend/src/i18n/en-US/index.ts @@ -162,6 +162,8 @@ export default { passwordsDoNotMatch: 'Passwords do not match', auditCustomSectionsSaveSuccess: 'The custom sections have been saved successfully', + vulnerabilityTypeCreatedOk: 'Vulnerability Type Created Successfully!', + vulnerabilityTypesUpdatedOk: 'Vulnerability Types Updated Successfully!', }, err: { notDefinedLanguage: 'Not defined for this language', @@ -202,6 +204,9 @@ export default { passwordsDontMatch: 'Passwords do not match', errorGeneratingPdf: 'Failed exporting audit to pdf', errorSavingAuditCustomSections: 'Failed saving the audit custom sections', + failedCreatingVulnerabilityType: 'Failed to create vulnerability type', + failedUpdatingVulnerabilityTypes: 'Failed to update vulnerability types', + vulnerabilityTypeAlreadyExists: 'Vulnerability Type already exists', }, cvss: { title: 'CVSS v3.1 Base Score', @@ -733,4 +738,5 @@ export default { recommendCVSS: 'Recommend CVSS', cleanRecommendations: 'Clean Recommendations', errorRecommendingCVSS: 'Error generating recommendation', + vulnerabilityType: 'Vulnerability Type', }; diff --git a/frontend/src/routes/data/CustomData/VulnerabilityTypeList.tsx b/frontend/src/routes/data/CustomData/VulnerabilityTypeList.tsx index 3a371b0c..f67eb486 100644 --- a/frontend/src/routes/data/CustomData/VulnerabilityTypeList.tsx +++ b/frontend/src/routes/data/CustomData/VulnerabilityTypeList.tsx @@ -1,4 +1,5 @@ import { Bars2Icon } from '@heroicons/react/24/outline'; +import { t } from 'i18next'; import React, { useEffect, useState } from 'react'; import PrimaryButton from '../../../components/button/PrimaryButton'; @@ -58,7 +59,7 @@ const VulnerabilityTypeList: React.FC = ({ id="name" name="name" onChange={e => handleInputChange(row.id, e)} - placeholder="Vulnerability Type" + placeholder={t('vulnerabilityType')} type="text" value={row.name} /> @@ -78,13 +79,10 @@ const VulnerabilityTypeList: React.FC = ({ }, [data]); useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const updatedData = rows.map(({ id, ...rest }) => rest); - const dataHasChanged = JSON.stringify(data) !== JSON.stringify(updatedData); - - if (dataHasChanged) { - onUpdateList(updatedData); - } - }, [onUpdateList, rows, data]); + onUpdateList(updatedData); + }, [onUpdateList, rows]); return (
diff --git a/frontend/src/routes/data/CustomData/VulnerabilityTypes.tsx b/frontend/src/routes/data/CustomData/VulnerabilityTypes.tsx index d82ec647..7fa8f09d 100644 --- a/frontend/src/routes/data/CustomData/VulnerabilityTypes.tsx +++ b/frontend/src/routes/data/CustomData/VulnerabilityTypes.tsx @@ -1,3 +1,4 @@ +import { ArrowPathIcon } from '@heroicons/react/24/outline'; import { t } from 'i18next'; import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; @@ -22,17 +23,22 @@ type ListItem = { label?: string; }; +type VulnerabilityType = { + name: string; + locale: string; +}; + export const VulnerabilityTypes = () => { const [languages, setLanguages] = useState([]); const [currentLanguage, setCurrentLanguage] = useState(null); const [loadingLanguages, setLoadingLanguages] = useState(true); const [vulnerabilityTypes, setVulnerabilityTypes] = useState< - { name: string; locale: string }[] + VulnerabilityType[] >([]); const [filteredVulnerabilityTypes, setFilteredVulnerabilityTypes] = useState< - { name: string; locale: string }[] + VulnerabilityType[] >([]); const [newVulnerabilityType, setNewVulnerabilityType] = useState(''); @@ -40,87 +46,152 @@ export const VulnerabilityTypes = () => { const [isEditing, setIsEditing] = useState(false); const [loadingVulnerabilityTypes, setLoadingVulnerabilityTypes] = useState(true); - const [error, setError] = useState(null); - useEffect(() => { - const fetchData = async () => { - setLoadingLanguages(true); - setLoadingVulnerabilityTypes(true); + const fetchLanguages = useCallback(async () => { + setLoadingLanguages(true); + + try { + const dataLanguage = await getLanguages(); + const languagesConverted = dataLanguage.datas.map( + (item: Language, index: number) => ({ + id: index, + value: item.locale, + label: item.language, + }), + ); + setLanguages(languagesConverted); + + if (languagesConverted.length > 0) { + setCurrentLanguage(languagesConverted[0]); + setLoadingLanguages(false); + return languagesConverted[0]; + } + } catch (err) { + console.error(err); + } + setLoadingLanguages(false); + return null; + }, []); + const fetchVulnerabilityTypes = useCallback( + async (languageSelected: ListItem | null) => { try { - const dataLanguage = await getLanguages(); - const languages = dataLanguage.datas.map( - (item: Language, index: number) => ({ - id: index, - value: item.locale, - label: item.language, + const dataVulnerabilityType = await getVulnerabilityTypes(); + const types = dataVulnerabilityType.datas.map( + (item: VulnerabilityType) => ({ + name: item.name, + locale: item.locale, }), ); - setLanguages(languages); - - if (!currentLanguage && languages.length > 0) { - setCurrentLanguage(languages[0]); - } - - const dataVulnerabilityType = await getVulnerabilityTypes(); - setVulnerabilityTypes(dataVulnerabilityType.datas); + setVulnerabilityTypes(types); setFilteredVulnerabilityTypes( - dataVulnerabilityType.datas.filter( + types.filter( (type: { locale: string }) => - type.locale === currentLanguage?.value, + type.locale === languageSelected?.value, ), ); } catch (err) { - setError('Error fetching data'); - } finally { - setLoadingLanguages(false); - setLoadingVulnerabilityTypes(false); + console.error(err); } - }; - void fetchData(); - }, [currentLanguage]); + }, + [], + ); + + const fetchVulnTypesAndLanguages = useCallback(async () => { + try { + const languageSelected = await fetchLanguages(); + setLoadingVulnerabilityTypes(true); + await fetchVulnerabilityTypes(languageSelected); + setLoadingVulnerabilityTypes(false); + } catch (error) { + console.error(error); + } + }, [fetchLanguages, fetchVulnerabilityTypes]); + + useEffect(() => { + void fetchVulnTypesAndLanguages(); + }, [fetchVulnTypesAndLanguages]); const handleAddVulnerabilityType = async () => { if (!newVulnerabilityType.trim()) { - setError(`${t('err.createEmptyField')}: ${t('name')}`); toast.error(`${t('err.createEmptyField')}: ${t('name')}`); return; } try { - await createVulnerabilityType({ - locale: currentLanguage.value, + const addVulnType = await createVulnerabilityType({ + locale: currentLanguage?.value ?? '', name: newVulnerabilityType, }); + if (addVulnType.status === 'success') { + await fetchVulnerabilityTypes(currentLanguage); + + toast.success(t('msg.vulnerabilityTypeCreatedOk')); + setNewVulnerabilityType(''); + } } catch (error) { - setError('Error creating vulnerability type'); - toast.error('Error creating vulnerability type'); + if ( + error instanceof Error && + error.message === 'Vulnerability Type already exists' + ) { + toast.error(t('err.vulnerabilityTypeAlreadyExists')); + } else { + toast.error(t('err.failedCreatingVulnerabilityType')); + } + console.error('Error:', error); return; } - toast.success('Vulnerability Type Created Successfully!'); - setNewVulnerabilityType(''); }; + const onChangeLanguage = (value: ListItem) => { + setFilteredVulnerabilityTypes( + vulnerabilityTypes.filter(type => type.locale === value.value), + ); + setCurrentLanguage(value); + }; + + const [newVulnerabilityTypeList, setNewVulnerabilityTypeList] = useState< + VulnerabilityType[] + >([]); + + const handleUpdateVulnerabilityType = useCallback( + (data: VulnerabilityType[]) => { + setNewVulnerabilityTypeList(data); + }, + [setNewVulnerabilityTypeList], + ); + const onClickSave = async () => { try { - await updateVulnerabilityTypes(vulnerabilityTypes); - toast.success('Vulnerability Type Updated Successfully!'); + const differentLanguageTypes = vulnerabilityTypes.filter( + type => type.locale !== currentLanguage?.value, + ); + + const vulnTypeMerged = [ + ...newVulnerabilityTypeList, + ...differentLanguageTypes, + ]; + + await updateVulnerabilityTypes(vulnTypeMerged); + await fetchVulnerabilityTypes(currentLanguage); + toast.success(t('msg.vulnerabilityTypesUpdatedOk')); setIsEditing(false); } catch (error) { - setError('Error updating vulnerability types'); - toast.error('Error updating vulnerability types'); + console.error(error); + + toast.error(t('err.failedUpdatingVulnerabilityTypes')); setIsEditing(false); return; } }; - const handleUpdateVulnerabilityType = useCallback( - (data: { name: string; locale: string }[]) => { - setVulnerabilityTypes(data); - }, - [setVulnerabilityTypes], - ); + const onClickCancel = () => { + setFilteredVulnerabilityTypes( + vulnerabilityTypes.filter(type => type.locale === currentLanguage?.value), + ); + setIsEditing(false); + }; return (
@@ -129,7 +200,7 @@ export const VulnerabilityTypes = () => { {!loadingLanguages ? ( onChangeLanguage(value)} selected={currentLanguage} title={t('language')} /> @@ -153,13 +224,17 @@ export const VulnerabilityTypes = () => { setIsEditing(false)} + onClickCancel={onClickCancel} onClickEdit={() => setIsEditing(true)} onClickSave={onClickSave} title={t('editVulnerabilityTypes')} > {loadingVulnerabilityTypes ? ( -

{t('loading')}

+
+ + + +
) : (