This is an automated email from the ASF dual-hosted git repository.

zehnder pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 8e51d30779 Data Retention Export to  s3 based object storage (#3824)
8e51d30779 is described below

commit 8e51d30779d844a2ccede0ad32da5966022188a1
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Wed Oct 15 08:47:38 2025 +0200

    Data Retention Export to  s3 based object storage (#3824)
---
 .../apache/streampipes/commons/constants/Envs.java |   2 +
 .../commons/environment/DefaultEnvironment.java    |   4 +
 .../commons/environment/Environment.java           |   1 +
 streampipes-data-explorer-export/pom.xml           |  31 ++++
 .../export/ObjectStorge/ExportProviderFactory.java |  19 +-
 .../export/ObjectStorge/LocalFolder.java           |   7 +-
 .../dataexplorer/export/ObjectStorge/S3.java       |  86 +++++++++
 .../DefaultExportProviderConfig.java}              |  17 +-
 .../configuration/DefaultSpCoreConfiguration.java  |   1 +
 .../configuration/ExportProviderSettings.java      | 115 ++++++++++++
 .../ProviderType.java}                             |  10 +-
 .../model/configuration/SpCoreConfiguration.java   |  12 ++
 .../model/datalake/DataLakeMeasure.java            |   4 +
 .../model/datalake/RetentionExportConfig.java      |  28 ++-
 .../model/datalake/RetentionTimeConfig.java        |  35 +++-
 .../admin/ExportProviderConfigurationResource.java | 105 +++++++++++
 .../rest/impl/datalake/DataLakeResource.java       |  15 ++
 streampipes-service-core/pom.xml                   |   1 +
 .../service/core/scheduler/DataLakeScheduler.java  | 106 ++++++++---
 ui/deployment/i18n/de.json                         |  88 ++++++---
 ui/deployment/i18n/en.json                         |  94 +++++++---
 .../src/lib/apis/datalake-rest.service.ts          |   6 +-
 .../src/lib/apis/export-provider.service.ts        |  66 +++++++
 .../src/lib/model/gen/streampipes-model-client.ts  |   5 +-
 .../src/lib/model/gen/streampipes-model.ts         |  35 ++--
 ui/src/app/configuration/configuration.module.ts   |   4 +
 .../datalake-configuration.component.html          | 197 +++++++++++++++++++++
 .../datalake-configuration.component.ts            |  80 +++++++--
 .../select-export/select-format.component.html     |  91 ++++++++--
 .../select-export/select-format.component.scss     |  19 ++
 .../select-export/select-format.component.ts       |  93 +++++++++-
 .../data-retention-dialog.component.html           |  10 ++
 .../data-retention-dialog.component.ts             |  54 ++++--
 .../delete-export-provider-dialog.component.html   |  82 +++++++++
 .../delete-export-provider-dialog.component.ts     |  51 ++++++
 .../export-provider-dialog.component.html          | 121 +++++++++++++
 .../export-provider-dialog.component.ts            | 133 ++++++++++++++
 37 files changed, 1681 insertions(+), 147 deletions(-)

diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index bd06f7317a..49622cf9d1 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -142,6 +142,8 @@ public enum Envs {
 
   // Retention Local File 
   SP_RETENTION_LOCAL_DIR("SP_RETENTION_LOCAL_DIR", "./ArchivedData"),
+  SP_DATALAKE_SCHEDULER_CRON("SP_DATALAKE_SCHEDULER_CRON", "0 1 0 * * 6"),// 
CronJob Scheduled every Saturday (6) 00:01 //@Scheduled(cron = "0 */2 * * *
+                                     // *") //Cron Job in Dev Setting; Running 
every 2 min
 
   // Logging
   SP_LOGGING_FILE_ENABLED("SP_LOGGING_FILE_ENABLED", "false"),
diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
index c8f5f0686c..fc3e765b51 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
@@ -442,4 +442,8 @@ public class DefaultEnvironment implements Environment {
   public StringEnvironmentVariable getRetentionLocalDir() {
     return new StringEnvironmentVariable(Envs.SP_RETENTION_LOCAL_DIR);
   }
+  @Override
+  public StringEnvironmentVariable getDatalakeSchedulerCron() {
+    return new StringEnvironmentVariable(Envs.SP_DATALAKE_SCHEDULER_CRON);
+  }
 }
diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
index 07ed38c9df..a670ed71a2 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
@@ -168,4 +168,5 @@ public interface Environment {
   StringEnvironmentVariable getFileLoggingPattern();
 
   StringEnvironmentVariable getRetentionLocalDir();
+  StringEnvironmentVariable getDatalakeSchedulerCron();
 }
diff --git a/streampipes-data-explorer-export/pom.xml 
b/streampipes-data-explorer-export/pom.xml
index b0a7452c6a..303d34ac8c 100644
--- a/streampipes-data-explorer-export/pom.xml
+++ b/streampipes-data-explorer-export/pom.xml
@@ -78,6 +78,37 @@
             <artifactId>spring-webmvc</artifactId>
             <version>5.3.32</version> 
         </dependency>
+            <dependency>
+        <groupId>software.amazon.awssdk</groupId>
+        <artifactId>s3</artifactId>
+        <version>2.25.14</version> <!-- Use latest, 2.25.14--> 
+       <exclusions>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-native-unix-common</artifactId>
+        </exclusion>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http</artifactId>
+        </exclusion>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport</artifactId>
+        </exclusion>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-resolver</artifactId>
+        </exclusion>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-handler</artifactId>
+        </exclusion>
+        <exclusion>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-classes-epoll</artifactId> <!-- 
Exclude this one -->
+        </exclusion>
+    </exclusions>
+    </dependency>
 
         <!-- Test dependencies -->
         <dependency>
diff --git 
a/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/ExportProviderFactory.java
 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/ExportProviderFactory.java
index c4c515475d..bd0b64823d 100644
--- 
a/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/ExportProviderFactory.java
+++ 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/ExportProviderFactory.java
@@ -17,14 +17,25 @@
  */
 package org.apache.streampipes.dataexplorer.export.ObjectStorge;
 
-import org.apache.streampipes.model.datalake.ExportProviderSettings;
+import org.apache.streampipes.model.configuration.ExportProviderSettings;
+import org.apache.streampipes.model.configuration.ProviderType;
 
 public class ExportProviderFactory {
-    public static IObjectStorage createExportProvider(String providerType, 
String measurementName, ExportProviderSettings settings, String format) throws 
Exception {
+
+    
+    public static IObjectStorage createExportProvider(
+            ProviderType providerType, 
+            String measurementName, 
+            ExportProviderSettings settings, 
+            String format) throws Exception {
+
         switch (providerType) {
-            case "local":
+            case FOLDER: 
                 return new LocalFolder(measurementName, format);
-            //Additional Providers can be added here 
+                
+            case S3: 
+                return new S3(measurementName, format, settings); 
+
             default:
                 throw new IllegalArgumentException("Unsupported provider: " + 
providerType);
         }
diff --git 
a/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/LocalFolder.java
 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/LocalFolder.java
index eeb1e6ffec..1e59f24d9c 100644
--- 
a/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/LocalFolder.java
+++ 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/LocalFolder.java
@@ -17,6 +17,7 @@
  */
 package org.apache.streampipes.dataexplorer.export.ObjectStorge;
 
+import org.apache.streampipes.commons.environment.Environments;
 import 
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
 
 import java.io.FileOutputStream;
@@ -31,9 +32,11 @@ public class LocalFolder implements IObjectStorage {
 
     private final Path filePath;
 
-    public LocalFolder(String measurementName, String format) throws Exception 
{
+    public LocalFolder(String measurementName, String format) throws 
RuntimeException, IOException {
 
-        
Files.createDirectories(Paths.get(System.getenv("SP_RETENTION_LOCAL_DIR") + "/" 
+ measurementName));
+        var env = Environments.getEnvironment(); 
+
+        
Files.createDirectories(Paths.get(env.getRetentionLocalDir().getValueOrDefault()
 + "/" + measurementName));   
 
         this.filePath = Paths.get(System.getenv("SP_RETENTION_LOCAL_DIR") + 
"/" + measurementName + "/dump_"
                 + Instant.now().toString() + "." + format);
diff --git 
a/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/S3.java
 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/S3.java
new file mode 100644
index 0000000000..885e4f9e90
--- /dev/null
+++ 
b/streampipes-data-explorer-export/src/main/java/org/apache/streampipes/dataexplorer/export/ObjectStorge/S3.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.dataexplorer.export.ObjectStorge;
+
+
+import org.apache.streampipes.model.configuration.ExportProviderSettings;
+import 
org.apache.streampipes.user.management.encryption.SecretEncryptionManager;
+
+import 
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.time.Instant;
+
+
+public class S3 implements IObjectStorage{
+
+    private final String fileName;
+    private final S3Client s3;
+    private final String bucketName;
+
+    public S3(String measurementName, String format, ExportProviderSettings 
settings) throws RuntimeException {
+
+          this.s3 = S3Client.builder()
+                .endpointOverride(URI.create(settings.getEndPoint())) 
+                .region(Region.of(settings.getAwsRegion()))
+                .credentialsProvider(
+                        StaticCredentialsProvider.create(
+                                
AwsBasicCredentials.create(settings.getAccessKey(),  
SecretEncryptionManager.decrypt(settings.getSecretKey()))
+                        )
+                )
+                .build();
+         
+            this.bucketName = settings.getBucketName();
+            this.fileName = "/" + measurementName + "/dump_"
+                + Instant.now().toString() + "." + format; 
+
+    }
+
+    @Override
+    public void store(StreamingResponseBody datastream) throws IOException {
+
+        PutObjectRequest putObjectRequest = PutObjectRequest.builder()
+                .bucket(bucketName)
+                .key(fileName)  
+                .build();
+
+        ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
+
+   
+        datastream.writeTo(byteArrayOutputStream);
+
+   
+        byte[] data = byteArrayOutputStream.toByteArray();
+
+        RequestBody requestBody = RequestBody.fromBytes(data);
+
+        // Upload to S3
+        this.s3.putObject(putObjectRequest, requestBody);
+        this.s3.close();
+    }
+
+    
+}
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultExportProviderConfig.java
similarity index 70%
copy from 
streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
copy to 
streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultExportProviderConfig.java
index 43e1b2199f..de17db5e51 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultExportProviderConfig.java
@@ -16,10 +16,17 @@
  *
  */
 
-package org.apache.streampipes.model.datalake;
+package org.apache.streampipes.model.configuration;
 
-public record RetentionTimeConfig(
-    DataRetentionConfig dataRetentionConfig,
-    RetentionExportConfig exportConfig
-    ) {}
+import java.util.List;
 
+public class DefaultExportProviderConfig {
+
+public List<ExportProviderSettings> make() {
+
+    var defaultSettings = new ExportProviderSettings(
+       ProviderType.FOLDER, "FOLDER", "", "", "", "", "US_EAST_1");
+
+    return List.of(defaultSettings);
+  }
+}
\ No newline at end of file
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
index 0514006380..6522d7358c 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java
@@ -36,6 +36,7 @@ public class DefaultSpCoreConfiguration {
     coreCfg.setFilesDir(makeFileLocation());
     coreCfg.setAssetDir(makeAssetLocation());
     coreCfg.setLocalAuthConfig(LocalAuthConfig.fromDefaults(getJwtSecret()));
+    coreCfg.setExportProviderSettings(new 
DefaultExportProviderConfig().make());
 
     return coreCfg;
   }
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ExportProviderSettings.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ExportProviderSettings.java
new file mode 100644
index 0000000000..5feaeabb31
--- /dev/null
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ExportProviderSettings.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.model.configuration;
+
+import org.apache.streampipes.model.shared.annotation.TsModel;
+
+@TsModel
+public class ExportProviderSettings {
+
+    private boolean secretEncrypted;
+
+    private ProviderType providerType;
+    private String providerId;
+    private String accessKey;
+    private String secretKey;
+    private String bucketName;
+    private String endPoint;
+    private String awsRegion;
+    
+    public ExportProviderSettings(ProviderType providerType, String 
providerId, String accessKey, String secretKey, String bucketName, String 
endPoint, String awsRegion) {
+        this.providerType = providerType;
+        this.providerId = providerId;
+        this.accessKey = accessKey;
+        this.secretKey = secretKey;
+        this.bucketName = bucketName;
+        this.endPoint = endPoint;
+        this.awsRegion = awsRegion;
+    }
+    
+    public ProviderType getProviderType() {
+        return providerType;
+    }
+    public String getAwsRegion() {
+        return awsRegion;
+    }
+
+    public void setAwsRegion(String awsRegion) {
+        this.awsRegion = awsRegion;
+    }
+
+    public void setProviderType(ProviderType providerType) {
+        this.providerType = providerType;
+    }
+    public String getAccessKey() {
+        return accessKey;
+    }
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+    public String getSecretKey() {
+        return secretKey;
+    }
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+    public String getBucketName() {
+        return bucketName;
+    }
+    public void setBucketName(String bucketName) {
+        this.bucketName = bucketName;
+    }
+
+    public String getEndPoint() {
+        return endPoint;
+    }
+
+    public void setEndPoint(String endPoint) {
+        this.endPoint = endPoint;
+    }
+
+        public String getProviderId() {
+        return providerId;
+    }
+
+    public void setProviderId(String providerId) {
+        this.providerId = providerId;
+    }
+
+    public static ExportProviderSettings fromDefaults() {
+   ExportProviderSettings config = new ExportProviderSettings();
+    config.setSecretEncrypted(false);
+
+    return config;
+  }
+
+    public ExportProviderSettings() {
+
+  }
+
+  public boolean isSecretEncrypted() {
+    return secretEncrypted;
+  }
+
+  public void setSecretEncrypted(boolean secretEncrypted) {
+    this.secretEncrypted = secretEncrypted;
+  }
+}
+
+
+
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ExportProviderSettings.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ProviderType.java
similarity index 87%
rename from 
streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ExportProviderSettings.java
rename to 
streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ProviderType.java
index fab05be587..d46d648152 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/ExportProviderSettings.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/ProviderType.java
@@ -15,8 +15,10 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.model.datalake;
 
-public record ExportProviderSettings(
-      String providerType
-) {}
\ No newline at end of file
+package org.apache.streampipes.model.configuration;
+
+public enum ProviderType {
+    FOLDER,
+    S3;    
+}
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
index b5766bd3f6..788ecbf1e7 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java
@@ -20,6 +20,8 @@ package org.apache.streampipes.model.configuration;
 
 import com.google.gson.annotations.SerializedName;
 
+import java.util.List;
+
 public class SpCoreConfiguration {
 
   public static final String ID = "core";
@@ -40,6 +42,8 @@ public class SpCoreConfiguration {
   private String assetDir;
   private String filesDir;
 
+  private List<ExportProviderSettings> exportProviderSettings;
+
   public SpCoreConfiguration() {
     this.locationConfig = new LocationConfig(false, "", "");
   }
@@ -139,4 +143,12 @@ public class SpCoreConfiguration {
   public void setLocationConfig(LocationConfig locationConfig) {
     this.locationConfig = locationConfig;
   }
+
+  public List<ExportProviderSettings> getExportProviderSettings() {
+    return exportProviderSettings;
+  }
+
+  public void setExportProviderSettings(List<ExportProviderSettings> 
exportProviderSettings) {
+    this.exportProviderSettings = exportProviderSettings;
+  }
 }
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/DataLakeMeasure.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/DataLakeMeasure.java
index 50eefd8da3..ae832d38ea 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/DataLakeMeasure.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/DataLakeMeasure.java
@@ -146,6 +146,10 @@ public class DataLakeMeasure implements Storable {
     this.retentionTime = retentionTime;
   }
 
+  public void deleteRetentionTime() {
+    this.retentionTime = null;
+  }
+
   public RetentionTimeConfig getRetentionTime() {
     return retentionTime;
   }
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionExportConfig.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionExportConfig.java
index d1fbf544e3..f126a63744 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionExportConfig.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionExportConfig.java
@@ -17,8 +17,28 @@
  */
 
 package org.apache.streampipes.model.datalake;
+public class RetentionExportConfig {
+    
+    private ExportConfig exportConfig; 
+    private String exportProviderId;
 
-public record RetentionExportConfig(
-    ExportConfig exportConfig, 
-    ExportProviderSettings exportProviderSettings
-) {}
+    public RetentionExportConfig(ExportConfig exportConfig, String 
exportProviderId) {
+        this.exportConfig = exportConfig;
+        this.exportProviderId = exportProviderId;
+    }
+
+    public ExportConfig getExportConfig() {
+        return exportConfig;
+    }
+
+    public void setExportConfig(ExportConfig exportConfig) {
+        this.exportConfig = exportConfig;
+    }
+    public String getExportProviderId() {
+        return exportProviderId;
+    }
+
+    public void setExportProviderId(String exportProviderId) {
+        this.exportProviderId = exportProviderId;
+    }
+}
\ No newline at end of file
diff --git 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
index 43e1b2199f..1afb3327c2 100644
--- 
a/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/datalake/RetentionTimeConfig.java
@@ -18,8 +18,35 @@
 
 package org.apache.streampipes.model.datalake;
 
-public record RetentionTimeConfig(
-    DataRetentionConfig dataRetentionConfig,
-    RetentionExportConfig exportConfig
-    ) {}
 
+public class RetentionTimeConfig {
+
+    private DataRetentionConfig dataRetentionConfig;
+    private RetentionExportConfig retentionExportConfig;
+
+    public RetentionTimeConfig() {}
+
+
+    public RetentionTimeConfig(
+        DataRetentionConfig dataRetentionConfig,
+        RetentionExportConfig retentionExportConfig) {
+        this.dataRetentionConfig = dataRetentionConfig;
+        this.retentionExportConfig = retentionExportConfig;
+    }
+
+    public DataRetentionConfig getDataRetentionConfig() {
+        return dataRetentionConfig;
+    }
+
+    public void setDataRetentionConfig(DataRetentionConfig 
dataRetentionConfig) {
+        this.dataRetentionConfig = dataRetentionConfig;
+    }
+
+    public RetentionExportConfig getRetentionExportConfig() {
+        return retentionExportConfig;
+    }
+
+    public void setRetentionExportConfig(RetentionExportConfig exportConfig) {
+        this.retentionExportConfig = exportConfig;
+    }
+}
\ No newline at end of file
diff --git 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/ExportProviderConfigurationResource.java
 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/ExportProviderConfigurationResource.java
new file mode 100644
index 0000000000..774e9805be
--- /dev/null
+++ 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/ExportProviderConfigurationResource.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.rest.impl.admin;
+
+import org.apache.streampipes.model.configuration.ExportProviderSettings;
+import 
org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
+import org.apache.streampipes.rest.security.AuthConstants;
+import 
org.apache.streampipes.user.management.encryption.SecretEncryptionManager;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api/v2/admin/exportprovider-config")
+public class ExportProviderConfigurationResource extends 
AbstractAuthGuardedRestResource {
+
+  @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public ResponseEntity<List<ExportProviderSettings>> 
getExportProviderConfiguration() {
+    return 
ok(getSpCoreConfigurationStorage().get().getExportProviderSettings());
+  }
+
+  @GetMapping(value = "/{providerId}", produces = 
MediaType.APPLICATION_JSON_VALUE)
+@PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+public ResponseEntity<ExportProviderSettings> 
getExportProviderSettingById(@PathVariable String providerId) {
+    return 
getSpCoreConfigurationStorage().get().getExportProviderSettings().stream()
+        .filter(setting -> 
setting.getProviderId().equalsIgnoreCase(providerId))
+        .findFirst()
+        .map(ResponseEntity::ok)
+        .orElse(ResponseEntity.notFound().build());
+}
+
+  @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public ResponseEntity<Void> updateExportProviderConfiguration(@RequestBody 
ExportProviderSettings config) {
+
+    if (!config.isSecretEncrypted()) {
+      
config.setSecretKey(SecretEncryptionManager.encrypt(config.getSecretKey()));
+      config.setSecretEncrypted(true);
+    }
+    var storage = getSpCoreConfigurationStorage();
+    var cfg = storage.get();
+
+    List<ExportProviderSettings> providerSettings = 
cfg.getExportProviderSettings();
+    if (providerSettings == null) {
+      providerSettings = new ArrayList<>();
+    }
+
+    List<ExportProviderSettings> providerSettingsWithoutExisting = 
providerSettings.stream()
+    .filter(existing -> existing != null && 
!existing.getProviderId().equals(config.getProviderId()))
+    .collect(Collectors.toList());
+
+    providerSettingsWithoutExisting.add(config);
+
+    cfg.setExportProviderSettings(providerSettingsWithoutExisting);
+    storage.updateElement(cfg);
+
+    return ok();
+  }
+
+  @DeleteMapping(value = "/{providerId}", produces = 
MediaType.APPLICATION_JSON_VALUE)
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public ResponseEntity<Void> deleteExportProviderConfiguration(@PathVariable 
String providerId) {
+
+    List<ExportProviderSettings> allProviders = 
getSpCoreConfigurationStorage().get().getExportProviderSettings();
+
+    List<ExportProviderSettings> filteredProviders = allProviders.stream()
+        .filter(provider -> !provider.getProviderId().equals(providerId))
+        .collect(Collectors.toList());
+
+    var storage = getSpCoreConfigurationStorage();
+    var cfg = storage.get();
+    cfg.setExportProviderSettings(filteredProviders);
+    storage.updateElement(cfg);
+    return ok();
+  }
+
+}
diff --git 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeResource.java
 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeResource.java
index 66ffa2a82d..2b93b2476c 100644
--- 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeResource.java
+++ 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeResource.java
@@ -31,6 +31,7 @@ import 
org.apache.streampipes.model.datalake.param.ProvidedRestQueryParams;
 import org.apache.streampipes.model.message.Notifications;
 import org.apache.streampipes.model.monitoring.SpLogMessage;
 import org.apache.streampipes.rest.core.base.impl.AbstractRestResource;
+import org.apache.streampipes.rest.security.AuthConstants;
 import org.apache.streampipes.rest.shared.exception.SpMessageException;
 
 import io.swagger.v3.oas.annotations.Operation;
@@ -46,6 +47,7 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -405,6 +407,7 @@ public class DataLakeResource extends AbstractRestResource {
       path = "/{elementId}/cleanup",
       produces = MediaType.APPLICATION_JSON_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE)
+      @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
   @Operation(summary = "Sets the retention mechanism for a certain 
measurement", tags = {"Data Lake"},
       responses = {
           @ApiResponse(
@@ -427,6 +430,18 @@ public class DataLakeResource extends AbstractRestResource 
{
     return ok();
   }
 
+@DeleteMapping(path = "/{elementId}/cleanup")
+public ResponseEntity<?> deleteDataLakeRetention(@PathVariable String 
elementId) {
+    var measure = this.dataExplorerSchemaManagement.getById(elementId);
+    measure.deleteRetentionTime();
+    try {
+        this.dataExplorerSchemaManagement.updateMeasurement(measure);
+    } catch (IllegalArgumentException e) {
+        return badRequest(e.getMessage());
+    }
+    return ok();
+}
+
   private ProvidedRestQueryParams populate(String measurementId, Map<String, 
String> rawParams) {
     Map<String, String> queryParamMap = new HashMap<>();
     rawParams.forEach((key, value) -> queryParamMap.put(key, String.join(",", 
value)));
diff --git a/streampipes-service-core/pom.xml b/streampipes-service-core/pom.xml
index 938af5f5c2..ccffcc14d8 100644
--- a/streampipes-service-core/pom.xml
+++ b/streampipes-service-core/pom.xml
@@ -109,6 +109,7 @@
             <groupId>jakarta.annotation</groupId>
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
+    
       <!-- Test dependencies -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
diff --git 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/scheduler/DataLakeScheduler.java
 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/scheduler/DataLakeScheduler.java
index 753eb33950..61d08936c4 100644
--- 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/scheduler/DataLakeScheduler.java
+++ 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/scheduler/DataLakeScheduler.java
@@ -17,31 +17,37 @@
  */
 package org.apache.streampipes.service.core.scheduler;
 
+import org.apache.streampipes.commons.environment.Environments;
 import org.apache.streampipes.dataexplorer.api.IDataExplorerQueryManagement;
 import org.apache.streampipes.dataexplorer.api.IDataExplorerSchemaManagement;
 import 
org.apache.streampipes.dataexplorer.export.ObjectStorge.ExportProviderFactory;
 import org.apache.streampipes.dataexplorer.export.ObjectStorge.IObjectStorage;
 import org.apache.streampipes.dataexplorer.export.OutputFormat;
 import org.apache.streampipes.dataexplorer.management.DataExplorerDispatcher;
+import org.apache.streampipes.model.configuration.ExportProviderSettings;
+import org.apache.streampipes.model.configuration.ProviderType;
 import org.apache.streampipes.model.datalake.DataLakeMeasure;
-import org.apache.streampipes.model.datalake.ExportProviderSettings;
 import org.apache.streampipes.model.datalake.RetentionAction;
 import org.apache.streampipes.model.datalake.param.ProvidedRestQueryParams;
+import org.apache.streampipes.storage.management.StorageDispatcher;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+import org.springframework.scheduling.support.CronTrigger;
 import 
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
 
+import java.io.IOException;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-@Component
-public class DataLakeScheduler {
+@Configuration
+public class DataLakeScheduler implements SchedulingConfigurer {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(DataLakeScheduler.class);
 
@@ -60,16 +66,18 @@ public class DataLakeScheduler {
         }
 
         var outputFormat = OutputFormat
-                
.fromString(dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().format());
+                
.fromString(dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig().format());
 
         Map<String, String> params = new HashMap<>();
 
-        params.put("delimiter", 
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().csvDelimiter());
-        params.put("format", 
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().format());
+        params.put("delimiter",
+                
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig().csvDelimiter());
+        params.put("format", 
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig().format());
         params.put("headerColumnName",
-                
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().headerColumnName());
+                
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig().headerColumnName());
         params.put("missingValueBehaviour",
-                
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().missingValueBehaviour());
+                
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig()
+                        .missingValueBehaviour());
         params.put("endDate", Long.toString(endDate));
 
         ProvidedRestQueryParams sanitizedParams = new 
ProvidedRestQueryParams(dataLakeMeasure.getMeasureName(), params);
@@ -77,23 +85,60 @@ public class DataLakeScheduler {
                 sanitizedParams,
                 outputFormat,
                 "ignore".equals(
-                        
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().missingValueBehaviour()),
+                        
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig()
+                                .missingValueBehaviour()),
                 output);
-        try {
-            ExportProviderSettings exportProviderSettings = 
dataLakeMeasure.getRetentionTime().exportConfig()
-                    .exportProviderSettings();
 
-            String providerType = exportProviderSettings.providerType();
+        String exportProviderId = 
dataLakeMeasure.getRetentionTime().getRetentionExportConfig()
+                .getExportProviderId();
+        // FInd Item in Document
+
+        List<ExportProviderSettings> exportProviders = 
StorageDispatcher.INSTANCE
+                .getNoSqlStore()
+                .getSpCoreConfigurationStorage()
+                .get()
+                .getExportProviderSettings();
+
+        ExportProviderSettings exportProviderSetting = null;
+
+        for (int i = 0; i < exportProviders.size(); i++) {
+            ExportProviderSettings existing = exportProviders.get(i);
+            if (existing != null && 
existing.getProviderId().equals(exportProviderId)) {
+                exportProviderSetting = existing;
+            }
+        }
 
-            LOG.info("Write to " + System.getenv("SP_RETENTION_LOCAL_DIR"));
+        if (exportProviderSetting == null) {
+            LOG.error("The desired export provider was not found. No export 
has been done.");
+            return;
+        }
+
+        ProviderType providerType = exportProviderSetting.getProviderType();
+
+        LOG.info("Write to " + System.getenv("SP_RETENTION_LOCAL_DIR"));
+
+        try {
 
             IObjectStorage exportProvider = 
ExportProviderFactory.createExportProvider(
-                    providerType, dataLakeMeasure.getMeasureName(), 
exportProviderSettings,
-                    
dataLakeMeasure.getRetentionTime().exportConfig().exportConfig().format());
+                    providerType, dataLakeMeasure.getMeasureName(), 
exportProviderSetting,
+                    
dataLakeMeasure.getRetentionTime().getRetentionExportConfig().getExportConfig().format());
             exportProvider.store(streamingOutput);
 
+        } catch (IllegalArgumentException e) {
+
+            LOG.error("Export provider could not be created. Unsupported 
provider type: {}. Error: {}", providerType,
+                    e.getMessage(), e);
+        } catch (IOException e) {
+
+            LOG.error("I/O error occurred while trying to store data. Provider 
Type: {}. Error: {}", providerType,
+                    e.getMessage(), e);
+        } catch (RuntimeException e) {
+            LOG.error("Runtime exception occurred while attempting to store 
data. Provider Type: {}. Error: {}",
+                    providerType, e.getMessage(), e);
         } catch (Exception e) {
-            e.printStackTrace();
+
+            LOG.error("An unexpected error occurred during export. Provider 
Type: {}. Error: {}", providerType,
+                    e.getMessage(), e);
         }
     }
 
@@ -117,8 +162,6 @@ public class DataLakeScheduler {
         return result;
     }
 
-    @Scheduled(cron = "0 1 0 * * 6") // CronJob Scheduled every Saturday (6) 
00:01 //@Scheduled(cron = "0 */2 * * *
-                                     // *") //Cron Job in Dev Setting; Running 
every 2 min
     public void cleanupMeasurements() {
         List<DataLakeMeasure> allMeasurements = 
this.dataExplorerSchemaManagement.getAllMeasurements();
         LOG.info("GET ALL Measurements");
@@ -127,16 +170,16 @@ public class DataLakeScheduler {
             if (dataLakeMeasure.getRetentionTime() != null) {
 
                 var result = getStartAndEndTime(
-                        
dataLakeMeasure.getRetentionTime().dataRetentionConfig().olderThanDays());
+                        
dataLakeMeasure.getRetentionTime().getDataRetentionConfig().olderThanDays());
                 Instant now = (Instant) result.get("now");
                 long endDate = (Long) result.get("endDate");
 
-                if 
(dataLakeMeasure.getRetentionTime().dataRetentionConfig().action() != 
RetentionAction.DELETE) {
+                if 
(dataLakeMeasure.getRetentionTime().getDataRetentionConfig().action() != 
RetentionAction.DELETE) {
                     LOG.info("Start saving Measurement " + 
dataLakeMeasure.getMeasureName());
                     exportMeasurement(dataLakeMeasure, now, endDate);
                     LOG.info("Measurements " + 
dataLakeMeasure.getMeasureName() + " successfully saved");
                 }
-                if 
(dataLakeMeasure.getRetentionTime().dataRetentionConfig().action() != 
RetentionAction.SAVE) {
+                if 
(dataLakeMeasure.getRetentionTime().getDataRetentionConfig().action() != 
RetentionAction.SAVE) {
                     LOG.info("Start delete Measurement " + 
dataLakeMeasure.getMeasureName());
                     deleteMeasurement(dataLakeMeasure, now, endDate);
                     LOG.info("Measurements " + 
dataLakeMeasure.getMeasureName() + " successfully deleted");
@@ -144,4 +187,19 @@ public class DataLakeScheduler {
             }
         }
     }
+
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        var env = Environments.getEnvironment(); 
+        taskRegistrar.addTriggerTask(
+
+                this::cleanupMeasurements,
+
+
+                triggerContext -> new 
CronTrigger(env.getDatalakeSchedulerCron().getValueOrDefault())
+                        .nextExecution(triggerContext)
+
+        );
+
+    }
 }
\ No newline at end of file
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index 840825bac3..30854ad724 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -32,9 +32,10 @@
   "Stop pipeline": "Pipeline stoppen",
   "Name": "Name",
   "Last modified": "Zuletzt geändert",
-  "Show pipeline": "Pipeline anzeigen",
-  "Modify pipeline": "Pipeline ändern",
-  "Permissions": "Berechtigungen",
+  "Show": "anzeigen",
+  "Edit": "Bearbeiten",
+  "Manage permissions": "Berechtigungen verwalten",
+  "Delete": "Löschen",
   "no log messages available": "keine Logs verfügbar",
   "Data Preview": "Datenvorschau",
   "Enable live preview": "Live-Vorschau aktivieren",
@@ -49,6 +50,7 @@
   "Start Pipeline": "Pipeline starten",
   "Stop Pipeline": "Pipeline stoppen",
   "Modify Pipeline": "Pipeline bearbeiten",
+  "Modify pipeline": "Pipeline ändern",
   "Delete Pipeline": "Pipeline löschen",
   "Preparing pipeline editor...": "Pipeline-Editor vorbereiten...",
   "The tutorial requires pipeline elements that are not yet installed.": "Für 
das Lernprogramm werden Pipeline-Elemente benötigt, die noch nicht installiert 
sind.",
@@ -72,7 +74,6 @@
   "Create new source": "Neue Quelle erstellen",
   "Sort": "Sortieren",
   "Group": "Gruppe",
-  "Save Pipeline": "Pipeline speichern",
   "Save pipeline": "Pipeline speichern",
   "Auto Layout": "Auto-Layout",
   "Add pipeline element": "Pipeline-Element hinzufügen",
@@ -192,9 +193,7 @@
   "New chart": "Neues Diagramm",
   "Chart": "Diagramm",
   "Created": "Erstellt",
-  "Show chart": "Diagramm anzeigen",
   "Edit chart": "Diagramm bearbeiten",
-  "Manage permissions": "Berechtigungen verwalten",
   "Clone chart": "Diagramm kopieren",
   "Delete chart": "Diagramm löschen",
   "Chart Name": "Diagrammname",
@@ -254,11 +253,17 @@
   "Default view mode": "Standard-Ansicht",
   "Grid view": "Rasteransicht",
   "Slide view": "Folienansicht",
+  "Grid": "Raster",
+  "Grid columns": "Gitternetz-Spalten",
   "Time settings": "Zeiteinstellungen",
   "Use global time settings instead of chart time settings": "Einheitliche 
Zeiteinstellungen anstelle von Diagramm-Zeiteinstellungen verwenden",
+  "New dashboard title": "Neuer Dashboard-Titel",
+  "Clone options": "Optionen zum Klonen",
+  "Deep clone (also clone widgets)": "Tiefes Klonen (auch Widgets klonen)",
+  "Modify chart configurations": "Ändern von Diagrammkonfigurationen",
+  "Clone": "Klonen",
   "This dashboard is empty and doesn't contain any charts.": "Dieses Dashboard 
ist leer und hat keine Diagramm zum Anzeigen.",
   "View mode": "Ansicht",
-  "Grid": "Raster",
   "Slides": "Folien",
   "Options": "Optionen",
   "Edit dashboard": "Dashboard bearbeiten",
@@ -270,9 +275,7 @@
   "Create chart": "Diagramm erstellen",
   "New dashboard": "Neues Dashboard",
   "Dashboards": "Dashboards",
-  "Show dashboard": "Dashboard anzeigen",
   "Kiosk mode": "Kiosk-Modus",
-  "Dashboard settings": "Dashboard-Einstellungen",
   "ID": "ID",
   "Output Topics": "Output-Topics",
   "Copy": "Kopieren",
@@ -324,6 +327,27 @@
   "Allow anonymous access through public link": "Anonymen Zugang über einen 
öffentlichen Link ermöglichen",
   "URL": "URL",
   "(no log messages available)": "(keine Protokollmeldungen verfügbar)",
+  "New adapter": "Neuer Adapter",
+  "Start all adapters": "Alle Adapter starten",
+  "Stop all adapters": "Alle Adapter anhalten",
+  "Messages": "Nachrichten",
+  "Last message": "Letzte Nachricht",
+  "Add to Asset": "Zum Asset hinzufügen",
+  "Add Adapter to an existing Asset": "Adapter zu einem bestehenden Asset 
hinzufügen",
+  "Remove Duplicates": "Duplikate entfernen",
+  "Avoid duplicated events within a certain time interval": "Vermeidung von 
doppelten Ereignissen innerhalb eines bestimmten Zeitintervalls",
+  "Remove Duplicates Time Window": "Duplikate entfernen Zeitfenster",
+  "Reduce event rate": "Ereignisrate reduzieren",
+  "Send maximum one event in the specified time window": "Maximal ein Ereignis 
im angegebenen Zeitfenster senden",
+  "Time Window (Milliseconds)": "Zeitfenster (Millisekunden)",
+  "Event Aggregation": "Ereignis-Aggregation",
+  "Persist events": "Ereignisse aufrechterhalten",
+  "Store all events of this source in the internal data store": "Speichern 
aller Ereignisse dieser Quelle im internen Datenspeicher",
+  "Select Time Field": "Zeitfeld auswählen",
+  "Show code": "Code anzeigen",
+  "Show code to programmatically deploy this adapter over the API": "Code 
anzeigen, um diesen Adapter programmatisch über die API einzusetzen",
+  "Start Adapter": "Start Adapter",
+  "Update Adapter": "Adapter aktualisieren",
   "Sites & Areas": "Standorte & Bereiche",
   "Manage your organization's sites and production areas": "Verwaltung der 
Standorte und Produktionsbereiche",
   "New site": "Neuer Standort",
@@ -352,12 +376,11 @@
   "Group name": "Name der Benutzergruppe",
   "Group ID": "Gruppen-ID",
   "Edit user": "Benutzer bearbeiten",
-  "Edit": "Bearbeiten",
   "Delete service": "Service löschen",
-  "Delete": "Löschen",
   "New User": "Neuer Benutzer",
   "Email": "E-Mail",
   "Full Name": "Vor- und Nachname",
+  "Last Login": "Letzte Anmeldung",
   "Delete user": "Benutzer löschen",
   "New Service Account": "Neues Service-Konto",
   "Username": "Benutzername",
@@ -455,7 +478,7 @@
   "Issuer": "Aussteller",
   "Expires": "Läuft aus",
   "Certificate Details": "Zertifikat-Details",
-  "Details": "Details",
+  "Details": "Einzelheiten",
   "Reject": "Ablehnen",
   "Trust": "Vertrauen",
   "Delete certificate": "Zertifikat löschen",
@@ -548,16 +571,28 @@
   "install": "installieren",
   "uninstall": "deinstallieren",
   "Press 'Next' to start the process.": "Auf \"Weiter\" drücken, um den 
Vorgang zu starten.",
-  "{{action}} {{current}} of {{total}} ({{name}})...{{status}}": "{{action}} 
{{current}} von {{total}} ({{name}})...{{status}}",
+  "{{action}} {{ current }} of {{ total }} ({{ name }})...{{ status }}": 
"{{Aktion}} {{ current }} von {{ total }} ({{ Name }})...{{ Status }}",
   "Installing": "Installation von",
   "Uninstalling": "Deinstallation von",
   "More Details:": "Mehr Details:",
   "Make available to all users with appropriate role": "Allen Benutzern mit 
entsprechender Rolle zur Verfügung stellen",
-  "Do you really want to delete the data index {{index}}?": "Den Datenindex 
{{index}} wirklich löschen?",
-  "Do you really want to truncate the data in {{index}}?": "Die Daten in 
{{index}} wirklich leeren?",
+  "Select Provider": "Anbieter auswählen",
+  "S3": "S3",
+  "Access Key": "Access Key",
+  "Enter Access Key": "Access Key eingeben",
+  "Secret Key": "Secret Key",
+  "Enter Secret Key": "Secret Key eingeben",
+  "Endpoint": "Endpunkt",
+  "Enter Endpoint": "Endpunkt eingeben",
+  "Invalid URI format.": "Ungültiges URI-Format.",
+  "Bucket": "Bucket",
+  "Enter Bucket Name": "Bucketnamen eingeben",
+  "Region": "Region",
+  "Select Region": "Region auswählen",
   "Truncate Data": "Daten leeren",
   "Delete Data": "Daten löschen",
   "Start Sync": "Synchronisierung starten",
+  "Delete Sync": "Sync löschen",
   "Data Retention Action": "Aktion",
   "delete": "löschen",
   "save": "speichern",
@@ -568,8 +603,9 @@
   "Export Settings": "Export-Einstellungen",
   "Download Format": "Download-Format",
   "Delimiter": "Trennzeichen",
-  "Select Provider": "Anbieter auswählen",
-  "local": "lokal",
+  "Export Provider": "Anbieter exportieren",
+  "Select Provider Type": "Anbietertyp auswählen",
+  "No export providers found. Please create one first.": "Keine Exportanbieter 
gefunden. Bitte erstellen Sie zuerst einen.",
   "Data Lake Settings": "Data Lake Einstellungen",
   "Manage persisted data streams": "Verwalten von gespeicherten Datenströmen",
   "Existing data lake indices": "Bestehende Data-Lake-Indizes",
@@ -584,6 +620,14 @@
   "Retention Rate": "Speicherrichtlinie",
   "Set retention rate": "Speicherrichtlinie festlegen",
   "(no stored measurements)": "(keine gespeicherten Measurements)",
+  "Export Providers": "Exportanbieter",
+  "Add, Edit, and Delete export providers used for backing up data lakes.": 
"Hinzufügen, Bearbeiten und Löschen von Exportanbietern, die für die Sicherung 
von Data Lakes verwendet werden.",
+  "Existing Export Providers": "Bestehende Exportanbieter",
+  "New Export Provider": "Neuer Exportanbieter",
+  "Provider Type": "Anbieter Typ",
+  "Edit Export Provider": "Exportanbieter bearbeiten",
+  "Remove export provider configuration": "Konfiguration des Exportanbieters 
entfernen",
+  "no stored export providers": "keine gespeicherten Exportanbieter",
   "success": "Erfolg",
   "error": "Fehler",
   "waiting": "Warten",
@@ -625,7 +669,6 @@
   "Save changes?": "Änderungen speichern?",
   "Update all changes to chart or discard current changes.": "Aktualisieren 
Sie alle Änderungen im Diagramm oder verwerfen Sie die aktuellen Änderungen.",
   "Discard changes": "Änderungen verwerfen",
-  "Update": "Aktualisieren",
   "Millisecond": "Millisekunde",
   "Second": "Sekunde",
   "Minute": "Minute",
@@ -636,9 +679,9 @@
   "Off": "Aus",
   "Are you sure you want to delete this dashboard?": "Sind Sie sicher, dass 
Sie dieses Dashboard löschen möchten?",
   "This action cannot be undone!": "Diese Aktion kann nicht rückgängig gemacht 
werden!",
+  "Clone dashboard": "Dashboard klonen",
   "Topics": "Themen",
   "Code": "Code",
-  "Loading": "Laden",
   "# Provide OPC UA Node IDs below, one per line.\n# Format: 
ns=<namespace>;s=<node_id> (e.g., ns=3;s=SampleNodeId)\n": "# Geben Sie unten 
OPC UA Node-IDs ein, eine pro Zeile.\n# Format: ns=<namespace>;s=<node_id> (z. 
B. ns=3;s=SampleNodeId)\n",
   "The value should be a number": "Der Wert sollte eine Zahl sein",
   "Please enter a valid URL": "Bitte geben Sie eine gültige URL ein",
@@ -647,6 +690,11 @@
   "This is a test": "Dies ist ein Test",
   "Error in line {{rowNumber}}. Value for \"{{property}}\" is not supported.": 
"Fehler in Zeile {{rowNumber}}. Wert für \"{{property}}\" wird nicht 
unterstützt.",
   "Error in line {{rowNumber}}. Value for \"{{property}}\" is not set.": 
"Fehler in Zeile {{rowNumber}}. Wert für \"{{property}}\" ist nicht gesetzt.",
+  "Resources": "Ressourcen",
+  "Updating adapter {{adapterName}}": "Aktualisieren des Adapters 
{{AdapterName}}",
+  "Creating adapter {{adapterName}}": "Adapter erstellen {{adapterName}}",
+  "Starting adapter {{adapterName}}": "Adapter starten {{AdapterName}}",
+  "Your {{assetTypes}} were successfully added to {{assetIds}}.": "Ihre 
{{assetTypes}} wurden erfolgreich zu {{assetIds}} hinzugefügt.",
   "General": "Allgemein",
   "Data Lake": "Data Lake",
   "Export/Import": "Export/Import",
@@ -720,7 +768,6 @@
   "Refresh interval": "Aktualisierungsintervall",
   "No entries available.": "Keine Einträge vorhanden.",
   "Error": "Fehler",
-  "Details": "Einzelheiten",
   "Probable cause": "Wahrscheinliche Ursache",
   "No more information": "Keine weiteren Informationen",
   "Full details": "Alle Einzelheiten",
@@ -771,7 +818,6 @@
   "Preview": "Vorschau",
   "Documentation": "Dokumentation",
   "Error Details": "Fehler-Details",
-  "Resources": "Ressourcen",
   "All {{allResourcesAlias}}": "Alle {{allResourcesAlias}}",
   "{{ widgetTitle }} Clone": "{{ widgetTitle }} Kopie",
   "{{action}} {{ current }} of {{ total }} ({{ name }})...{{ status }}": 
"{{action}} {{ current }} von {{ total }} ({{name}})...{{ status }}"
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index 80d555a267..aa7fe2af2f 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -32,9 +32,10 @@
   "Stop pipeline": null,
   "Name": null,
   "Last modified": null,
-  "Show pipeline": null,
-  "Modify pipeline": null,
-  "Permissions": null,
+  "Show": null,
+  "Edit": null,
+  "Manage permissions": null,
+  "Delete": null,
   "no log messages available": null,
   "Data Preview": null,
   "Enable live preview": null,
@@ -49,6 +50,7 @@
   "Start Pipeline": null,
   "Stop Pipeline": null,
   "Modify Pipeline": null,
+  "Modify pipeline": null,
   "Delete Pipeline": null,
   "Preparing pipeline editor...": null,
   "The tutorial requires pipeline elements that are not yet installed.": null,
@@ -72,7 +74,6 @@
   "Create new source": null,
   "Sort": null,
   "Group": null,
-  "Save Pipeline": null,
   "Save pipeline": null,
   "Auto Layout": null,
   "Add pipeline element": null,
@@ -192,9 +193,7 @@
   "New chart": null,
   "Chart": null,
   "Created": null,
-  "Show chart": null,
   "Edit chart": null,
-  "Manage permissions": null,
   "Clone chart": null,
   "Delete chart": null,
   "Chart Name": null,
@@ -254,11 +253,17 @@
   "Default view mode": null,
   "Grid view": null,
   "Slide view": null,
+  "Grid": null,
+  "Grid columns": null,
   "Time settings": null,
   "Use global time settings instead of chart time settings": null,
+  "New dashboard title": null,
+  "Clone options": null,
+  "Deep clone (also clone widgets)": null,
+  "Modify chart configurations": null,
+  "Clone": null,
   "This dashboard is empty and doesn't contain any charts.": null,
   "View mode": null,
-  "Grid": null,
   "Slides": null,
   "Options": null,
   "Edit dashboard": null,
@@ -270,9 +275,7 @@
   "Create chart": null,
   "New dashboard": null,
   "Dashboards": null,
-  "Show dashboard": null,
   "Kiosk mode": null,
-  "Dashboard settings": null,
   "ID": null,
   "Output Topics": null,
   "Copy": null,
@@ -324,6 +327,27 @@
   "Allow anonymous access through public link": null,
   "URL": null,
   "(no log messages available)": null,
+  "New adapter": null,
+  "Start all adapters": null,
+  "Stop all adapters": null,
+  "Messages": null,
+  "Last message": null,
+  "Add to Asset": null,
+  "Add Adapter to an existing Asset": null,
+  "Remove Duplicates": null,
+  "Avoid duplicated events within a certain time interval": null,
+  "Remove Duplicates Time Window": null,
+  "Reduce event rate": null,
+  "Send maximum one event in the specified time window": null,
+  "Time Window (Milliseconds)": null,
+  "Event Aggregation": null,
+  "Persist events": null,
+  "Store all events of this source in the internal data store": null,
+  "Select Time Field": null,
+  "Show code": null,
+  "Show code to programmatically deploy this adapter over the API": null,
+  "Start Adapter": null,
+  "Update Adapter": null,
   "Sites & Areas": null,
   "Manage your organization's sites and production areas": null,
   "New site": null,
@@ -352,12 +376,11 @@
   "Group name": null,
   "Group ID": null,
   "Edit user": null,
-  "Edit": null,
   "Delete service": null,
-  "Delete": null,
   "New User": null,
   "Email": null,
   "Full Name": null,
+  "Last Login": null,
   "Delete user": null,
   "New Service Account": null,
   "Username": null,
@@ -463,9 +486,9 @@
   "Uninstall selected": null,
   "All": null,
   "Adapters": null,
-  "Streams": null,
+  "Data Streams": null,
   "Data Processors": null,
-  "Sinks": null,
+  "Data Sinks": null,
   "Find Element": null,
   "Available & Installed": null,
   "Installed": null,
@@ -490,7 +513,6 @@
   "Upload application package file": null,
   "Select resources to import": null,
   "Assets": null,
-  "Data Streams": null,
   "Data Lake Storage": null,
   "Pipelines": null,
   "Import options": null,
@@ -531,7 +553,7 @@
   "You can set various placeholder variables that will be replaced with the 
actual values when sending an email:": null,
   "Save changes": null,
   "Choose a name for your site": null,
-  "Site label is required": null,
+  "Site name is required": null,
   "Available areas within the site (e.g. plants or facilities)": null,
   "Location": null,
   "Exact location of the site": null,
@@ -549,16 +571,28 @@
   "install": null,
   "uninstall": null,
   "Press 'Next' to start the process.": null,
-  "{{action}} {{current}} of {{total}} ({{name}})...{{status}}": "{{action}} 
{{current}} of {{total}} ({{name}})...{{status}}",
+  "{{action}} {{ current }} of {{ total }} ({{ name }})...{{ status }}": null,
   "Installing": null,
   "Uninstalling": null,
   "More Details:": null,
   "Make available to all users with appropriate role": null,
-  "Do you really want to delete the data index {{index}}?": "Do you really 
want to delete the data index {{index}}?",
-  "Do you really want to truncate the data in {{index}}?": "Do you really want 
to truncate the data in {{index}}?",
+  "Select Provider": null,
+  "S3": null,
+  "Access Key": null,
+  "Enter Access Key": null,
+  "Secret Key": null,
+  "Enter Secret Key": null,
+  "Endpoint": null,
+  "Enter Endpoint": null,
+  "Invalid URI format.": null,
+  "Bucket": null,
+  "Enter Bucket Name": null,
+  "Region": null,
+  "Select Region": null,
   "Truncate Data": null,
   "Delete Data": null,
   "Start Sync": null,
+  "Delete Sync": null,
   "Data Retention Action": null,
   "delete": null,
   "save": null,
@@ -569,8 +603,9 @@
   "Export Settings": null,
   "Download Format": null,
   "Delimiter": null,
-  "Select Provider": null,
-  "local": null,
+  "Export Provider": null,
+  "Select Provider Type": null,
+  "No export providers found. Please create one first.": null,
   "Data Lake Settings": null,
   "Manage persisted data streams": null,
   "Existing data lake indices": null,
@@ -585,6 +620,14 @@
   "Retention Rate": null,
   "Set retention rate": null,
   "(no stored measurements)": null,
+  "Export Providers": null,
+  "Add, Edit, and Delete export providers used for backing up data lakes.": 
null,
+  "Existing Export Providers": null,
+  "New Export Provider": null,
+  "Provider Type": null,
+  "Edit Export Provider": null,
+  "Remove export provider configuration": null,
+  "no stored export providers": null,
   "success": null,
   "error": null,
   "waiting": null,
@@ -626,7 +669,6 @@
   "Save changes?": null,
   "Update all changes to chart or discard current changes.": null,
   "Discard changes": null,
-  "Update": null,
   "Millisecond": null,
   "Second": null,
   "Minute": null,
@@ -637,9 +679,9 @@
   "Off": null,
   "Are you sure you want to delete this dashboard?": null,
   "This action cannot be undone!": null,
+  "Clone dashboard": null,
   "Topics": null,
   "Code": null,
-  "Loading": null,
   "# Provide OPC UA Node IDs below, one per line.\n# Format: 
ns=<namespace>;s=<node_id> (e.g., ns=3;s=SampleNodeId)\n": null,
   "The value should be a number": null,
   "Please enter a valid URL": null,
@@ -648,6 +690,11 @@
   "This is a test": null,
   "Error in line {{rowNumber}}. Value for \"{{property}}\" is not supported.": 
"Error in line {{rowNumber}}. Value for \"{{property}}\" is not supported.",
   "Error in line {{rowNumber}}. Value for \"{{property}}\" is not set.": 
"Error in line {{rowNumber}}. Value for \"{{property}}\" is not set.",
+  "Resources": null,
+  "Updating adapter {{adapterName}}": "Updating adapter {{adapterName}}",
+  "Creating adapter {{adapterName}}": "Creating adapter {{adapterName}}",
+  "Starting adapter {{adapterName}}": "Starting adapter {{adapterName}}",
+  "Your {{assetTypes}} were successfully added to {{assetIds}}.": "Your 
{{assetTypes}} were successfully added to {{assetIds}}.",
   "General": null,
   "Data Lake": null,
   "Export/Import": null,
@@ -717,12 +764,10 @@
   "Ignore lines with missing value": null,
   "Leave entry empty": null,
   "Download successful": null,
-  "Refresh": null,
   "Apply": null,
   "Refresh interval": null,
   "No entries available.": null,
   "Error": null,
-  "Details": null,
   "Probable cause": null,
   "No more information": null,
   "Full details": null,
@@ -773,7 +818,6 @@
   "Preview": null,
   "Documentation": null,
   "Error Details": null,
-  "Resources": null,
   "All {{allResourcesAlias}}": "All {{allResourcesAlias}}",
   "{{ widgetTitle }} Clone": "{{ widgetTitle }} Clone",
   "Your {{assetTypes}} were successfully added to {{assetIds}}.": "Your 
{{assetTypes}} were successfully added to {{assetIds}}.",
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
 
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
index fc47f133ea..e5eeb66950 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
@@ -163,7 +163,6 @@ export class DatalakeRestService {
 
     cleanup(index: string, config: any) {
         const url = `${this.dataLakeUrl}/${index}/cleanup`;
-
         const request = new HttpRequest('POST', url, config, {
             headers: new HttpHeaders({ 'Content-Type': 'application/json' }), 
// optional if already handled globally
         });
@@ -171,6 +170,11 @@ export class DatalakeRestService {
         return this.http.request(request);
     }
 
+    deleteCleanup(index: string) {
+        const url = `${this.dataLakeUrl}/${index}/cleanup`;
+        return this.http.delete(url);
+    }
+
     buildDownloadRequest(index: string, queryParams: any) {
         const url = this.dataLakeUrl + '/measurements/' + index + '/download';
         const request = new HttpRequest('GET', url, {
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/apis/export-provider.service.ts
 
b/ui/projects/streampipes/platform-services/src/lib/apis/export-provider.service.ts
new file mode 100644
index 0000000000..b59b57d55c
--- /dev/null
+++ 
b/ui/projects/streampipes/platform-services/src/lib/apis/export-provider.service.ts
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { inject, Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { PlatformServicesCommons } from './commons.service';
+import { ExportProviderSettings } from '@streampipes/platform-services';
+
+@Injectable({
+    providedIn: 'root',
+})
+export class ExportProviderService {
+    private http = inject(HttpClient);
+    private platformServicesCommons = inject(PlatformServicesCommons);
+
+    getAllExportProviders(): Observable<ExportProviderSettings[]> {
+        return this.http.get<ExportProviderSettings[]>(
+            this.exportProviderBasePath,
+        );
+    }
+    getExportProviderById(
+        providerId: string,
+    ): Observable<ExportProviderSettings> {
+        return this.http.get<ExportProviderSettings>(
+            `${this.exportProviderBasePath}/${providerId}`,
+        );
+    }
+
+    updateExportProvider(
+        exportProviderSettings: ExportProviderSettings,
+    ): Observable<ExportProviderSettings> {
+        return this.http.put<ExportProviderSettings>(
+            `${this.exportProviderBasePath}`,
+            exportProviderSettings,
+        );
+    }
+
+    deleteExportProvider(exportProviderId: string): Observable<void> {
+        return this.http.delete<void>(
+            `${this.exportProviderBasePath}/${exportProviderId}`,
+        );
+    }
+
+    private get exportProviderBasePath() {
+        return (
+            this.platformServicesCommons.apiBasePath +
+            '/admin/exportprovider-config'
+        );
+    }
+}
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
index 47ae72ca3e..55adefc997 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
@@ -16,13 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-10-01 
08:36:56.
+// Generated using typescript-generator version 3.2.1263 on 2025-10-09 
16:21:15.
 
-import { Storable } from './streampipes-model';
+import { Storable } from './platform-services';
 
 export class Group implements Storable {
     alternateIds: string[];
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index 6823c795ec..ebe29936c7 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -16,11 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-10-01 
08:36:50.
+// Generated using typescript-generator version 3.2.1263 on 2025-10-09 
16:21:10.
 
 export class NamedStreamPipesEntity implements Storable {
     '@class':
@@ -1162,6 +1161,7 @@ export class DashboardModel implements Storable, 
SpResource {
     description: string;
     displayHeader: boolean;
     elementId: string;
+    gridColumns: number;
     id: string;
     metadata: ResourceMetadata;
     name: string;
@@ -1189,6 +1189,7 @@ export class DashboardModel implements Storable, 
SpResource {
         instance.description = data.description;
         instance.displayHeader = data.displayHeader;
         instance.elementId = data.elementId;
+        instance.gridColumns = data.gridColumns;
         instance.id = data.id;
         instance.metadata = ResourceMetadata.fromData(data.metadata);
         instance.name = data.name;
@@ -1875,7 +1876,14 @@ export class ExportItem {
 }
 
 export class ExportProviderSettings {
-    providerType: string;
+    accessKey: string;
+    awsRegion: string;
+    bucketName: string;
+    endPoint: string;
+    providerId: string;
+    providerType: ProviderType;
+    secretEncrypted: boolean;
+    secretKey: string;
 
     static fromData(
         data: ExportProviderSettings,
@@ -1885,7 +1893,14 @@ export class ExportProviderSettings {
             return data;
         }
         const instance = target || new ExportProviderSettings();
+        instance.accessKey = data.accessKey;
+        instance.awsRegion = data.awsRegion;
+        instance.bucketName = data.bucketName;
+        instance.endPoint = data.endPoint;
+        instance.providerId = data.providerId;
         instance.providerType = data.providerType;
+        instance.secretEncrypted = data.secretEncrypted;
+        instance.secretKey = data.secretKey;
         return instance;
     }
 }
@@ -3356,7 +3371,7 @@ export class ResourceMetadata {
 
 export class RetentionExportConfig {
     exportConfig: ExportConfig;
-    exportProviderSettings: ExportProviderSettings;
+    exportProviderId: string;
 
     static fromData(
         data: RetentionExportConfig,
@@ -3367,16 +3382,14 @@ export class RetentionExportConfig {
         }
         const instance = target || new RetentionExportConfig();
         instance.exportConfig = ExportConfig.fromData(data.exportConfig);
-        instance.exportProviderSettings = ExportProviderSettings.fromData(
-            data.exportProviderSettings,
-        );
+        instance.exportProviderId = data.exportProviderId;
         return instance;
     }
 }
 
 export class RetentionTimeConfig {
     dataRetentionConfig: DataRetentionConfig;
-    exportConfig: RetentionExportConfig;
+    retentionExportConfig: RetentionExportConfig;
 
     static fromData(
         data: RetentionTimeConfig,
@@ -3389,8 +3402,8 @@ export class RetentionTimeConfig {
         instance.dataRetentionConfig = DataRetentionConfig.fromData(
             data.dataRetentionConfig,
         );
-        instance.exportConfig = RetentionExportConfig.fromData(
-            data.exportConfig,
+        instance.retentionExportConfig = RetentionExportConfig.fromData(
+            data.retentionExportConfig,
         );
         return instance;
     }
@@ -4301,6 +4314,8 @@ export type PropertyScope =
     | 'MEASUREMENT_PROPERTY'
     | 'NONE';
 
+export type ProviderType = 'FOLDER' | 'S3';
+
 export type RetentionAction = 'DELETE' | 'SAVE' | 'SAVEDELETE';
 
 export type RetentionInterval = 'DAILY' | 'MONTHLY' | 'WEEKLY';
diff --git a/ui/src/app/configuration/configuration.module.ts 
b/ui/src/app/configuration/configuration.module.ts
index 51f8856b7d..f4ffeaafec 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -107,10 +107,12 @@ import { UserAcknowledgmentComponent } from 
'./general-configuration/user-acknow
 import { QuillEditorComponent } from 'ngx-quill';
 import { MatStepperModule } from '@angular/material/stepper';
 import { DataRetentionDialogComponent } from 
'./dialog/data-retention-dialog/data-retention-dialog.component';
+import { ExportProviderComponent } from 
'./dialog/export-provider-dialog/export-provider-dialog.component';
 import { SelectDataComponent } from 
'./dialog/data-retention-dialog/components/select-retention/select-data.component';
 import { SelectDataRetentionComponent } from 
'./dialog/data-retention-dialog/components/select-retention/select-data-retention/select-data-retention.component';
 import { SelectRetentionActionComponent } from 
'./dialog/data-retention-dialog/components/select-retention/select-retention-action/select-retention-action.component';
 import { SelectDataExportComponent } from 
'./dialog/data-retention-dialog/components/select-export/select-format.component';
+import { DeleteExportProviderComponent } from 
'./dialog/delete-export-provider/delete-export-provider-dialog.component';
 @NgModule({
     imports: [
         CommonModule,
@@ -261,6 +263,7 @@ import { SelectDataExportComponent } from 
'./dialog/data-retention-dialog/compon
         SpRegisteredExtensionsServiceComponent,
         SpExtensionsServiceConfigurationComponent,
         DataRetentionDialogComponent,
+        ExportProviderComponent,
         SpExtensionsInstallationComponent,
         SpExtensionsInstallationDialogComponent,
         EndpointItemComponent,
@@ -276,6 +279,7 @@ import { SelectDataExportComponent } from 
'./dialog/data-retention-dialog/compon
         SelectDataRetentionComponent,
         SelectRetentionActionComponent,
         SelectDataExportComponent,
+        DeleteExportProviderComponent,
     ],
     providers: [
         OrderByPipe,
diff --git 
a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
 
b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
index c5bf509434..52fa1c9191 100644
--- 
a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
+++ 
b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
@@ -279,6 +279,203 @@
                     <h5>{{ '(no stored measurements)' | translate }}</h5>
                 }
             </sp-split-section>
+            <sp-split-section
+                [title]="'Export Providers' | translate"
+                [subtitle]="
+                    'Add, Edit, and Delete export providers used for backing 
up data lakes.'
+                        | translate
+                "
+            >
+                <div fxFlex="100" fxLayout="column">
+                    <div fxFlex="100" fxLayout="row">
+                        <div
+                            fxFlex
+                            fxLayoutAlign="start center"
+                            class="subsection-title"
+                        >
+                            {{ 'Existing Export Providers' | translate }}
+                        </div>
+
+                        <div fxLayout="row" fxLayoutAlign="end center">
+                            <button
+                                color="accent"
+                                mat-icon-button
+                                matTooltip="{{
+                                    'New Export Provider' | translate
+                                }}"
+                                data-cy="new-export-providers"
+                                (click)="createExportProvider(null)"
+                            >
+                                <mat-icon>add</mat-icon>
+                            </button>
+                        </div>
+
+                        <div fxLayout="row" fxLayoutAlign="end center">
+                            <button
+                                color="accent"
+                                mat-icon-button
+                                matTooltip="{{ 'Refresh' | translate }}"
+                                data-cy="refresh-export-providers-measures"
+                                (click)="loadAvailableExportProvider()"
+                            >
+                                <mat-icon>refresh</mat-icon>
+                            </button>
+                        </div>
+                    </div>
+
+                    <table
+                        fxFlex="100"
+                        mat-table
+                        data-cy="exportproviders-settings"
+                        [dataSource]="dataSourceExport"
+                        style="width: 100%"
+                        matSort
+                    >
+                        <ng-container matColumnDef="providertype">
+                            <th
+                                mat-header-cell
+                                mat-sort-header
+                                *matHeaderCellDef
+                            >
+                                {{ 'Provider Type' | translate }}
+                            </th>
+                            <td mat-cell *matCellDef="let configurationEntry">
+                                <h4 style="margin-bottom: 0px">
+                                    {{ configurationEntry.providerType }}
+                                </h4>
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="endpoint">
+                            <th
+                                mat-header-cell
+                                mat-sort-header
+                                *matHeaderCellDef
+                            >
+                                {{ 'Endpoint' | translate }}
+                            </th>
+                            <td mat-cell *matCellDef="let configurationEntry">
+                                {{ configurationEntry.endPoint }}
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="bucket">
+                            <th
+                                mat-header-cell
+                                mat-sort-header
+                                *matHeaderCellDef
+                            >
+                                {{ 'Bucket' | translate }}
+                            </th>
+                            <td mat-cell *matCellDef="let configurationEntry">
+                                {{ configurationEntry.bucketName }}
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="editExportProvider">
+                            <th mat-header-cell *matHeaderCellDef>
+                                {{ 'Edit' | translate }}
+                            </th>
+                            <td mat-cell *matCellDef="let configurationEntry">
+                                <div
+                                    *ngIf="
+                                        configurationEntry.providerId !==
+                                        'FOLDER'
+                                    "
+                                >
+                                    <div fxLayout="row">
+                                        <span
+                                            fxFlex
+                                            fxFlexOrder="3"
+                                            fxLayout="row"
+                                            fxLayoutAlign="start center"
+                                        >
+                                            <button
+                                                color="accent"
+                                                mat-icon-button
+                                                matTooltip="{{
+                                                    'Edit Export Provider'
+                                                        | translate
+                                                }}"
+                                                matTooltipPosition="above"
+                                                
data-cy="exportProvider-edit-btn"
+                                                (click)="
+                                                    createExportProvider(
+                                                        configurationEntry
+                                                    )
+                                                "
+                                            >
+                                                <i class="material-icons"
+                                                    >edit</i
+                                                >
+                                            </button>
+                                        </span>
+                                    </div>
+                                </div>
+                            </td>
+                        </ng-container>
+
+                        <ng-container matColumnDef="delete">
+                            <th mat-header-cell *matHeaderCellDef>
+                                {{ 'Remove' | translate }}
+                            </th>
+                            <td mat-cell *matCellDef="let configurationEntry">
+                                <div
+                                    *ngIf="
+                                        configurationEntry.providerId !==
+                                        'FOLDER'
+                                    "
+                                >
+                                    <div fxLayout="row">
+                                        <span
+                                            fxFlex
+                                            fxFlexOrder="3"
+                                            fxLayout="row"
+                                            fxLayoutAlign="start center"
+                                        >
+                                            <button
+                                                color="accent"
+                                                mat-icon-button
+                                                matTooltip="{{
+                                                    'Remove export provider 
configuration'
+                                                        | translate
+                                                }}"
+                                                
data-cy="exportProvider-delete-btn"
+                                                matTooltipPosition="above"
+                                                (click)="
+                                                    deleteExportProvider(
+                                                        
configurationEntry.providerId
+                                                    )
+                                                "
+                                            >
+                                                <i class="material-icons"
+                                                    >delete</i
+                                                >
+                                            </button>
+                                        </span>
+                                    </div>
+                                </div>
+                            </td>
+                        </ng-container>
+
+                        <tr
+                            mat-header-row
+                            *matHeaderRowDef="displayedColumnsExport"
+                        ></tr>
+                        <tr
+                            mat-row
+                            *matRowDef="
+                                let row;
+                                columns: displayedColumnsExport
+                            "
+                        ></tr>
+                    </table>
+                </div>
+
+                <div *ngIf="availableExportProvider.length === 0">
+                    <h5>{{ 'no stored export providers' | translate }}</h5>
+                </div>
+            </sp-split-section>
         </div>
     </div>
 </sp-basic-nav-tabs>
diff --git 
a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.ts
 
b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.ts
index ef79e9ed71..80a0c30f28 100644
--- 
a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.ts
+++ 
b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.ts
@@ -16,12 +16,13 @@
  *
  */
 
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, inject, OnInit, ViewChild } from '@angular/core';
 import { MatTableDataSource } from '@angular/material/table';
 import { DataLakeConfigurationEntry } from './datalake-configuration-entry';
 import {
     ChartService,
     DatalakeRestService,
+    ExportProviderSettings,
 } from '@streampipes/platform-services';
 import { MatPaginator } from '@angular/material/paginator';
 import { MatSort } from '@angular/material/sort';
@@ -37,6 +38,9 @@ import { DeleteDatalakeIndexComponent } from 
'../dialog/delete-datalake-index/de
 import { SpConfigurationTabsService } from '../configuration-tabs.service';
 import { SpConfigurationRoutes } from '../configuration.routes';
 import { DataRetentionDialogComponent } from 
'../dialog/data-retention-dialog/data-retention-dialog.component';
+import { ExportProviderService } from 
'projects/streampipes/platform-services/src/lib/apis/export-provider.service';
+import { ExportProviderComponent } from 
'../dialog/export-provider-dialog/export-provider-dialog.component';
+import { DeleteExportProviderComponent } from 
'../dialog/delete-export-provider/delete-export-provider-dialog.component';
 import { TranslateService } from '@ngx-translate/core';
 
 @Component({
@@ -51,9 +55,21 @@ export class DatalakeConfigurationComponent implements 
OnInit {
     @ViewChild(MatPaginator) paginator: MatPaginator;
     @ViewChild(MatSort) sort: MatSort;
 
+    private datalakeRestService = inject(DatalakeRestService);
+    private dataViewDataExplorerService = inject(ChartService);
+    private dialogService = inject(DialogService);
+    private breadcrumbService = inject(SpBreadcrumbService);
+    private tabService = inject(SpConfigurationTabsService);
+    private exportProviderRestService = inject(ExportProviderService);
+    private translateService = inject(TranslateService);
+
     dataSource: MatTableDataSource<DataLakeConfigurationEntry> =
         new MatTableDataSource([]);
     availableMeasurements: DataLakeConfigurationEntry[] = [];
+    availableExportProvider: ExportProviderSettings[] = [];
+
+    dataSourceExport: MatTableDataSource<ExportProviderSettings> =
+        new MatTableDataSource([]);
 
     displayedColumns: string[] = [
         'name',
@@ -65,18 +81,17 @@ export class DatalakeConfigurationComponent implements 
OnInit {
         'retention',
     ];
 
+    displayedColumnsExport: string[] = [
+        'providertype',
+        'endpoint',
+        'bucket',
+        'editExportProvider',
+        'delete',
+    ];
+
     pageSize = 15;
     pageIndex = 0;
 
-    constructor(
-        private datalakeRestService: DatalakeRestService,
-        private dataViewDataExplorerService: ChartService,
-        private dialogService: DialogService,
-        private breadcrumbService: SpBreadcrumbService,
-        private tabService: SpConfigurationTabsService,
-        private translateService: TranslateService,
-    ) {}
-
     ngOnInit(): void {
         this.tabs = this.tabService.getTabs();
         this.breadcrumbService.updateBreadcrumb([
@@ -84,6 +99,17 @@ export class DatalakeConfigurationComponent implements 
OnInit {
             { label: this.tabService.getTabTitle('datalake') },
         ]);
         this.loadAvailableMeasurements();
+        this.loadAvailableExportProvider();
+    }
+
+    loadAvailableExportProvider() {
+        this.availableExportProvider = [];
+        this.exportProviderRestService
+            .getAllExportProviders()
+            .subscribe(allExportProviders => {
+                this.availableExportProvider = allExportProviders;
+                this.dataSourceExport.data = this.availableExportProvider;
+            });
     }
 
     loadAvailableMeasurements() {
@@ -133,6 +159,22 @@ export class DatalakeConfigurationComponent implements 
OnInit {
             });
     }
 
+    createExportProvider(provider: ExportProviderSettings | null) {
+        const dialogRef: DialogRef<ExportProviderComponent> =
+            this.dialogService.open(ExportProviderComponent, {
+                panelType: PanelType.STANDARD_PANEL,
+                title: 'New Export Provider',
+                width: '70vw',
+                data: {
+                    provider: provider,
+                },
+            });
+
+        dialogRef.afterClosed().subscribe(() => {
+            this.loadAvailableExportProvider();
+        });
+    }
+
     cleanDatalakeIndex(measurementIndex: string) {
         const dialogRef: DialogRef<DeleteDatalakeIndexComponent> =
             this.dialogService.open(DeleteDatalakeIndexComponent, {
@@ -171,6 +213,24 @@ export class DatalakeConfigurationComponent implements 
OnInit {
         });
     }
 
+    deleteExportProvider(providerId: string) {
+        const dialogRef: DialogRef<DeleteExportProviderComponent> =
+            this.dialogService.open(DeleteExportProviderComponent, {
+                panelType: PanelType.STANDARD_PANEL,
+                title: 'Delete Export Provider',
+                width: '70vw',
+                data: {
+                    providerId: providerId,
+                },
+            });
+
+        dialogRef.afterClosed().subscribe(data => {
+            if (data) {
+                this.loadAvailableExportProvider();
+            }
+        });
+    }
+
     openDownloadDialog(measurementName: string) {
         this.dialogService.open(DataDownloadDialogComponent, {
             panelType: PanelType.SLIDE_IN_PANEL,
diff --git 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.html
 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.html
index 54f7253134..0a98cce1c9 100644
--- 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.html
+++ 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.html
@@ -22,7 +22,8 @@
             <mat-radio-group
                 class="sp-radio-group"
                 [(ngModel)]="
-                    dataRetentionConfig.exportConfig.exportConfig.format
+                    dataRetentionConfig.retentionExportConfig.exportConfig
+                        .format
                 "
             >
                 <mat-radio-button
@@ -50,11 +51,14 @@
         </sp-configuration-box>
     </div>
 
-    @if (dataRetentionConfig.exportConfig.exportConfig.format === 'csv') {
+    @if (
+        dataRetentionConfig.retentionExportConfig.exportConfig.format === 'csv'
+    ) {
         <sp-configuration-box [title]="'Delimiter' | translate">
             <mat-radio-group
                 [(ngModel)]="
-                    dataRetentionConfig.exportConfig.exportConfig.delimiter
+                    dataRetentionConfig.retentionExportConfig.exportConfig
+                        .csvDelimiter
                 "
                 class="sp-radio-group"
             >
@@ -73,18 +77,71 @@
             </mat-radio-group>
         </sp-configuration-box>
     }
-    <mat-form-field appearance="fill" class="sp-select-field">
-        <mat-label>{{ 'Select Provider' | translate }}</mat-label>
-        <mat-select
-            [(ngModel)]="
-                dataRetentionConfig.exportConfig.exportProviderSettings
-                    .providerType
-            "
-            data-cy="exportProvider-select"
-        >
-            <mat-option value="local" data-cy="exportProvider-local">
-                {{ 'local' | translate }}
-            </mat-option>
-        </mat-select>
-    </mat-form-field>
+    <sp-configuration-box [title]="'Export Provider' | translate">
+        <div class="mt-10 p-10">
+            <mat-form-field class="w-100">
+                <mat-label>{{ 'Select Provider Type' | translate }}</mat-label>
+                <mat-select
+                    [(ngModel)]="selectedProviderType"
+                    data-cy="exportProvider-select"
+                    (selectionChange)="onProviderTypeChange($event.value)"
+                >
+                    @for (type of providerType; track type) {
+                        <mat-option
+                            [value]="type.toUpperCase()"
+                            [attr.data-cy]="
+                                'exportProvider-' + type.toLowerCase()
+                            "
+                        >
+                            {{ type | translate }}
+                        </mat-option>
+                    }
+                </mat-select>
+            </mat-form-field>
+            @if (selectedProviderType === 'S3') {
+                <mat-form-field class="w-100">
+                    <mat-label>{{ 'Select Provider' | translate }}</mat-label>
+                    <mat-select
+                        [(ngModel)]="
+                            dataRetentionConfig.retentionExportConfig
+                                .exportProviderId
+                        "
+                        data-cy="exportProviderId-select"
+                    >
+                        @for (
+                            provider of availableS3ExportProvider;
+                            track provider
+                        ) {
+                            <mat-option
+                                [value]="provider.providerId"
+                                [attr.data-cy]="
+                                    'exportProviderId-' + provider.providerId
+                                "
+                                style="font-size: 12px"
+                            >
+                                {{
+                                    'Endpoint: ' +
+                                        provider.endPoint +
+                                        ' Bucket: ' +
+                                        provider.bucketName
+                                }}
+                            </mat-option>
+                        }
+                    </mat-select>
+                </mat-form-field>
+            }
+        </div>
+        @if (
+            selectedProviderType === 'S3' &&
+            availableS3ExportProvider?.length === 0
+        ) {
+            <div fxLayout="row" fxLayoutAlign="start center">
+                <mat-icon color="primary" class="mr-5 
info-icon">info</mat-icon>
+                {{
+                    'No export providers found. Please create one first.'
+                        | translate
+                }}
+            </div>
+        }
+    </sp-configuration-box>
 </sp-configuration-box>
diff --git 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.scss
 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.scss
index 2b53bbd279..a7bb6246ac 100644
--- 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.scss
+++ 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.scss
@@ -15,3 +15,22 @@
  *  limitations under the License.
  *
  */
+.invalid-uri {
+    border: var(--color-warn);
+}
+
+.s3-config-container {
+    margin-top: 5px;
+    padding: 15px;
+    background-color: var(--color-bg-2);
+    border-radius: 8px;
+}
+
+.s3-config-container mat-form-field {
+    margin-bottom: 5px;
+}
+
+mat-hint {
+    color: red;
+    font-size: 12px;
+}
diff --git 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.ts
 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.ts
index 7de468301f..c6b8a54c17 100644
--- 
a/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.ts
+++ 
b/ui/src/app/configuration/dialog/data-retention-dialog/components/select-export/select-format.component.ts
@@ -16,9 +16,13 @@
  *
  */
 
-import { Component, Input, OnInit } from '@angular/core';
-import { DataExplorerDataConfig } from '@streampipes/platform-services';
+import { Component, inject, Input, OnInit } from '@angular/core';
+import {
+    DataExplorerDataConfig,
+    ExportProviderSettings,
+} from '@streampipes/platform-services';
 import { RetentionTimeConfig } from '@streampipes/platform-services';
+import { ExportProviderService } from 
'projects/streampipes/platform-services/src/lib/apis/export-provider.service';
 
 @Component({
     selector: 'sp-data-export',
@@ -26,9 +30,92 @@ import { RetentionTimeConfig } from 
'@streampipes/platform-services';
     styleUrls: ['./select-format.component.scss'],
     standalone: false,
 })
-export class SelectDataExportComponent {
+export class SelectDataExportComponent implements OnInit {
     @Input()
     dataExplorerDataConfig: DataExplorerDataConfig;
     @Input()
     dataRetentionConfig: RetentionTimeConfig;
+
+    exportProviderRestService = inject(ExportProviderService);
+
+    exportProvider: ExportProviderSettings;
+
+    availableExportProvider: ExportProviderSettings[] = [];
+    availableS3ExportProvider: ExportProviderSettings[] = [];
+    availableFolderExportProvider: ExportProviderSettings[] = [];
+    providerType: string[] = ['Folder', 'S3'];
+    selectedProviderType: string;
+    selectedProviderId: string;
+
+    ngOnInit() {
+        this.loadAvailableExportProvider();
+        this.selectedProviderType = 'FOLDER';
+
+        if (
+            this.dataRetentionConfig.retentionExportConfig.exportProviderId !==
+            ''
+        ) {
+            this.exportProviderRestService
+                .getExportProviderById(
+                    this.dataRetentionConfig.retentionExportConfig
+                        .exportProviderId,
+                )
+                .subscribe(exportProvider => {
+                    this.exportProvider = exportProvider;
+                    if (this.exportProvider) {
+                        this.selectedProviderType =
+                            this.exportProvider.providerType;
+
+                        this.selectedProviderId =
+                            this.exportProvider.providerId;
+                        
this.dataRetentionConfig.retentionExportConfig.exportProviderId =
+                            this.selectedProviderId;
+                    }
+                });
+        }
+    }
+
+    loadAvailableExportProvider() {
+        this.availableExportProvider = [];
+        this.exportProviderRestService
+            .getAllExportProviders()
+            .subscribe(allExportProviders => {
+                this.availableExportProvider = allExportProviders;
+
+                this.availableS3ExportProvider =
+                    this.availableExportProvider.filter(
+                        provider => provider.providerType === 'S3',
+                    );
+                this.availableFolderExportProvider =
+                    this.availableExportProvider.filter(
+                        provider => provider.providerType === 'FOLDER',
+                    );
+                // Defualts to Folder
+                
this.dataRetentionConfig.retentionExportConfig.exportProviderId =
+                    this.availableFolderExportProvider[0].providerId;
+            });
+    }
+    onProviderTypeChange(type: string): void {
+        this.selectedProviderType = type;
+        // sets default
+        if (
+            type === 'FOLDER' &&
+            this.availableFolderExportProvider.length > 0
+        ) {
+            this.dataRetentionConfig.retentionExportConfig.exportProviderId =
+                this.availableFolderExportProvider[0].providerId;
+            this.selectedProviderId =
+                this.availableFolderExportProvider[0].providerId;
+        } else if (type === 'S3' && this.availableS3ExportProvider.length > 0) 
{
+            this.dataRetentionConfig.retentionExportConfig.exportProviderId =
+                this.availableS3ExportProvider[0].providerId;
+            this.selectedProviderId =
+                this.availableS3ExportProvider[0].providerId;
+        } else {
+            // no providers available for this type, clear the exportProviderId
+            this.dataRetentionConfig.retentionExportConfig.exportProviderId =
+                '';
+            this.selectedProviderId = '';
+        }
+    }
 }
diff --git 
a/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.html
 
b/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.html
index 399b5c5036..b190cdd024 100644
--- 
a/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.html
+++ 
b/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.html
@@ -52,9 +52,19 @@
             color="accent"
             data-cy="download-configuration-download-btn"
             (click)="setCleanUp()"
+            [disabled]="requiresExportValidation() && !isExportValid()"
         >
             {{ 'Start Sync' | translate }}
         </button>
+
+        <button
+            mat-flat-button
+            color="accent"
+            data-cy="download-configuration-download-btn"
+            (click)="deleteCleanUp()"
+        >
+            {{ 'Delete Sync' | translate }}
+        </button>
         <button
             mat-flat-button
             class="mat-basic"
diff --git 
a/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.ts
 
b/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.ts
index f1f5139fd7..f7d7c90d84 100644
--- 
a/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.ts
+++ 
b/ui/src/app/configuration/dialog/data-retention-dialog/data-retention-dialog.component.ts
@@ -16,13 +16,15 @@
  *
  */
 
-import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { Component, inject, Input, OnInit, ViewChild } from '@angular/core';
 import { DialogRef } from '@streampipes/shared-ui';
 import { DataRetentionDialogModel } from './model/data-retention-dialog.model';
 import {
     DatalakeRestService,
+    ExportProviderSettings,
     RetentionTimeConfig,
 } from '@streampipes/platform-services';
+import { ExportProviderService } from 
'projects/streampipes/platform-services/src/lib/apis/export-provider.service';
 
 @Component({
     selector: 'sp-data-retention-dialog',
@@ -34,14 +36,13 @@ export class DataRetentionDialogComponent implements OnInit 
{
     @Input() dataRetentionDialogModel: DataRetentionDialogModel;
 
     retentionConfig: RetentionTimeConfig;
+    exportProvider: ExportProviderSettings;
 
     @Input()
     measurementIndex: string;
 
-    constructor(
-        public dialogRef: DialogRef<DataRetentionDialogComponent>,
-        private datalakeRestService: DatalakeRestService,
-    ) {}
+    dialogRef = inject(DialogRef<DataRetentionDialogComponent>);
+    datalakeRestService = inject(DatalakeRestService);
 
     ngOnInit() {
         this.datalakeRestService
@@ -60,17 +61,14 @@ export class DataRetentionDialogComponent implements OnInit 
{
                                 interval: 'DAILY',
                                 action: 'DELETE',
                             },
-                            exportConfig: {
+                            retentionExportConfig: {
                                 exportConfig: {
                                     format: 'csv',
                                     csvDelimiter: 'comma',
                                     missingValueBehaviour: 'ignore',
                                     headerColumnName: 'key',
                                 },
-                                exportProviderSettings: {
-                                    providerType: 'local',
-                                    path: './output',
-                                },
+                                exportProviderId: '',
                             },
                         } as RetentionTimeConfig);
                     }
@@ -96,4 +94,40 @@ export class DataRetentionDialogComponent implements OnInit {
                 this.close(true);
             });
     }
+
+    deleteCleanUp() {
+        this.datalakeRestService
+            .deleteCleanup(this.measurementIndex)
+            .subscribe(data => {
+                this.close(true);
+            });
+    }
+
+    requiresExportValidation(): boolean {
+        const action = this.retentionConfig?.dataRetentionConfig?.action;
+        return action === 'SAVE' || action === 'SAVEDELETE';
+    }
+
+    isExportValid(): boolean {
+        const exportConfig =
+            this.retentionConfig?.retentionExportConfig?.exportConfig;
+        const providerId =
+            this.retentionConfig?.retentionExportConfig?.exportProviderId;
+        if (!exportConfig?.format) {
+            console.error('Export format is required.');
+            return false;
+        }
+
+        if (exportConfig.format === 'csv' && !exportConfig.csvDelimiter) {
+            console.error('CSV delimiter is required for CSV format.');
+            return false;
+        }
+
+        if (providerId == '') {
+            console.error('S3 provider details must be selected.');
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git 
a/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.html
 
b/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.html
new file mode 100644
index 0000000000..6c2cbee36c
--- /dev/null
+++ 
b/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.html
@@ -0,0 +1,82 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <div *ngIf="!isInProgress" fxLayout="column">
+            <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column">
+                <b
+                    ><h4>
+                        {{
+                            'Do you really want to delete the export provider?'
+                                | translate
+                        }}
+                    </h4></b
+                >
+                <b
+                    ><h5>
+                        {{
+                            'This operation cannot be undone. Please ensure 
that the data provider is not used in a datalake retention.'
+                                | translate
+                        }}
+                    </h5></b
+                >
+            </div>
+
+            <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column">
+                <button
+                    mat-button
+                    mat-flat-button
+                    color="accent"
+                    data-cy="confirm-delete-data-btn"
+                    (click)="deleteExportProvider()"
+                >
+                    {{ 'Delete Data' | translate }}
+                </button>
+            </div>
+        </div>
+        <div
+            fxFlex="100"
+            fxLayoutAlign="center center"
+            fxLayout="column"
+            *ngIf="isInProgress"
+            data-cy="adapter-deletion-in-progress"
+        >
+            <div fxLayout="row" fxLayoutAlign="space-around">
+                <mat-spinner
+                    [mode]="'indeterminate'"
+                    color="accent"
+                ></mat-spinner>
+            </div>
+            <b
+                ><h4>{{ currentStatus }}</h4></b
+            >
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions actions-align-right">
+        <button
+            mat-button
+            mat-flat-button
+            class="mat-basic"
+            (click)="close(false)"
+        >
+            Close
+        </button>
+    </div>
+</div>
diff --git 
a/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.ts
 
b/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.ts
new file mode 100644
index 0000000000..9349fcd2aa
--- /dev/null
+++ 
b/ui/src/app/configuration/dialog/delete-export-provider/delete-export-provider-dialog.component.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, inject, Input } from '@angular/core';
+import { DialogRef } from '@streampipes/shared-ui';
+import { ExportProviderService } from 
'projects/streampipes/platform-services/src/lib/apis/export-provider.service';
+
+@Component({
+    selector: 'sp-delete-export-provider-dialog',
+    templateUrl: './delete-export-provider-dialog.component.html',
+    standalone: false,
+})
+export class DeleteExportProviderComponent {
+    @Input()
+    providerId: string;
+
+    private dialogRef = inject(DialogRef<DeleteExportProviderComponent>);
+    private exportProviderRestService = inject(ExportProviderService);
+
+    isInProgress = false;
+    currentStatus: any;
+
+    close(refreshDataLakeIndex: boolean) {
+        this.dialogRef.close(refreshDataLakeIndex);
+    }
+
+    deleteExportProvider() {
+        this.isInProgress = true;
+        this.currentStatus = 'Deleting export provider.';
+        this.exportProviderRestService
+            .deleteExportProvider(this.providerId)
+            .subscribe(data => {
+                this.close(true);
+            });
+    }
+}
diff --git 
a/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.html
 
b/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.html
new file mode 100644
index 0000000000..883197ccd0
--- /dev/null
+++ 
b/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.html
@@ -0,0 +1,121 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <form [formGroup]="exportForm">
+            <mat-form-field class="sp-select-field">
+                <mat-label>{{ 'Select Provider' | translate }}</mat-label>
+                <mat-select
+                    formControlName="providerType"
+                    data-cy="exportProvider-select"
+                >
+                    <mat-option value="S3" data-cy="exportProvider-s3">
+                        {{ 'S3' | translate }}
+                    </mat-option>
+                </mat-select>
+            </mat-form-field>
+            @if (exportForm.get('providerType')?.value === 'S3') {
+                <div class="s3-config-container">
+                    <mat-form-field class="w-100">
+                        <mat-label>{{ 'Access Key' | translate }}</mat-label>
+                        <input
+                            matInput
+                            formControlName="accessKey"
+                            placeholder="{{ 'Enter Access Key' | translate }}"
+                            data-cy="exportProvider-s3-accessKey"
+                        />
+                    </mat-form-field>
+
+                    <mat-form-field class="w-100">
+                        <mat-label>{{ 'Secret Key' | translate }}</mat-label>
+                        <input
+                            matInput
+                            type="password"
+                            formControlName="secretKey"
+                            placeholder="{{ 'Enter Secret Key' | translate }}"
+                            data-cy="exportProvider-s3-secretKey"
+                        />
+                    </mat-form-field>
+
+                    <mat-form-field class="w-100">
+                        <mat-label>{{ 'Endpoint' | translate }}</mat-label>
+                        <input
+                            matInput
+                            formControlName="endPoint"
+                            placeholder="{{ 'Enter Endpoint' | translate }}"
+                            data-cy="exportProvider-s3-endpoint"
+                        />
+                        @if (
+                            exportForm.get('endPoint')?.hasError('invalidUri')
+                        ) {
+                            <mat-error
+                                >{{ 'Invalid URI format.' | translate }}
+                            </mat-error>
+                        }
+                    </mat-form-field>
+
+                    <mat-form-field class="w-100">
+                        <mat-label>{{ 'Bucket' | translate }}</mat-label>
+                        <input
+                            matInput
+                            formControlName="bucketName"
+                            placeholder="{{ 'Enter Bucket Name' | translate }}"
+                            data-cy="exportProvider-s3-bucket"
+                        />
+                    </mat-form-field>
+
+                    <mat-form-field class="w-100">
+                        <mat-label>{{ 'Region' | translate }}</mat-label>
+                        <input
+                            matInput
+                            formControlName="awsRegion"
+                            placeholder="{{ 'Enter Region Name' | translate }}"
+                            data-cy="exportProvider-s3-region"
+                        />
+                    </mat-form-field>
+                </div>
+            }
+
+            <mat-divider></mat-divider>
+
+            <div class="sp-dialog-actions actions-align-right">
+                <button
+                    mat-button
+                    mat-flat-button
+                    class="mat-basic"
+                    type="button"
+                    (click)="addData()"
+                    [disabled]="exportForm.invalid"
+                >
+                    Save
+                </button>
+
+                <button
+                    mat-button
+                    mat-flat-button
+                    class="mat-basic"
+                    style="margin-left: 12px"
+                    type="button"
+                    (click)="close(false)"
+                >
+                    Close
+                </button>
+            </div>
+        </form>
+    </div>
+</div>
diff --git 
a/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.ts
 
b/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.ts
new file mode 100644
index 0000000000..ebf0afb436
--- /dev/null
+++ 
b/ui/src/app/configuration/dialog/export-provider-dialog/export-provider-dialog.component.ts
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import { Component, inject, Input, OnInit } from '@angular/core';
+import {
+    AbstractControl,
+    FormBuilder,
+    FormGroup,
+    ValidationErrors,
+    Validators,
+} from '@angular/forms';
+import { DialogRef } from '@streampipes/shared-ui';
+import { ExportProviderSettings } from '@streampipes/platform-services';
+import { ExportProviderService } from 
'projects/streampipes/platform-services/src/lib/apis/export-provider.service';
+
+@Component({
+    selector: 'sp-export-provider-dialog',
+    templateUrl: './export-provider-dialog.component.html',
+    standalone: false,
+})
+export class ExportProviderComponent implements OnInit {
+    @Input()
+    provider: ExportProviderSettings;
+
+    private dialogRef = inject<DialogRef<ExportProviderComponent>>(DialogRef);
+    private exportProviderRestService = inject(ExportProviderService);
+    private fb = inject(FormBuilder);
+
+    exportForm: FormGroup;
+
+    ngOnInit() {
+        this.initForm();
+
+        if (this.provider) {
+            this.exportForm.patchValue(this.provider);
+        }
+
+        this.exportForm.get('providerType')?.valueChanges.subscribe(type => {
+            this.toggleS3Fields(type === 'S3');
+        });
+        this.toggleS3Fields(
+            this.exportForm.get('providerType')?.value === 'S3',
+        );
+    }
+
+    initForm() {
+        this.exportForm = this.fb.group({
+            providerType: ['FOLDER', Validators.required],
+            accessKey: ['', Validators.required],
+            secretKey: ['', Validators.required],
+            endPoint: ['', [Validators.required, this.uriValidator]],
+            bucketName: ['', Validators.required],
+            awsRegion: ['US_EAST_1', Validators.required],
+            providerId: [''],
+            secretEncrypted: [false],
+        });
+    }
+
+    toggleS3Fields(enabled: boolean) {
+        const fields = [
+            'accessKey',
+            'secretKey',
+            'endPoint',
+            'bucketName',
+            'awsRegion',
+        ];
+        fields.forEach(field => {
+            const control = this.exportForm.get(field);
+            if (enabled) {
+                control?.setValidators(Validators.required);
+                if (field === 'endPoint') {
+                    control?.addValidators(this.uriValidator.bind(this));
+                }
+                control?.enable();
+            } else {
+                control?.clearValidators();
+                control?.disable();
+            }
+            control?.updateValueAndValidity();
+        });
+    }
+    uriValidator(control: AbstractControl): ValidationErrors | null {
+        const value = control.value;
+        if (!value) return null;
+
+        try {
+            new URL(value);
+            return null;
+        } catch (e) {
+            return { invalidUri: true };
+        }
+    }
+
+    addData() {
+        if (this.exportForm.invalid) {
+            this.exportForm.markAllAsTouched();
+            return;
+        }
+
+        const formValue: ExportProviderSettings = this.exportForm.value;
+
+        if (!formValue.providerId) {
+            formValue.providerId = this.makeProviderId();
+        }
+        formValue.awsRegion = formValue.awsRegion;
+
+        this.exportProviderRestService
+            .updateExportProvider(formValue)
+            .subscribe(() => this.dialogRef.close(true));
+    }
+
+    close(refresh: boolean) {
+        this.dialogRef.close(refresh);
+    }
+
+    private makeProviderId(): string {
+        return 'p' + Math.random().toString(36).substring(2, 9);
+    }
+}

Reply via email to