diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 99fa50ab3..33ad8c1e0 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -362,6 +362,7 @@ "authenticated": "Authenticated", "authors": "Authors", "bom": "BOM", + "bom_deleted": "BOM successfully deleted", "bom_format": "BOM Format", "bom_uploaded": "BOM uploaded", "browse": "Browse", @@ -493,6 +494,7 @@ "data": "Data", "dates": "Dates", "delete": "Delete", + "delete_bom_tooltip": "Delete BOM from project.", "delete_license_group": "Delete License Group", "delete_policy": "Delete Policy", "delete_selected": "Delete selected items", @@ -593,6 +595,7 @@ "matrix": "Matrix", "metric_refresh_requested": "A refresh has been requested. Metrics will be updated when the refresh task has completed.", "name": "Name", + "no_bom_available": "No BOM exists for project", "no_file_chosen": "No file chosen", "non_vulnerable": "Non Vulnerable", "not_affected": "Not Affected", @@ -780,7 +783,9 @@ "references": "References", "reindex": "Rebuild index(es)", "rejected": "Rejected", + "remove_bom": "Remove BOM", "remove_component": "Remove Component", + "removing_dependencies": "Removing dependencies, {n} left", "reported_by": "Reported By", "required_component_identifier": "A component identifier is required", "required_component_name": "The component name is required", diff --git a/src/views/portfolio/projects/ProjectComponents.vue b/src/views/portfolio/projects/ProjectComponents.vue index 8239c62c3..74b225eac 100644 --- a/src/views/portfolio/projects/ProjectComponents.vue +++ b/src/views/portfolio/projects/ProjectComponents.vue @@ -33,6 +33,15 @@ {{ $t('message.upload_bom_tooltip') }} + + {{ $t('message.remove_bom') }} + + +

Are you sure you want to remove the BOM and all its components?

+
+ Cancel + Remove +
+
@@ -400,6 +420,56 @@ export default { } this.$refs.table.uncheckAll(); }, + handleRemoveBom() { + this.$refs.confirmModal.hide(); + this.removeBom(); + }, + removeBom: async function () { + let getDependenciesUrl = `${this.$api.BASE_URL}/${this.$api.URL_COMPONENT}/project/${this.uuid}`; + let deleteBomUrl = `${this.$api.BASE_URL}/${this.$api.URL_BOM}/project/${this.uuid}`; + try { + let allDependencies = []; + let page = 1; + let pageSize = 100; + while (true) { + let response = await this.axios.get( + `${getDependenciesUrl}?page=${page}&size=${pageSize}`, + ); + let dependencies = response.data; + if (!dependencies || dependencies.length === 0) break; + allDependencies = allDependencies.concat(dependencies); + page++; + } + let batchSize = 50; + let lengthAllDependencies = allDependencies.length; + if (lengthAllDependencies !== 0) { + for (let i = 0; i < allDependencies.length; i += batchSize) { + let batch = allDependencies.slice(i, i + batchSize); + this.$toastr.s( + this.$t('message.removing_dependencies', { + n: lengthAllDependencies, + }), + ); + lengthAllDependencies -= batch.length; + let deletePromises = batch.map((dep) => + this.axios.delete( + `${this.$api.BASE_URL}/${this.$api.URL_COMPONENT}/${dep.uuid}`, + ), + ); + await Promise.all(deletePromises); + this.$refs.table.refresh({ silent: true }); + } + await this.axios.delete(deleteBomUrl); + this.$toastr.s(this.$t('message.bom_deleted')); + this.$refs.table.removeAll(); + await this.axios.get(`/api/v1/metrics/project/${this.uuid}/refresh`); + } else { + this.$toastr.w(this.$t('message.no_bom_available')); + } + } catch (error) { + this.$toastr.w(this.$t('condition.unsuccessful_action')); + } + }, downloadBom: function (data) { let url = `${this.$api.BASE_URL}/${this.$api.URL_BOM}/cyclonedx/project/${this.uuid}`; this.axios