diff --git a/openaev-api/src/main/java/io/openaev/api/xtm_composer/XtmComposerApi.java b/openaev-api/src/main/java/io/openaev/api/xtm_composer/XtmComposerApi.java index eca82054d36..0a2e7cb6d86 100644 --- a/openaev-api/src/main/java/io/openaev/api/xtm_composer/XtmComposerApi.java +++ b/openaev-api/src/main/java/io/openaev/api/xtm_composer/XtmComposerApi.java @@ -78,7 +78,7 @@ public boolean isXtmComposerReachable() { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successful retrieval")}) public List getAllConnectorInstances( @PathVariable @NotBlank final String xtmComposerId) { - return orchestrationService.findConnectorInstancesManagedByComposer(xtmComposerId); + return orchestrationService.findActiveConnectorInstancesManagedByComposer(xtmComposerId); } @PutMapping( @@ -130,4 +130,25 @@ public XtmComposerInstanceOutput receiveConnectorInstanceHealthCheck( return orchestrationService.patchConnectorInstanceHealthCheck( xtmComposerId, connectorInstanceId, input); } + + @DeleteMapping( + value = XTMCOMPOSER_URI + "/{xtmComposerId}/connector-instances/{connectorInstanceId}") + @Operation( + summary = "Delete connector instances and remove associated connector", + description = + """ + Receives a deletion notification from XtmComposer for a connector instance. + This triggers: + - Removal of the associated injector, collector, or executor + - Permanent deletion of the connector instance from OpenAEV database + """) + @RBAC(actionPerformed = Action.DELETE, resourceType = ResourceType.CATALOG) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "Successfully delete connector")}) + public void notifyConnectorDeleted( + @PathVariable @NotBlank final String xtmComposerId, + @PathVariable @NotBlank final String connectorInstanceId) { + orchestrationService.deleteConnectorInstanceAndAssociatedConnector( + xtmComposerId, connectorInstanceId); + } } diff --git a/openaev-api/src/main/java/io/openaev/executors/ExecutorService.java b/openaev-api/src/main/java/io/openaev/executors/ExecutorService.java index 7987da1e336..544268dcd33 100644 --- a/openaev-api/src/main/java/io/openaev/executors/ExecutorService.java +++ b/openaev-api/src/main/java/io/openaev/executors/ExecutorService.java @@ -79,8 +79,11 @@ protected Executor getConnectorById(String executorId) { @Override protected ExecutorOutput mapToOutput( - Executor executor, CatalogConnector catalogConnector, boolean isVerified) { - return executorMapper.toExecutorOutput(executor, catalogConnector, isVerified); + Executor executor, + CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified) { + return executorMapper.toExecutorOutput(executor, catalogConnector, instance, isVerified); } @Override @@ -241,4 +244,13 @@ public List manageWithoutPlatformAgents(List agents, InjectStatus } return agents; } + + /** + * Delete an executor by ID + * + * @param id the executor ID to delete + */ + public void deleteById(String id) { + executorRepository.deleteById(id); + } } diff --git a/openaev-api/src/main/java/io/openaev/migration/V4_59__Add_deleting_requested_connector_status.java b/openaev-api/src/main/java/io/openaev/migration/V4_59__Add_deleting_requested_connector_status.java new file mode 100644 index 00000000000..1f6f0904a1c --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/migration/V4_59__Add_deleting_requested_connector_status.java @@ -0,0 +1,21 @@ +package io.openaev.migration; + +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +@Component +public class V4_59__Add_deleting_requested_connector_status extends BaseJavaMigration { + @Override + public void migrate(Context context) throws Exception { + try (Statement select = context.getConnection().createStatement()) { + select.execute("ALTER TYPE connector_instance_requested_status_type ADD VALUE 'deleting'; "); + select.execute( + """ + ALTER TABLE connector_instances + ADD COLUMN connector_instance_enable_deletion boolean DEFAULT FALSE; + """); + } + } +} diff --git a/openaev-api/src/main/java/io/openaev/rest/collector/CollectorApi.java b/openaev-api/src/main/java/io/openaev/rest/collector/CollectorApi.java index 6c03b78f0ab..c764c7928c6 100644 --- a/openaev-api/src/main/java/io/openaev/rest/collector/CollectorApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/collector/CollectorApi.java @@ -50,10 +50,7 @@ public class CollectorApi extends RestBehavior { mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = CollectorOutput.class)))) public Iterable collectors( - @Parameter( - name = "includeNext", - description = "Include collectors pending deployment", - required = false) + @Parameter(name = "includeNext", description = "Include collectors pending deployment") @RequestParam(value = "include_next", required = false, defaultValue = "false") boolean includeNext) { return collectorService.collectorsOutput(includeNext); diff --git a/openaev-api/src/main/java/io/openaev/rest/collector/form/CollectorOutput.java b/openaev-api/src/main/java/io/openaev/rest/collector/form/CollectorOutput.java index 8d6a07e92d4..34991e8f067 100644 --- a/openaev-api/src/main/java/io/openaev/rest/collector/form/CollectorOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/collector/form/CollectorOutput.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openaev.rest.catalog_connector.dto.CatalogConnectorSimpleOutput; +import io.openaev.rest.connector_instance.dto.ConnectorInstanceOutput; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import java.time.Instant; import lombok.Builder; @@ -35,4 +37,8 @@ public class CollectorOutput { @JsonProperty("is_verified") private boolean verified = false; + + @JsonProperty("connector_instance") + @Nullable + private ConnectorInstanceOutput connectorInstance; } diff --git a/openaev-api/src/main/java/io/openaev/rest/collector/service/CollectorService.java b/openaev-api/src/main/java/io/openaev/rest/collector/service/CollectorService.java index c4aad114cf6..9710a2acb0e 100644 --- a/openaev-api/src/main/java/io/openaev/rest/collector/service/CollectorService.java +++ b/openaev-api/src/main/java/io/openaev/rest/collector/service/CollectorService.java @@ -80,8 +80,11 @@ protected Collector getConnectorById(String collectorId) { @Override protected CollectorOutput mapToOutput( - Collector collector, CatalogConnector catalogConnector, boolean isVerified) { - return collectorMapper.toCollectorOutput(collector, catalogConnector, isVerified); + Collector collector, + CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified) { + return collectorMapper.toCollectorOutput(collector, catalogConnector, instance, isVerified); } @Override @@ -161,6 +164,15 @@ public Collector updateCollectorState(Collector collectorToUpdate, ObjectNode ne return collectorRepository.save(collectorToUpdate); } + /** + * Delete a collector by ID + * + * @param id the collector ID to delete + */ + public void deleteById(String id) { + collectorRepository.deleteById(id); + } + // -- ACTION -- @Transactional diff --git a/openaev-api/src/main/java/io/openaev/rest/connector_instance/ConnectorInstanceApi.java b/openaev-api/src/main/java/io/openaev/rest/connector_instance/ConnectorInstanceApi.java index ed450414f35..1016a91a0e3 100644 --- a/openaev-api/src/main/java/io/openaev/rest/connector_instance/ConnectorInstanceApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/connector_instance/ConnectorInstanceApi.java @@ -140,15 +140,4 @@ public ConnectorInstance updateRequestedStatus( return orchestrationService.updateRequestedStatus( connectorInstanceId, input.getRequestedStatus()); } - - @DeleteMapping(value = CONNECTOR_INSTANCE_URI + "/{connectorInstanceId}") - @Operation(summary = "Delete connector instance") - @RBAC(actionPerformed = Action.DELETE, resourceType = ResourceType.CATALOG) - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "Successfully deleted connector instance") - }) - public void deleteConnectorInstance(@PathVariable @NotBlank final String connectorInstanceId) { - connectorInstanceService.deleteById(connectorInstanceId); - } } diff --git a/openaev-api/src/main/java/io/openaev/rest/connector_instance/dto/ConnectorInstanceOutput.java b/openaev-api/src/main/java/io/openaev/rest/connector_instance/dto/ConnectorInstanceOutput.java index 3eab01f8239..d74d91952a6 100644 --- a/openaev-api/src/main/java/io/openaev/rest/connector_instance/dto/ConnectorInstanceOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/connector_instance/dto/ConnectorInstanceOutput.java @@ -18,4 +18,7 @@ public class ConnectorInstanceOutput { @JsonProperty("connector_instance_requested_status") private ConnectorInstance.REQUESTED_STATUS_TYPE requestedStatus; + + @JsonProperty("connector_instance_enable_deletion") + private boolean enableDeletion; } diff --git a/openaev-api/src/main/java/io/openaev/rest/executor/form/ExecutorOutput.java b/openaev-api/src/main/java/io/openaev/rest/executor/form/ExecutorOutput.java index 11902078176..a4bd0d6565a 100644 --- a/openaev-api/src/main/java/io/openaev/rest/executor/form/ExecutorOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/executor/form/ExecutorOutput.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openaev.rest.catalog_connector.dto.CatalogConnectorSimpleOutput; +import io.openaev.rest.connector_instance.dto.ConnectorInstanceOutput; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import java.time.Instant; import lombok.Builder; @@ -32,4 +34,8 @@ public class ExecutorOutput { @JsonProperty("is_verified") private boolean verified = false; + + @JsonProperty("connector_instance") + @Nullable + private ConnectorInstanceOutput connectorInstance; } diff --git a/openaev-api/src/main/java/io/openaev/rest/injector/form/InjectorOutput.java b/openaev-api/src/main/java/io/openaev/rest/injector/form/InjectorOutput.java index 94c589ad011..f7f41285376 100644 --- a/openaev-api/src/main/java/io/openaev/rest/injector/form/InjectorOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/injector/form/InjectorOutput.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openaev.rest.catalog_connector.dto.CatalogConnectorSimpleOutput; +import io.openaev.rest.connector_instance.dto.ConnectorInstanceOutput; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import java.time.Instant; import lombok.Builder; @@ -34,4 +36,8 @@ public class InjectorOutput { @JsonProperty("injector_updated_at") private Instant updatedAt; + + @JsonProperty("connector_instance") + @Nullable + private ConnectorInstanceOutput connectorInstance; } diff --git a/openaev-api/src/main/java/io/openaev/service/InjectorService.java b/openaev-api/src/main/java/io/openaev/service/InjectorService.java index d96dcbb7fc0..53fde19f5be 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectorService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectorService.java @@ -102,8 +102,11 @@ protected Injector getConnectorById(String injectorId) { @Override protected InjectorOutput mapToOutput( - Injector injector, CatalogConnector catalogConnector, boolean isVerified) { - return injectorMapper.toInjectorOutput(injector, catalogConnector, isVerified); + Injector injector, + CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified) { + return injectorMapper.toInjectorOutput(injector, catalogConnector, instance, isVerified); } @Override @@ -341,4 +344,13 @@ public Injector updateInjector( injectorContractRepository.saveAll(toCreates); return injectorRepository.save(injector); } + + /** + * Delete an injector by ID + * + * @param id the injector ID to delete + */ + public void deleteById(String id) { + injectorRepository.deleteById(id); + } } diff --git a/openaev-api/src/main/java/io/openaev/service/connector_instances/ConnectorInstanceService.java b/openaev-api/src/main/java/io/openaev/service/connector_instances/ConnectorInstanceService.java index cfa1c5b709c..f4d8bde762b 100644 --- a/openaev-api/src/main/java/io/openaev/service/connector_instances/ConnectorInstanceService.java +++ b/openaev-api/src/main/java/io/openaev/service/connector_instances/ConnectorInstanceService.java @@ -13,6 +13,7 @@ import io.openaev.rest.connector_instance.dto.ConnectorInstanceHealthInput; import io.openaev.rest.connector_instance.dto.ConnectorInstanceOutput; import io.openaev.rest.connector_instance.dto.CreateConnectorInstanceInput; +import io.openaev.rest.exception.BadRequestException; import io.openaev.service.connectors.ConnectorOrchestrationService; import io.openaev.utils.mapper.ConnectorInstanceMapper; import jakarta.persistence.EntityNotFoundException; @@ -38,12 +39,12 @@ public class ConnectorInstanceService { private final EncryptionFactory encryptionFactory; /** - * Retrieves all connector instances managed by XtmComposer with their configurations. + * Retrieves all active connector instances managed by XtmComposer with their configurations. * * @return the list of connector instances managed by XtmComposer */ - public List connectorInstancesManagedByXtmComposer() { - return connectorInstanceRepository.findAllManagedByXtmComposerAndConfiguration(); + public List activeConnectorInstancesManagedByXtmComposer() { + return connectorInstanceRepository.findAllActiveManagedByXtmComposerAndConfiguration(); } /** @@ -142,7 +143,12 @@ public ConnectorInstance updateCurrentStatus( * @return the connector instance updated */ public ConnectorInstance updateRequestedStatus( - ConnectorInstance instance, ConnectorInstance.REQUESTED_STATUS_TYPE newRequestedStatus) { + ConnectorInstance instance, ConnectorInstance.REQUESTED_STATUS_TYPE newRequestedStatus) + throws BadRequestException { + if (ConnectorInstance.REQUESTED_STATUS_TYPE.deleting.equals(newRequestedStatus) + && !instance.isEnableDeletion()) { + throw new BadRequestException("Deletion request is not enabled for this connector instance"); + } instance.setRequestedStatus(newRequestedStatus); return this.save(instance); } @@ -202,6 +208,7 @@ private ConnectorInstance buildNewConnectorInstanceFromCatalog( newInstance.setRequestedStatus(ConnectorInstance.REQUESTED_STATUS_TYPE.stopping); newInstance.setCurrentStatus(ConnectorInstance.CURRENT_STATUS_TYPE.stopped); newInstance.setSource(ConnectorInstance.SOURCE.CATALOG_DEPLOYMENT); + newInstance.setEnableDeletion(!catalogConnector.isManagerSupported()); return newInstance; } diff --git a/openaev-api/src/main/java/io/openaev/service/connectors/AbstractConnectorService.java b/openaev-api/src/main/java/io/openaev/service/connectors/AbstractConnectorService.java index 8b21380e431..032ac7b096a 100644 --- a/openaev-api/src/main/java/io/openaev/service/connectors/AbstractConnectorService.java +++ b/openaev-api/src/main/java/io/openaev/service/connectors/AbstractConnectorService.java @@ -35,11 +35,14 @@ protected AbstractConnectorService( protected abstract T getConnectorById(String id); protected abstract Output mapToOutput( - T connector, CatalogConnector catalogConnector, boolean isVerified); + T connector, + CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified); protected abstract T createNewConnector(); - private String getConnectorIdFromInstance(ConnectorInstance instance) { + public String getConnectorIdFromInstance(ConnectorInstance instance) { return instance.getConfigurations().stream() .filter(c -> this.connectorType.getIdKeyName().equals(c.getKey())) .map(c -> c.getValue().asText()) @@ -69,7 +72,7 @@ private Output toConnectorOutput(T connector, Map ins : catalogConnectorService .findBySlug(connector.getType().replace("openaev_", "")) .orElse(null); - return mapToOutput(connector, catalogConnector, isVerified); + return mapToOutput(connector, catalogConnector, instance, isVerified); } private T createExternalCollector(String collectorId, ConnectorInstance instance) { @@ -112,7 +115,12 @@ public Iterable getConnectorsOutput(boolean includeNext) { .forEach( entry -> { T newConnector = createExternalCollector(entry.getKey(), entry.getValue()); - result.add(mapToOutput(newConnector, entry.getValue().getCatalogConnector(), true)); + result.add( + mapToOutput( + newConnector, + entry.getValue().getCatalogConnector(), + entry.getValue(), + true)); }); } diff --git a/openaev-api/src/main/java/io/openaev/service/connectors/ConnectorOrchestrationService.java b/openaev-api/src/main/java/io/openaev/service/connectors/ConnectorOrchestrationService.java index d3e992260f9..7420a69b8a5 100644 --- a/openaev-api/src/main/java/io/openaev/service/connectors/ConnectorOrchestrationService.java +++ b/openaev-api/src/main/java/io/openaev/service/connectors/ConnectorOrchestrationService.java @@ -15,9 +15,7 @@ import io.openaev.service.connector_instances.ConnectorInstanceLogService; import io.openaev.service.connector_instances.ConnectorInstanceService; import jakarta.persistence.EntityNotFoundException; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -46,19 +44,22 @@ public class ConnectorOrchestrationService { private final LicenseCacheManager licenseCacheManager; /** - * Find connector instances managed by xtm Composer + * Find active connector instances managed by xtm Composer * * @param xtmComposerId XTM Composer id * @return List of connector instances */ - public List findConnectorInstancesManagedByComposer( + public List findActiveConnectorInstancesManagedByComposer( String xtmComposerId) { this.xtmComposerService.throwIfInvalidXtmComposerId(xtmComposerId); - - List instances = - connectorInstanceService.connectorInstancesManagedByXtmComposer(); - - return instances.stream().map(xtmComposerService::toXtmComposerInstanceOutput).toList(); + List activeInstances = + connectorInstanceService.activeConnectorInstancesManagedByXtmComposer(); + activeInstances.forEach( + connectorInstance -> { + connectorInstance.setEnableDeletion(true); + }); + connectorInstanceService.saveAll(new HashSet<>(activeInstances)); + return activeInstances.stream().map(xtmComposerService::toXtmComposerInstanceOutput).toList(); } /** @@ -225,6 +226,42 @@ public List updateConnectorInstanceConfiguration connectorInstanceId, catalogConnectorWithConfigMap.configurationsMap, input); } + private Optional getConnectorId(ConnectorInstance instance, ConnectorType type) { + return Optional.ofNullable( + switch (type) { + case COLLECTOR -> collectorService.getConnectorIdFromInstance(instance); + case INJECTOR -> injectorService.getConnectorIdFromInstance(instance); + case EXECUTOR -> executorService.getConnectorIdFromInstance(instance); + }); + } + + private void deleteConnector(String id, ConnectorType type) { + switch (type) { + case COLLECTOR -> collectorService.deleteById(id); + case INJECTOR -> injectorService.deleteById(id); + case EXECUTOR -> executorService.deleteById(id); + } + } + + /** + * Delete connector instance and associated connector + * + * @param xtmComposerId the unique identifier of the XTM composer to validate + * @param connectorInstanceId the unique identifier of the connector instance to delete + */ + public void deleteConnectorInstanceAndAssociatedConnector( + String xtmComposerId, String connectorInstanceId) { + this.xtmComposerService.throwIfInvalidXtmComposerId(xtmComposerId); + + ConnectorInstance connectorInstance = + connectorInstanceService.connectorInstanceById(connectorInstanceId); + getConnectorId(connectorInstance, connectorInstance.getCatalogConnector().getContainerType()) + .ifPresent( + id -> deleteConnector(id, connectorInstance.getCatalogConnector().getContainerType())); + + this.connectorInstanceService.deleteById(connectorInstanceId); + } + /** * Pushes log entries to a specific connector instance after validating the XTM composer. * diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/CollectorMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/CollectorMapper.java index 6cea8e1ff11..bef85d54752 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/CollectorMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/CollectorMapper.java @@ -2,6 +2,7 @@ import io.openaev.database.model.CatalogConnector; import io.openaev.database.model.Collector; +import io.openaev.database.model.ConnectorInstance; import io.openaev.rest.collector.form.CollectorOutput; import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; @@ -14,9 +15,13 @@ public class CollectorMapper { private final CatalogConnectorMapper catalogConnectorMapper; + private final ConnectorInstanceMapper connectorInstanceMapper; public CollectorOutput toCollectorOutput( - Collector collector, @Nullable CatalogConnector catalogConnector, boolean isVerified) { + Collector collector, + @Nullable CatalogConnector catalogConnector, + @Nullable ConnectorInstance instance, + boolean isVerified) { return CollectorOutput.builder() .id(collector.getId()) .name(collector.getName()) @@ -24,6 +29,8 @@ public CollectorOutput toCollectorOutput( .external(collector.isExternal()) .lastExecution(collector.getUpdatedAt()) .catalog(catalogConnectorMapper.toCatalogSimpleOutput(catalogConnector)) + .connectorInstance( + instance != null ? connectorInstanceMapper.toConnectorInstanceOutput(instance) : null) .verified(isVerified) .build(); } diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/ConnectorInstanceMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/ConnectorInstanceMapper.java index 13db22e569a..caf372224f8 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/ConnectorInstanceMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/ConnectorInstanceMapper.java @@ -16,6 +16,7 @@ public ConnectorInstanceOutput toConnectorInstanceOutput(ConnectorInstance conne .id(connectorInstance.getId()) .currentStatus(connectorInstance.getCurrentStatus()) .requestedStatus(connectorInstance.getRequestedStatus()) + .enableDeletion(connectorInstance.isEnableDeletion()) .build(); } } diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/ExecutorMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/ExecutorMapper.java index f2a6dca67cb..4955b111499 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/ExecutorMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/ExecutorMapper.java @@ -1,6 +1,7 @@ package io.openaev.utils.mapper; import io.openaev.database.model.CatalogConnector; +import io.openaev.database.model.ConnectorInstance; import io.openaev.database.model.Executor; import io.openaev.rest.executor.form.ExecutorOutput; import jakarta.annotation.Nullable; @@ -13,15 +14,21 @@ @Slf4j public class ExecutorMapper { private final CatalogConnectorMapper catalogConnectorMapper; + private final ConnectorInstanceMapper connectorInstanceMapper; public ExecutorOutput toExecutorOutput( - Executor executor, @Nullable CatalogConnector catalogConnector, boolean isVerified) { + Executor executor, + @Nullable CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified) { return ExecutorOutput.builder() .id(executor.getId()) .name(executor.getName()) .type(executor.getType()) .updatedAt(executor.getUpdatedAt()) .catalog(catalogConnectorMapper.toCatalogSimpleOutput(catalogConnector)) + .connectorInstance( + instance != null ? connectorInstanceMapper.toConnectorInstanceOutput(instance) : null) .verified(isVerified) .build(); } diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectorMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectorMapper.java index 235e0bb8c9a..3a0e4ec4edb 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectorMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectorMapper.java @@ -1,6 +1,7 @@ package io.openaev.utils.mapper; import io.openaev.database.model.CatalogConnector; +import io.openaev.database.model.ConnectorInstance; import io.openaev.database.model.Injector; import io.openaev.rest.injector.form.InjectorOutput; import jakarta.annotation.Nullable; @@ -13,15 +14,21 @@ @Slf4j public class InjectorMapper { private final CatalogConnectorMapper catalogConnectorMapper; + private final ConnectorInstanceMapper connectorInstanceMapper; public InjectorOutput toInjectorOutput( - Injector injector, @Nullable CatalogConnector catalogConnector, boolean isVerified) { + Injector injector, + @Nullable CatalogConnector catalogConnector, + ConnectorInstance instance, + boolean isVerified) { return InjectorOutput.builder() .id(injector.getId()) .name(injector.getName()) .type(injector.getType()) .external(injector.isExternal()) .catalog(catalogConnectorMapper.toCatalogSimpleOutput(catalogConnector)) + .connectorInstance( + instance != null ? connectorInstanceMapper.toConnectorInstanceOutput(instance) : null) .verified(isVerified) .updatedAt(injector.getUpdatedAt()) .build(); diff --git a/openaev-api/src/test/java/io/openaev/api/xtm_composer/XtmComposerApiTest.java b/openaev-api/src/test/java/io/openaev/api/xtm_composer/XtmComposerApiTest.java index a72b3f701aa..4a17fb7af1a 100644 --- a/openaev-api/src/test/java/io/openaev/api/xtm_composer/XtmComposerApiTest.java +++ b/openaev-api/src/test/java/io/openaev/api/xtm_composer/XtmComposerApiTest.java @@ -2,10 +2,10 @@ import static io.openaev.api.xtm_composer.XtmComposerApi.XTMCOMPOSER_URI; import static io.openaev.database.model.SettingKeys.*; +import static io.openaev.helper.StreamHelper.fromIterable; import static io.openaev.utils.JsonUtils.asJsonString; import static io.openaev.utils.fixtures.CatalogConnectorFixture.createDefaultCatalogConnectorManagedByXtmComposer; -import static io.openaev.utils.fixtures.ConnectorInstanceFixture.createDefaultConnectorInstance; -import static io.openaev.utils.fixtures.ConnectorInstanceFixture.createDefaultConnectorInstanceConfiguration; +import static io.openaev.utils.fixtures.ConnectorInstanceFixture.*; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -18,9 +18,11 @@ import io.openaev.api.xtm_composer.dto.XtmComposerRegisterInput; import io.openaev.api.xtm_composer.dto.XtmComposerUpdateStatusInput; import io.openaev.config.OpenAEVConfig; +import io.openaev.database.model.Collector; import io.openaev.database.model.ConnectorInstance; import io.openaev.database.model.ConnectorInstanceLog; import io.openaev.database.model.Setting; +import io.openaev.database.repository.CollectorRepository; import io.openaev.database.repository.ConnectorInstanceLogRepository; import io.openaev.database.repository.ConnectorInstanceRepository; import io.openaev.rest.connector_instance.dto.ConnectorInstanceHealthInput; @@ -50,6 +52,7 @@ public class XtmComposerApiTest extends IntegrationTest { @Autowired private PlatformSettingsService platformSettingsService; @Autowired private ConnectorInstanceRepository connectorInstanceRepository; @Autowired private ConnectorInstanceLogRepository connectorInstanceLogRepository; + @Autowired private CollectorRepository collectorRepository; @Autowired private CatalogConnectorComposer catalogConnectorComposer; @Autowired private ConnectorInstanceComposer connectorInstanceComposer; @@ -332,6 +335,10 @@ void should_retrieveAllInstancesManagedByXtmComposer() throws Exception { .isArray() .size() .isEqualTo(1); + + // verify that instance enable deletion + List instancesDB = fromIterable(connectorInstanceRepository.findAll()); + instancesDB.forEach(i -> assertTrue(i.isEnableDeletion())); } } @@ -539,5 +546,70 @@ void givenHealthCheck_should_updateInstance() throws Exception { assertFalse(instanceDb.get().isInRebootLoop()); } } + + @Nested + class deleteConnectorInstanceAndAssociatedConnector { + @Test + @DisplayName("Given fake composer id should throw error") + void givenFakeComposerId_should_throwError() throws Exception { + Map composerSettings = new HashMap<>(); + composerSettings.put(XTM_COMPOSER_ID.key(), "composer-id-test"); + composerSettings.put(XTM_COMPOSER_VERSION.key(), "composer-version-test"); + platformSettingsService.saveSettings(composerSettings); + + mvc.perform( + delete(XTMCOMPOSER_URI + "/fake-composer-id/connector-instances/fake-instance-id") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect( + result -> { + String errorMessage = result.getResolvedException().getMessage(); + assertTrue(errorMessage.contains("Invalid xtm-composer identifier")); + }); + } + + @Test + @DisplayName("Given connector instance id should delete it and associated connector") + void givenConnectorInstanceId_should_deleteItAndAssociatedConnector() throws Exception { + String collectorId = UUID.randomUUID().toString(); + ConnectorInstance instance = + connectorInstanceComposer + .forConnectorInstance(createDefaultConnectorInstance()) + .withConnectorInstanceConfiguration( + connectorInstanceConfigurationComposer.forConnectorInstanceConfiguration( + createConnectorInstanceConfiguration("COLLECTOR_ID", collectorId))) + .withCatalogConnector( + catalogConnectorComposer.forCatalogConnector( + createDefaultCatalogConnectorManagedByXtmComposer("Microsoft collector"))) + .persist() + .get(); + Collector collector = new Collector(); + collector.setId(collectorId); + collector.setType("microsoft-collector"); + collector.setName("Microsoft collector"); + collectorRepository.save(collector); + + Map composerSettings = new HashMap<>(); + composerSettings.put(XTM_COMPOSER_ID.key(), "composer-id-test"); + composerSettings.put(XTM_COMPOSER_VERSION.key(), "composer-version-test"); + platformSettingsService.saveSettings(composerSettings); + + mvc.perform( + delete( + XTMCOMPOSER_URI + + "/composer-id-test/connector-instances/" + + instance.getId()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()); + + Optional instanceDb = + connectorInstanceRepository.findById(instance.getId()); + assertFalse(instanceDb.isPresent()); + Optional collectorDb = collectorRepository.findById(collectorId); + assertFalse(collectorDb.isPresent()); + } + } } } diff --git a/openaev-api/src/test/java/io/openaev/rest/ConnectorInstanceApiTest.java b/openaev-api/src/test/java/io/openaev/rest/ConnectorInstanceApiTest.java index 9bcebc8f416..8a1f8f61cb6 100644 --- a/openaev-api/src/test/java/io/openaev/rest/ConnectorInstanceApiTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/ConnectorInstanceApiTest.java @@ -273,6 +273,7 @@ void should_successfullyCreateConnectorInstance() throws Exception { ConnectorInstance.REQUESTED_STATUS_TYPE.stopping, instanceDb.getFirst().getRequestedStatus()); assertEquals(ConnectorInstance.SOURCE.CATALOG_DEPLOYMENT, instanceDb.getFirst().getSource()); + assertFalse(instanceDb.getFirst().isEnableDeletion()); assertEquals( 5, instanceDb.getFirst().getConfigurations().size()); // 3 from input + token + collector_id @@ -564,6 +565,38 @@ void givenConnectorSupportedByManager_should_throwErrorIfXtmComposerDown() throw }); } + @Test + @DisplayName("Should throw exception when attempting to delete instance with deletion disabled") + void givenDeletionNotEnabledInstance_should_throwErrorWhenDeletionRequested() throws Exception { + when(eeService.isLicenseActive(any())).thenReturn(true); + + CatalogConnector catalogConnector = getCatalogConnector(); + ConnectorInstance connectorInstance = getConnectorInstance(catalogConnector, Set.of()); + + UpdateConnectorInstanceRequestedStatus input = new UpdateConnectorInstanceRequestedStatus(); + input.setRequestedStatus(ConnectorInstance.REQUESTED_STATUS_TYPE.deleting); + + Map composerSettings = new HashMap<>(); + composerSettings.put(XTM_COMPOSER_ID.key(), "composer-id-test"); + composerSettings.put(XTM_COMPOSER_VERSION.key(), "composer-version-test"); + composerSettings.put(XTM_COMPOSER_LAST_CONNECTIVITY_CHECK.key(), Instant.now().toString()); + platformSettingsService.saveSettings(composerSettings); + + mvc.perform( + put(CONNECTOR_INSTANCE_URI + "/" + connectorInstance.getId() + "/requested-status") + .content(asJsonString(input)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect( + result -> { + String errorMessage = result.getResolvedException().getMessage(); + assertTrue( + errorMessage.contains( + "Deletion request is not enabled for this connector instance")); + }); + } + @Test @DisplayName("Should successfully update requested status") void shouldSuccessfullyUpdateRequestedStatus() throws Exception { diff --git a/openaev-front/src/actions/connector_instances/connector-instance-actions.ts b/openaev-front/src/actions/connector_instances/connector-instance-actions.ts index f7b9303d09f..c13de978884 100644 --- a/openaev-front/src/actions/connector_instances/connector-instance-actions.ts +++ b/openaev-front/src/actions/connector_instances/connector-instance-actions.ts @@ -1,7 +1,6 @@ import type { Dispatch } from 'redux'; import { - delReferential, getReferential, putReferential, simpleCall, @@ -39,11 +38,6 @@ export const updateRequestedStatus = (instanceId: string, data: UpdateConnectorI return putReferential(connectorInstance, uri, data)(dispatch); }; -export const deleteConnectorInstance = (instanceId: string) => (dispatch: Dispatch) => { - const uri = `${CONNECTOR_INSTANCE_URI}/${instanceId}`; - return delReferential(uri, connectorInstance.key, instanceId)(dispatch); -}; - export const fetchConnectorInstanceLogs = (instanceId: string) => () => { const uri = `${CONNECTOR_INSTANCE_URI}/${instanceId}/logs`; return simpleCall(uri); diff --git a/openaev-front/src/admin/components/integrations/Index.tsx b/openaev-front/src/admin/components/integrations/Index.tsx index 877eab8c07b..d24933ef507 100644 --- a/openaev-front/src/admin/components/integrations/Index.tsx +++ b/openaev-front/src/admin/components/integrations/Index.tsx @@ -5,7 +5,7 @@ import { makeStyles } from 'tss-react/mui'; import { errorWrapper } from '../../../components/Error'; import Loader from '../../../components/Loader'; import NotFound from '../../../components/NotFound'; -import ConnectorDetails from './common/ConnectorDetails'; +import CatalogConnectorDetails from './common/CatalogConnectorDetails'; import InjectorPage from './injectors/InjectorPage'; const Catalog = lazy(() => import('./catalog_connectors/Catalog')); @@ -29,7 +29,7 @@ const Index = () => { } /> - } /> + } /> diff --git a/openaev-front/src/admin/components/integrations/common/ConnectorDetails.tsx b/openaev-front/src/admin/components/integrations/common/CatalogConnectorDetails.tsx similarity index 97% rename from openaev-front/src/admin/components/integrations/common/ConnectorDetails.tsx rename to openaev-front/src/admin/components/integrations/common/CatalogConnectorDetails.tsx index 1dbfc8f363b..32d795e5ec8 100644 --- a/openaev-front/src/admin/components/integrations/common/ConnectorDetails.tsx +++ b/openaev-front/src/admin/components/integrations/common/CatalogConnectorDetails.tsx @@ -10,7 +10,7 @@ import CreateConnectorInstanceDrawer from '../connector_instance/CreateConnector import ConnectorCatalogInfo from './ConnectorCatalogInfo'; import ConnectorTitle from './ConnectorTitle'; -const ConnectorDetails = () => { +const CatalogConnectorDetails = () => { // Standard hooks const ability = useContext(AbilityContext); const { t } = useFormatter(); @@ -58,4 +58,4 @@ const ConnectorDetails = () => { ); }; -export default ConnectorDetails; +export default CatalogConnectorDetails; diff --git a/openaev-front/src/admin/components/integrations/common/ConnectorCard.tsx b/openaev-front/src/admin/components/integrations/common/ConnectorCard.tsx index 8e53f92145e..24cf9942274 100644 --- a/openaev-front/src/admin/components/integrations/common/ConnectorCard.tsx +++ b/openaev-front/src/admin/components/integrations/common/ConnectorCard.tsx @@ -1,4 +1,4 @@ -import { Card, CardActionArea, CardContent, Chip, Typography } from '@mui/material'; +import { Card, CardActionArea, CardContent, Chip, Tooltip, Typography } from '@mui/material'; import type { SyntheticEvent } from 'react'; import { Link } from 'react-router'; import { makeStyles } from 'tss-react/mui'; @@ -77,6 +77,7 @@ type ConnectorCardProps = { isNotClickable?: boolean; connector: ConnectorMainInfo; onDeployBtnClick?: (e: SyntheticEvent) => void; + disabled?: boolean; }; const ConnectorCard = ({ @@ -85,48 +86,58 @@ const ConnectorCard = ({ showLastUpdatedAt = false, isNotClickable = false, onDeployBtnClick, + disabled = false, }: ConnectorCardProps) => { const { classes } = useStyles(); const { t, nsdt } = useFormatter(); return ( - - + - - - {connector.connectorDescription && ( - - {connector.connectorDescription} - - )} -
- - {showLastUpdatedAt - && ( -
-
- - {`${t('Updated at')} ${nsdt(connector.lastUpdatedAt)}`} - -
- )} - {onDeployBtnClick - && } -
- - - + + + + {connector.connectorDescription && ( + + {connector.connectorDescription} + + )} +
+ + {showLastUpdatedAt + && ( +
+
+ + {`${t('Updated at')} ${nsdt(connector.lastUpdatedAt)}`} + +
+ )} + {onDeployBtnClick + && } +
+ + + + ); }; diff --git a/openaev-front/src/admin/components/integrations/common/ConnectorContext.ts b/openaev-front/src/admin/components/integrations/common/ConnectorContext.ts index 16393f756bd..14c8c8cd9e6 100644 --- a/openaev-front/src/admin/components/integrations/common/ConnectorContext.ts +++ b/openaev-front/src/admin/components/integrations/common/ConnectorContext.ts @@ -20,6 +20,7 @@ export interface ConnectorOutput { catalog?: CatalogConnectorSimpleOutput; updatedAt?: string; isVerified: boolean; + instance?: ConnectorInstanceOutput; } export interface ConnectorContextType { @@ -59,6 +60,7 @@ export const injectorConfig: ConnectorContextType = { catalog: data?.catalog, updatedAt: data?.injector_updated_at, isVerified: data?.is_verified ?? false, + instance: data?.connector_instance, }), }; @@ -77,6 +79,7 @@ export const collectorConfig: ConnectorContextType = { catalog: data?.catalog, updatedAt: data?.collector_last_execution, isVerified: data?.is_verified ?? false, + instance: data?.connector_instance, }), routes: { list: '/admin/integrations/collectors', @@ -103,6 +106,7 @@ export const executorConfig: ConnectorContextType = { catalog: data?.catalog, updatedAt: data?.executor_updated_at, isVerified: data?.is_verified ?? false, + instance: data?.connector_instance, }), }; diff --git a/openaev-front/src/admin/components/integrations/common/ConnectorList.tsx b/openaev-front/src/admin/components/integrations/common/ConnectorList.tsx index de75c452a75..0314d45a657 100644 --- a/openaev-front/src/admin/components/integrations/common/ConnectorList.tsx +++ b/openaev-front/src/admin/components/integrations/common/ConnectorList.tsx @@ -77,6 +77,7 @@ const ConnectorList = () => { cardActionUrl={routes.detail(connector.id)} isNotClickable={connector.catalog == null && connectorType != 'injector'} showLastUpdatedAt + disabled={connector.instance?.connector_instance_requested_status == 'deleting'} /> ))} diff --git a/openaev-front/src/admin/components/integrations/common/ConnectorPage.tsx b/openaev-front/src/admin/components/integrations/common/ConnectorPage.tsx index 4bbfb13149d..953f3d11089 100644 --- a/openaev-front/src/admin/components/integrations/common/ConnectorPage.tsx +++ b/openaev-front/src/admin/components/integrations/common/ConnectorPage.tsx @@ -62,7 +62,9 @@ const ConnectorPage = ({ extraInfoComponent }: { extraInfoComponent?: ReactNode instanceCurrentStatus={instance?.connector_instance_current_status} instanceRequestedStatus={instance?.connector_instance_requested_status} showUpdateButtons={isEnterpriseEdition && ability.can(ACTIONS.MANAGE, SUBJECTS.PLATFORM_SETTINGS)} - disabledUpdateButtons={!isXtmComposerUp && catalogConnector?.catalog_connector_manager_supported} + disabledUpdateStatusButtons={(!isXtmComposerUp && catalogConnector?.catalog_connector_manager_supported)} + disabledDeleteButton={(!isXtmComposerUp && catalogConnector?.catalog_connector_manager_supported) || !instance?.connector_instance_enable_deletion} + disabled={instance?.connector_instance_requested_status == 'deleting'} /> ({ autoMarginLeft: { marginLeft: 'auto' } })); -const ConnectorPopover = ({ connectorInstanceId, connectorName }: ConnectorPopoverProps) => { - // Standard hooks +const ConnectorPopover = ({ connectorInstanceId, connectorName, disabled = false, disabledDeleteButtons = false }: ConnectorPopoverProps) => { const { classes } = useStyles(); + const theme = useTheme(); const { t } = useFormatter(); + + const navigate = useNavigate(); const dispatch = useAppDispatch(); const ability = useContext(AbilityContext); const { instance, catalogConnector, isXtmComposerUp } = useOutletContext(); const [openDialogDelete, setOpenDialogDelete] = useState(false); - // const handleDelete = () => { - // setOpenDialogDelete(true); - // }; + const handleDelete = () => { + setOpenDialogDelete(true); + }; const submitDeleteConnectorInstance = () => { - dispatch(deleteConnectorInstance(connectorInstanceId)); + dispatch(updateRequestedStatus(connectorInstanceId, { connector_instance_requested_status: 'deleting' })).then(() => { + const parentPath = location.pathname.split('/').slice(0, -1).join('/'); + navigate(parentPath); + }); setOpenDialogDelete(false); }; + const [openUpdateConnectorInstanceDrawer, setOpenCreateConnectorInstanceDrawer] = useState(false); const onOpenUpdateConnectorInstanceDrawer = () => setOpenCreateConnectorInstanceDrawer(true); const onCloseUpdateConnectorInstanceDrawer = () => setOpenCreateConnectorInstanceDrawer(false); // Button Popover - const entries = [ - { - // label: t('Delete'), - // action: handleDelete, - // userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.PLATFORM_SETTINGS), - // }, { - label: t('Update'), - action: () => onOpenUpdateConnectorInstanceDrawer(), - userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.PLATFORM_SETTINGS), - }]; + const entries = [{ + label: t('Update'), + action: () => onOpenUpdateConnectorInstanceDrawer(), + userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.PLATFORM_SETTINGS), + }, { + label: t('Delete'), + action: handleDelete, + userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.PLATFORM_SETTINGS), + disabled: disabledDeleteButtons, + style: { color: theme.palette.error.light }, + }]; return ( <> @@ -59,6 +69,7 @@ const ConnectorPopover = ({ connectorInstanceId, connectorName }: ConnectorPopov className={classes.autoMarginLeft} entries={entries} variant="toggle" + disabled={disabled} /> void; - disabledUpdateButtons?: boolean; + disabledUpdateStatusButtons?: boolean; + disabledDeleteButton?: boolean; + disabled?: boolean; }; const ConnectorTitle = ({ @@ -89,9 +91,10 @@ const ConnectorTitle = ({ instanceRequestedStatus, showDeployButton = false, showUpdateButtons = false, - disabledUpdateButtons = false, - onDeployBtnClick = () => { - }, + disabledUpdateStatusButtons = false, + disabledDeleteButton = false, + disabled = false, + onDeployBtnClick = () => {}, }: ConnectorHeaderProps) => { // Standard hooks const { classes } = useStyles(); @@ -159,6 +162,8 @@ const ConnectorTitle = ({ )} {showDeployButton && ( @@ -174,7 +179,7 @@ const ConnectorTitle = ({ color={instanceRequestedStatus == 'starting' ? 'error' : 'success'} size="small" onClick={onUpdateRequestedStatusClick} - disabled={disabledUpdateButtons} + disabled={disabledUpdateStatusButtons || disabled} > {instanceRequestedStatus == 'starting' ? t('Stop') : t('Start')} diff --git a/openaev-front/src/components/common/ButtonPopover.tsx b/openaev-front/src/components/common/ButtonPopover.tsx index 719cd2be2f7..1882efc770e 100644 --- a/openaev-front/src/components/common/ButtonPopover.tsx +++ b/openaev-front/src/components/common/ButtonPopover.tsx @@ -9,6 +9,7 @@ export interface PopoverEntry { action: () => void | Dispatch>; disabled?: boolean; userRight: boolean; + style?: CSSProperties; } export type VariantButtonPopover = 'toggle' | 'icon'; @@ -78,6 +79,7 @@ const ButtonPopover: FunctionComponent = ({ { entry.action(); setAnchorEl(null); diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index 4449f6354dc..7662a67ff7c 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -904,6 +904,7 @@ export interface CollectorOutput { collector_last_execution?: string; collector_name: string; collector_type: string; + connector_instance?: ConnectorInstanceOutput; is_verified?: boolean; } @@ -1064,11 +1065,12 @@ export interface ConnectorInstance { /** @uniqueItems true */ connector_instance_configurations: ConnectorInstanceConfiguration[]; connector_instance_current_status: "started" | "stopped"; + connector_instance_enable_deletion?: boolean; connector_instance_id: string; connector_instance_is_in_reboot_loop?: boolean; /** @uniqueItems true */ connector_instance_logs: ConnectorInstanceLog[]; - connector_instance_requested_status?: "starting" | "stopping"; + connector_instance_requested_status?: "starting" | "stopping" | "deleting"; /** @format int32 */ connector_instance_restart_count?: number; connector_instance_source: @@ -1120,8 +1122,9 @@ export interface ConnectorInstanceLogsInput { export interface ConnectorInstanceOutput { connector_instance_current_status: "started" | "stopped"; + connector_instance_enable_deletion?: boolean; connector_instance_id: string; - connector_instance_requested_status?: "starting" | "stopping"; + connector_instance_requested_status?: "starting" | "stopping" | "deleting"; } export interface ContractOutputElement { @@ -2409,6 +2412,7 @@ export interface ExecutorCreateInput { export interface ExecutorOutput { /** Catalog simple output */ catalog?: CatalogConnectorSimpleOutput; + connector_instance?: ConnectorInstanceOutput; /** Executor id */ executor_id: string; executor_name: string; @@ -3655,6 +3659,7 @@ export interface InjectorCreateInput { export interface InjectorOutput { /** Catalog simple output */ catalog?: CatalogConnectorSimpleOutput; + connector_instance?: ConnectorInstanceOutput; injector_external?: boolean; /** Injector id */ injector_id: string; @@ -6261,7 +6266,7 @@ export interface UpdateAssetsOnAssetGroupInput { export interface UpdateConnectorInstanceRequestedStatus { /** The connector instance current status */ - connector_instance_requested_status: "starting" | "stopping"; + connector_instance_requested_status: "starting" | "stopping" | "deleting"; } export interface UpdateExerciseInput { @@ -6822,7 +6827,7 @@ export interface XtmComposerInstanceOutput { /** Connector Instance name */ connector_instance_name: string; /** Connector Instance requested status */ - connector_instance_requested_status: "starting" | "stopping"; + connector_instance_requested_status: "starting" | "stopping" | "deleting"; } export interface XtmComposerOutput { diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index 61ca139cd9b..3746babb345 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -432,6 +432,7 @@ "Delete": "Delete", "Delete test": "Delete test", "Deleted": "Deleted", + "Deletion is being processed": "Deletion is being processed", "Deploy": "Deploy", "Deploy in one-click threat management resources such as scenarios": "Deploy in one-click threat management resources such as scenarios", "Deployment": "Deployment", diff --git a/openaev-front/src/utils/lang/es.json b/openaev-front/src/utils/lang/es.json index d8a24a44556..d495414b4cf 100644 --- a/openaev-front/src/utils/lang/es.json +++ b/openaev-front/src/utils/lang/es.json @@ -432,6 +432,7 @@ "Delete": "Borrar", "Delete test": "Borrar prueba", "Deleted": "Borrado", + "Deletion is being processed": "Se está tramitando el borrado", "Deploy": "Desplegar", "Deploy in one-click threat management resources such as scenarios": "Despliegue en un clic de recursos de gestión de amenazas como escenarios", "Deployment": "Despliegue", diff --git a/openaev-front/src/utils/lang/fr.json b/openaev-front/src/utils/lang/fr.json index 6dee5ac0d95..7c9878b693d 100644 --- a/openaev-front/src/utils/lang/fr.json +++ b/openaev-front/src/utils/lang/fr.json @@ -432,6 +432,7 @@ "Delete": "Supprimer", "Delete test": "Supprimer le test", "Deleted": "Supprimé", + "Deletion is being processed": "La suppression est en cours", "Deploy": "Déployer", "Deploy in one-click threat management resources such as scenarios": "Déployer en un clic des ressources de gestion des menaces telles que des scénarios", "Deployment": "Déploiement", @@ -567,8 +568,8 @@ "documentation.": "documentation.", "Documents": "Documents", "Does not repeat": "Ne se répète pas", - "domains": "domaines", "Domains": "Domaines", + "domains": "domaines", "Done": "Fait", "Download": "Télécharger", "Draft": "Brouillon ", diff --git a/openaev-front/src/utils/lang/zh.json b/openaev-front/src/utils/lang/zh.json index 2c26d0fc259..6fbb646cf5c 100644 --- a/openaev-front/src/utils/lang/zh.json +++ b/openaev-front/src/utils/lang/zh.json @@ -432,6 +432,7 @@ "Delete": "删除", "Delete test": "删除测试", "Deleted": "已删除", + "Deletion is being processed": "删除正在处理中", "Deploy": "部署", "Deploy in one-click threat management resources such as scenarios": "一键部署威胁管理资源,如场景", "Deployment": "部署", @@ -567,8 +568,8 @@ "documentation.": "文档。", "Documents": "文档", "Does not repeat": "不要重复", - "domains": "领域", "Domains": "领域", + "domains": "领域", "Done": "完成", "Download": "下载", "Draft": "草稿 ", diff --git a/openaev-model/src/main/java/io/openaev/database/audit/BaseEvent.java b/openaev-model/src/main/java/io/openaev/database/audit/BaseEvent.java index a042d45950b..a379936fc66 100644 --- a/openaev-model/src/main/java/io/openaev/database/audit/BaseEvent.java +++ b/openaev-model/src/main/java/io/openaev/database/audit/BaseEvent.java @@ -7,6 +7,7 @@ import io.openaev.database.model.Base; import jakarta.persistence.Id; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import lombok.Getter; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -32,6 +33,17 @@ public class BaseEvent implements Cloneable { @JsonProperty("listened") private boolean listened; + private Class findBaseClass(Class currentClass) { + Class superClass = currentClass.getSuperclass(); + + // If superclass is Object or abstract, current class is the target + if (superClass.equals(Object.class) || Modifier.isAbstract(superClass.getModifiers())) { + return currentClass; + } + + return superClass; + } + public BaseEvent(String type, Base data, ObjectMapper mapper) { this.type = type; this.instance = data; @@ -39,9 +51,7 @@ public BaseEvent(String type, Base data, ObjectMapper mapper) { this.listened = data.isListened(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); this.sessionId = requestAttributes != null ? requestAttributes.getSessionId() : null; - Class currentClass = data.getClass(); - boolean isTargetClass = currentClass.getSuperclass().equals(Object.class); - Class baseClass = isTargetClass ? currentClass : currentClass.getSuperclass(); + Class baseClass = findBaseClass(data.getClass()); String className = baseClass.getSimpleName().toLowerCase(); Field[] fields = baseClass.getDeclaredFields(); for (Field field : fields) { diff --git a/openaev-model/src/main/java/io/openaev/database/model/ConnectorInstance.java b/openaev-model/src/main/java/io/openaev/database/model/ConnectorInstance.java index 14bfdafa46b..f8857ecfbc0 100644 --- a/openaev-model/src/main/java/io/openaev/database/model/ConnectorInstance.java +++ b/openaev-model/src/main/java/io/openaev/database/model/ConnectorInstance.java @@ -28,7 +28,8 @@ public enum CURRENT_STATUS_TYPE { public enum REQUESTED_STATUS_TYPE { starting, - stopping + stopping, + deleting } public enum SOURCE { @@ -83,6 +84,10 @@ public enum SOURCE { @JsonProperty("connector_instance_is_in_reboot_loop") private boolean isInRebootLoop; + @Column(name = "connector_instance_enable_deletion") + @JsonProperty("connector_instance_enable_deletion") + private boolean enableDeletion; + @OneToMany( mappedBy = "connectorInstance", fetch = FetchType.LAZY, diff --git a/openaev-model/src/main/java/io/openaev/database/repository/ConnectorInstanceRepository.java b/openaev-model/src/main/java/io/openaev/database/repository/ConnectorInstanceRepository.java index d557e35eda6..963b7224c62 100644 --- a/openaev-model/src/main/java/io/openaev/database/repository/ConnectorInstanceRepository.java +++ b/openaev-model/src/main/java/io/openaev/database/repository/ConnectorInstanceRepository.java @@ -17,8 +17,9 @@ public interface ConnectorInstanceRepository @Query( "SELECT DISTINCT instance FROM ConnectorInstance instance " + "WHERE instance.catalogConnector.containerImage IS NOT NULL " - + "AND instance.catalogConnector.isManagerSupported = TRUE") - List findAllManagedByXtmComposerAndConfiguration(); + + "AND instance.catalogConnector.isManagerSupported = TRUE " + + "AND instance.requestedStatus != 'deleting'") + List findAllActiveManagedByXtmComposerAndConfiguration(); List findAllByCatalogConnectorId(String catalogConnectorId);