diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 5f4e5672b..b878319fd 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -481,6 +481,8 @@ "direction": "Richtung", "download_bom": "BOM herunterladen", "download_component": "Komponenten herunterladen", + "edit": "Bearbeiten", + "edit_affected_component": "Bearbeiten Sie die betroffene Komponente", "email": "E-Mail", "empty_selection": "Keine Elemente ausgewählt", "endpoints": "Endpunkte", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 76dc3b0bf..efa8b29ab 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -481,6 +481,8 @@ "direction": "Direction", "download_bom": "Download BOM", "download_component": "Download Components", + "edit": "Edit", + "edit_affected_component": "Edit Affected Component", "email": "Email", "empty_selection": "No items selected", "endpoints": "Endpoints", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index aa6bf05d8..cd1a6b071 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -481,6 +481,8 @@ "direction": "Dirección", "download_bom": "Descargar lista de materiales", "download_component": "Descargar componentes", + "edit": "Editar", + "edit_affected_component": "Editar componente afectado", "email": "Correo electrónico", "empty_selection": "No hay elementos seleccionados", "endpoints": "Puntos finales", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 31828fbb7..92d5ce3a1 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -481,6 +481,8 @@ "direction": "Direction", "download_bom": "Télécharger la nomenclature", "download_component": "Télécharger les composants", + "edit": "Modifier", + "edit_affected_component": "Modifier le composant concerné", "email": "Courriel", "empty_selection": "Aucun élément sélectionné", "endpoints": "Points de terminaison", diff --git a/src/i18n/locales/hi.json b/src/i18n/locales/hi.json index 6101ffe26..70487ab93 100644 --- a/src/i18n/locales/hi.json +++ b/src/i18n/locales/hi.json @@ -481,6 +481,8 @@ "direction": "दिशा", "download_bom": "BOM डाउनलोड करें", "download_component": "घटक डाउनलोड करें", + "edit": "संपादन करना", + "edit_affected_component": "प्रभावित घटक संपादित करें", "email": "ईमेल", "empty_selection": "कोई आइटम चयनित नहीं", "endpoints": "अंतिमबिंदुओं", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 4ddebcf4c..ee254427a 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -481,6 +481,8 @@ "direction": "Direzione", "download_bom": "Scarica distinta base", "download_component": "Scarica Componenti", + "edit": "Modificare", + "edit_affected_component": "Modifica componente interessato", "email": "E-mail", "empty_selection": "Nessun elemento selezionato", "endpoints": "Endpoint", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 277a6dc28..87bca4b92 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -481,6 +481,8 @@ "direction": "方向", "download_bom": "BOMをダウンロード", "download_component": "コンポーネントをダウンロードする", + "edit": "編集", + "edit_affected_component": "影響を受けるコンポーネントの編集", "email": "Eメール", "empty_selection": "項目が選択されていません", "endpoints": "エンドポイント", diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json index 752a7795a..b11cc9047 100644 --- a/src/i18n/locales/pl.json +++ b/src/i18n/locales/pl.json @@ -481,6 +481,8 @@ "direction": "Kierunek", "download_bom": "Pobierz BOM", "download_component": "Pobierz komponenty", + "edit": "Redagować", + "edit_affected_component": "Edytuj komponent, którego dotyczy problem", "email": "E-mail", "empty_selection": "Nie wybrano żadnych elementów", "endpoints": "Punkty końcowe", diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json index d169881f4..53c8abfd8 100644 --- a/src/i18n/locales/pt-BR.json +++ b/src/i18n/locales/pt-BR.json @@ -481,6 +481,8 @@ "direction": "Direção", "download_bom": "Baixe a lista técnica", "download_component": "Baixar componentes", + "edit": "Editar", + "edit_affected_component": "Editar componente afetado", "email": "E-mail", "empty_selection": "Nenhum item selecionado", "endpoints": "Pontos finais", diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index 380ce0cf6..439ead554 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -481,6 +481,8 @@ "direction": "Direção", "download_bom": "Baixe a lista técnica", "download_component": "Baixar componentes", + "edit": "Editar", + "edit_affected_component": "Editar componente afetado", "email": "E-mail", "empty_selection": "Nenhum item selecionado", "endpoints": "Pontos finais", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 11f555ccf..5e642ffda 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -481,6 +481,8 @@ "direction": "Направление", "download_bom": "Скачать спецификацию", "download_component": "Загрузить компоненты", + "edit": "Редактировать", + "edit_affected_component": "Редактировать затронутый компонент", "email": "Электронная почта", "empty_selection": "Элементы не выбраны", "endpoints": "Конечные точки", diff --git a/src/i18n/locales/uk-UA.json b/src/i18n/locales/uk-UA.json index 53cf2918e..bbcfd19c6 100644 --- a/src/i18n/locales/uk-UA.json +++ b/src/i18n/locales/uk-UA.json @@ -481,6 +481,8 @@ "direction": "Напрямок", "download_bom": "Завантажити BOM", "download_component": "Завантажити компоненти", + "edit": "Редагувати", + "edit_affected_component": "Редагувати пошкоджений компонент", "email": "Електронна пошта", "empty_selection": "Елементи не вибрано", "endpoints": "Кінцеві точки", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index a7f07f64e..f1acf593b 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -481,6 +481,8 @@ "direction": "方向", "download_bom": "下载 BOM", "download_component": "下载组件", + "edit": "编辑", + "edit_affected_component": "编辑受影响的组件", "email": "电子邮件", "empty_selection": "没有选择任何项目", "endpoints": "终结点", diff --git a/src/views/portfolio/vulnerabilities/AddAffectedComponentModal.vue b/src/views/portfolio/vulnerabilities/AddAffectedComponentModal.vue index e053f4900..3941e36d0 100644 --- a/src/views/portfolio/vulnerabilities/AddAffectedComponentModal.vue +++ b/src/views/portfolio/vulnerabilities/AddAffectedComponentModal.vue @@ -4,7 +4,7 @@ @hide="resetValues()" size="lg" hide-header-close - :title="$t('message.add_affected_component')" + :title="title" > @@ -82,7 +82,7 @@ $t('message.cancel') }} {{ - $t('message.add') + okButton }} @@ -103,6 +103,8 @@ export default { }, data() { return { + okButton: this.$t('message.add'), + title: this.$t('message.add_affected_component'), affectedComponent: { identityType: 'PURL', identity: null, @@ -135,6 +137,13 @@ export default { ], }; }, + created() { + this.$root.$on('object-event', (data) => { + this.affectedComponent = data; + this.okButton = this.$t('message.edit'); + this.title = this.$t('message.edit_affected_component'); + }); + }, methods: { composeObject: function () { if (this.affectedComponent.versionType === 'RANGE') { @@ -194,6 +203,15 @@ export default { this.tempVersionEndRange = null; this.rangeBeginSyntax = null; this.rangeEndSyntax = null; + this.okButton = this.$t('message.add'); + this.title = this.$t('message.add_affected_component'); + }, + cancelEdit(cancel) { + this.resetValues(); + cancel(); + }, + beforeDestroy() { + this.$root.$off('object-event'); }, }, }; diff --git a/src/views/portfolio/vulnerabilities/VulnerabilityDetailsModal.vue b/src/views/portfolio/vulnerabilities/VulnerabilityDetailsModal.vue index ac58710dd..b89670721 100644 --- a/src/views/portfolio/vulnerabilities/VulnerabilityDetailsModal.vue +++ b/src/views/portfolio/vulnerabilities/VulnerabilityDetailsModal.vue @@ -995,6 +995,8 @@ export default { }, data() { return { + newAdded: 0, + canChange: null, primaryColor: getStyle('--primary'), recessedColor: getStyle('--recessed'), criticalColor: getStyle('--severity-critical'), @@ -1489,53 +1491,6 @@ export default { }, ], }, - columns: [ - { - title: this.$t('message.component'), - field: 'versionRange', - sortable: true, - }, - { - title: this.$t('message.reported_by'), - field: 'reportedBy', - sortable: true, - formatter(source) { - return common.formatSourceLabel(source); - }, - }, - { - title: this.$t('message.first_seen'), - field: 'firstSeenAt', - sortable: true, - visible: false, // Hide per default to not overload the table - formatter(timestamp) { - return typeof timestamp === 'number' - ? common.formatTimestamp(timestamp, true) - : '-'; - }, - }, - { - title: this.$t('message.last_seen'), - field: 'lastSeenAt', - sortable: true, - formatter(timestamp) { - return typeof timestamp === 'number' - ? common.formatTimestamp(timestamp, true) - : '-'; - }, - }, - ], - options: { - search: true, - showColumns: true, - showRefresh: false, - pagination: true, - silentSort: false, - sidePagination: 'client', - pageList: '[10, 25]', - pageSize: 10, - toolbar: '#vulnerableSoftwareToolbar', - }, }; }, watch: { @@ -1604,6 +1559,81 @@ export default { }, }, computed: { + columns() { + let columns = [ + { + title: this.$t('message.component'), + field: 'versionRange', + sortable: true, + }, + { + title: this.$t('message.reported_by'), + field: 'reportedBy', + sortable: true, + formatter(source) { + return common.formatSourceLabel(source); + }, + }, + { + title: this.$t('message.first_seen'), + field: 'firstSeenAt', + sortable: true, + visible: false, // Hide per default to not overload the table + formatter(timestamp) { + return typeof timestamp === 'number' + ? common.formatTimestamp(timestamp, true) + : '-'; + }, + }, + { + title: this.$t('message.last_seen'), + field: 'lastSeenAt', + sortable: true, + formatter(timestamp) { + return typeof timestamp === 'number' + ? common.formatTimestamp(timestamp, true) + : '-'; + }, + }, + ]; + this.canChange = + this.vulnerability.source === 'INTERNAL' && + this.isPermitted(this.PERMISSIONS.VULNERABILITY_MANAGEMENT); + if (this.canChange) { + columns.push({ + title: 'Actions', + field: 'actions', + visible: this.vulnerability.source === 'INTERNAL', + footerFormatter: this.actionFooter, + formatter: (value, row, index) => { + return ` + + + `; + }, + events: { + 'click .edit': this.handleEdit, + 'click .delete': this.handleDelete, + }, + }); + } + return columns; + }, + options() { + return { + search: true, + showColumns: true, + showRefresh: false, + showFooter: this.canChange, + pagination: true, + silentSort: false, + sidePagination: 'client', + pageList: '[10, 25]', + pageSize: 10, + toolbar: '#vulnerableSoftwareToolbar', + onPostBody: this.attachFooterEvent, + }; + }, computedCvssSeverity() { let severity = 'UNASSIGNED'; if (this.cvssV2Score && this.cvssV2Score.baseScore) { @@ -1659,6 +1689,28 @@ export default { }, }, methods: { + actionFooter() { + return ` + + `; + }, + attachFooterEvent() { + if (this.columns != null) { + try { + document + .getElementById('footer-button') + .addEventListener('click', this.openAddAffectedComponentModal); + } catch {} + } + }, + openAddAffectedComponentModal() { + this.$root.$emit('bv::show::modal', 'addAffectedComponentModal'); + }, resolveVulnAliases: function (aliases) { return common.resolveVulnAliases(this.vulnerability.source, aliases); }, @@ -1670,6 +1722,11 @@ export default { }, updateVulnerability: function () { let url = `${this.$api.BASE_URL}/${this.$api.URL_VULNERABILITY}`; + this.vulnerability.affectedComponents.forEach((affectedComponent) => { + affectedComponent.uuid <= this.newAdded + ? (affectedComponent.uuid = null) + : null; + }); this.axios .post(url, { uuid: this.vulnerability.uuid, @@ -2016,7 +2073,59 @@ export default { if (!this.vulnerability.affectedComponents) { this.vulnerability.affectedComponents = []; } - this.vulnerability.affectedComponents.push(affectedComponent); + let affectedComponentIndex = + this.vulnerability.affectedComponents.findIndex( + (obj) => obj.uuid === affectedComponent.uuid, + ); + if (affectedComponentIndex >= 0) { + this.vulnerability.affectedComponents[affectedComponentIndex] = + affectedComponent; + this.getAttributions(); + } else { + affectedComponent['affectedVersionAttributions'] = [{}]; + this.affectedVersionAttributions.push({ + uuid: this.newAdded, + reportedBy: 'INTERNAL', + firstSeenAt: '/', + lastSeenAt: '/', + versionRange: this.affectedComponentLabel(affectedComponent), + }); + affectedComponent['uuid'] = this.newAdded++; + this.vulnerability.affectedComponents.push(affectedComponent); + } + }, + handleDelete(event, value, row, index) { + let affectedComponentIndex = + this.vulnerability.affectedComponents.findIndex( + (obj) => obj.uuid === row.uuid, + ); + if (affectedComponentIndex < 0) { + return; + } + let affectedComponent = + this.vulnerability.affectedComponents[affectedComponentIndex]; + let attributionIndex = + affectedComponent.affectedVersionAttributions.findIndex( + (obj) => obj.uuid === row.attrUuid, + ); + if (attributionIndex < 0) { + return; + } + affectedComponent.affectedVersionAttributions.splice(attributionIndex, 1); + if (affectedComponent.affectedVersionAttributions.length == 0) { + this.vulnerability.affectedComponents.splice(affectedComponentIndex, 1); + } + this.affectedVersionAttributions.splice(index, 1); + }, + handleEdit(event, value, row, index) { + let affectedComponent = this.vulnerability.affectedComponents.find( + (obj) => obj.uuid === row.uuid, + ); + if (!affectedComponent) { + return; + } + this.$root.$emit('object-event', affectedComponent); + this.$root.$emit('bv::show::modal', 'addAffectedComponentModal'); }, resetValues: function () { this.cvssV2Score = { @@ -2086,6 +2195,8 @@ export default { ) { let attribution = affectedComponent.affectedVersionAttributions[j]; this.affectedVersionAttributions.push({ + uuid: affectedComponent.uuid, + attrUuid: attribution.uuid, versionRange: this.affectedComponentLabel(affectedComponent), reportedBy: attribution.source, firstSeenAt: attribution.firstSeen,